xref: /dokuwiki/inc/Subscriptions/BulkSubscriptionSender.php (revision 7d34963b3e75ea04c63ec066a6b7a692e123cb53)
1704a815fSMichael Große<?php
2704a815fSMichael Große
3704a815fSMichael Große
4704a815fSMichael Großenamespace dokuwiki\Subscriptions;
5704a815fSMichael Große
6704a815fSMichael Große
7704a815fSMichael Großeuse dokuwiki\ChangeLog\PageChangeLog;
851ee2399SGerrit Uitslaguse dokuwiki\Extension\AuthPlugin;
9704a815fSMichael Großeuse dokuwiki\Input\Input;
1051ee2399SGerrit Uitslaguse Exception;
11704a815fSMichael Große
12704a815fSMichael Großeclass BulkSubscriptionSender extends SubscriptionSender
13704a815fSMichael Große{
14704a815fSMichael Große
15704a815fSMichael Große    /**
16704a815fSMichael Große     * Send digest and list subscriptions
17704a815fSMichael Große     *
18704a815fSMichael Große     * This sends mails to all subscribers that have a subscription for namespaces above
19704a815fSMichael Große     * the given page if the needed $conf['subscribe_time'] has passed already.
20704a815fSMichael Große     *
21704a815fSMichael Große     * This function is called form lib/exe/indexer.php
22704a815fSMichael Große     *
23704a815fSMichael Große     * @param string $page
24704a815fSMichael Große     * @return int number of sent mails
2551ee2399SGerrit Uitslag     * @throws Exception
26704a815fSMichael Große     */
27704a815fSMichael Große    public function sendBulk($page)
28704a815fSMichael Große    {
29704a815fSMichael Große        $subscriberManager = new SubscriberManager();
30704a815fSMichael Große        if (!$subscriberManager->isenabled()) {
31704a815fSMichael Große            return 0;
32704a815fSMichael Große        }
33704a815fSMichael Große
3451ee2399SGerrit Uitslag        /** @var AuthPlugin $auth */
35704a815fSMichael Große        global $auth;
36704a815fSMichael Große        global $conf;
37704a815fSMichael Große        global $USERINFO;
38704a815fSMichael Große        /** @var Input $INPUT */
39704a815fSMichael Große        global $INPUT;
40704a815fSMichael Große        $count = 0;
41704a815fSMichael Große
42704a815fSMichael Große        $subscriptions = $subscriberManager->subscribers($page, null, ['digest', 'list']);
43704a815fSMichael Große
44704a815fSMichael Große        // remember current user info
45704a815fSMichael Große        $olduinfo = $USERINFO;
46704a815fSMichael Große        $olduser = $INPUT->server->str('REMOTE_USER');
47704a815fSMichael Große
48704a815fSMichael Große        foreach ($subscriptions as $target => $users) {
49704a815fSMichael Große            if (!$this->lock($target)) {
50704a815fSMichael Große                continue;
51704a815fSMichael Große            }
52704a815fSMichael Große
53704a815fSMichael Große            foreach ($users as $user => $info) {
545983e241SAndreas Gohr                [$style, $lastupdate] = $info;
55704a815fSMichael Große
56704a815fSMichael Große                $lastupdate = (int)$lastupdate;
57704a815fSMichael Große                if ($lastupdate + $conf['subscribe_time'] > time()) {
58704a815fSMichael Große                    // Less than the configured time period passed since last
59704a815fSMichael Große                    // update.
60704a815fSMichael Große                    continue;
61704a815fSMichael Große                }
62704a815fSMichael Große
63704a815fSMichael Große                // Work as the user to make sure ACLs apply correctly
64704a815fSMichael Große                $USERINFO = $auth->getUserData($user);
65704a815fSMichael Große                $INPUT->server->set('REMOTE_USER', $user);
66704a815fSMichael Große                if ($USERINFO === false) {
67704a815fSMichael Große                    continue;
68704a815fSMichael Große                }
69704a815fSMichael Große                if (!$USERINFO['mail']) {
70704a815fSMichael Große                    continue;
71704a815fSMichael Große                }
72704a815fSMichael Große
73704a815fSMichael Große                if (substr($target, -1, 1) === ':') {
74704a815fSMichael Große                    // subscription target is a namespace, get all changes within
75704a815fSMichael Große                    $changes = getRecentsSince($lastupdate, null, getNS($target));
76704a815fSMichael Große                } else {
77704a815fSMichael Große                    // single page subscription, check ACL ourselves
78704a815fSMichael Große                    if (auth_quickaclcheck($target) < AUTH_READ) {
79704a815fSMichael Große                        continue;
80704a815fSMichael Große                    }
81704a815fSMichael Große                    $meta = p_get_metadata($target);
82704a815fSMichael Große                    $changes = [$meta['last_change']];
83704a815fSMichael Große                }
84704a815fSMichael Große
85704a815fSMichael Große                // Filter out pages only changed in small and own edits
86704a815fSMichael Große                $change_ids = [];
87704a815fSMichael Große                foreach ($changes as $rev) {
88704a815fSMichael Große                    $n = 0;
89e2905cd4SAndreas Gohr                    $pagelog = new PageChangeLog($rev['id']);
90*7d34963bSAndreas Gohr                    while (
91*7d34963bSAndreas Gohr                        !is_null($rev) && $rev['date'] >= $lastupdate &&
92704a815fSMichael Große                        ($INPUT->server->str('REMOTE_USER') === $rev['user'] ||
93252acce3SSatoshi Sahara                            $rev['type'] === DOKU_CHANGE_TYPE_MINOR_EDIT)
94252acce3SSatoshi Sahara                    ) {
95e2905cd4SAndreas Gohr                        $revisions = $pagelog->getRevisions($n++, 1);
965983e241SAndreas Gohr                        $rev = ($revisions !== []) ? $pagelog->getRevisionInfo($revisions[0]) : null;
97704a815fSMichael Große                    }
98704a815fSMichael Große
99704a815fSMichael Große                    if (!is_null($rev) && $rev['date'] >= $lastupdate) {
100704a815fSMichael Große                        // Some change was not a minor one and not by myself
101704a815fSMichael Große                        $change_ids[] = $rev['id'];
102704a815fSMichael Große                    }
103704a815fSMichael Große                }
104704a815fSMichael Große
105704a815fSMichael Große                // send it
106704a815fSMichael Große                if ($style === 'digest') {
107704a815fSMichael Große                    foreach ($change_ids as $change_id) {
108704a815fSMichael Große                        $this->sendDigest(
109704a815fSMichael Große                            $USERINFO['mail'],
110704a815fSMichael Große                            $change_id,
111704a815fSMichael Große                            $lastupdate
112704a815fSMichael Große                        );
113704a815fSMichael Große                        $count++;
114704a815fSMichael Große                    }
1155983e241SAndreas Gohr                } elseif ($style === 'list') {
116704a815fSMichael Große                    $this->sendList($USERINFO['mail'], $change_ids, $target);
117704a815fSMichael Große                    $count++;
118704a815fSMichael Große                }
119704a815fSMichael Große                // TODO: Handle duplicate subscriptions.
120704a815fSMichael Große
121704a815fSMichael Große                // Update notification time.
122704a815fSMichael Große                $subscriberManager->add($target, $user, $style, time());
123704a815fSMichael Große            }
124704a815fSMichael Große            $this->unlock($target);
125704a815fSMichael Große        }
126704a815fSMichael Große
127704a815fSMichael Große        // restore current user info
128704a815fSMichael Große        $USERINFO = $olduinfo;
129704a815fSMichael Große        $INPUT->server->set('REMOTE_USER', $olduser);
130704a815fSMichael Große        return $count;
131704a815fSMichael Große    }
132704a815fSMichael Große
133704a815fSMichael Große    /**
134704a815fSMichael Große     * Lock subscription info
135704a815fSMichael Große     *
136704a815fSMichael Große     * We don't use io_lock() her because we do not wait for the lock and use a larger stale time
137704a815fSMichael Große     *
138704a815fSMichael Große     * @param string $id The target page or namespace, specified by id; Namespaces
139704a815fSMichael Große     *                   are identified by appending a colon.
140704a815fSMichael Große     *
141704a815fSMichael Große     * @return bool true, if you got a succesful lock
142704a815fSMichael Große     * @author Adrian Lang <lang@cosmocode.de>
143704a815fSMichael Große     */
144704a815fSMichael Große    protected function lock($id)
145704a815fSMichael Große    {
146704a815fSMichael Große        global $conf;
147704a815fSMichael Große
148704a815fSMichael Große        $lock = $conf['lockdir'] . '/_subscr_' . md5($id) . '.lock';
149704a815fSMichael Große
150704a815fSMichael Große        if (is_dir($lock) && time() - @filemtime($lock) > 60 * 5) {
151704a815fSMichael Große            // looks like a stale lock - remove it
152704a815fSMichael Große            @rmdir($lock);
153704a815fSMichael Große        }
154704a815fSMichael Große
155704a815fSMichael Große        // try creating the lock directory
156bd539124SAndreas Gohr        if (!@mkdir($lock)) {
157704a815fSMichael Große            return false;
158704a815fSMichael Große        }
159704a815fSMichael Große
16023420346SDamien Regad        if ($conf['dperm']) {
161704a815fSMichael Große            chmod($lock, $conf['dperm']);
162704a815fSMichael Große        }
163704a815fSMichael Große        return true;
164704a815fSMichael Große    }
165704a815fSMichael Große
166704a815fSMichael Große    /**
167704a815fSMichael Große     * Unlock subscription info
168704a815fSMichael Große     *
169704a815fSMichael Große     * @param string $id The target page or namespace, specified by id; Namespaces
170704a815fSMichael Große     *                   are identified by appending a colon.
171704a815fSMichael Große     *
172704a815fSMichael Große     * @return bool
173704a815fSMichael Große     * @author Adrian Lang <lang@cosmocode.de>
174704a815fSMichael Große     */
175704a815fSMichael Große    protected function unlock($id)
176704a815fSMichael Große    {
177704a815fSMichael Große        global $conf;
178704a815fSMichael Große        $lock = $conf['lockdir'] . '/_subscr_' . md5($id) . '.lock';
179704a815fSMichael Große        return @rmdir($lock);
180704a815fSMichael Große    }
181704a815fSMichael Große
182704a815fSMichael Große    /**
183704a815fSMichael Große     * Send a digest mail
184704a815fSMichael Große     *
185704a815fSMichael Große     * Sends a digest mail showing a bunch of changes of a single page. Basically the same as sendPageDiff()
186704a815fSMichael Große     * but determines the last known revision first
187704a815fSMichael Große     *
188704a815fSMichael Große     * @param string $subscriber_mail The target mail address
189704a815fSMichael Große     * @param string $id              The ID
190704a815fSMichael Große     * @param int    $lastupdate      Time of the last notification
191704a815fSMichael Große     *
192704a815fSMichael Große     * @return bool
193704a815fSMichael Große     * @author Adrian Lang <lang@cosmocode.de>
194704a815fSMichael Große     *
195704a815fSMichael Große     */
196704a815fSMichael Große    protected function sendDigest($subscriber_mail, $id, $lastupdate)
197704a815fSMichael Große    {
198704a815fSMichael Große        $pagelog = new PageChangeLog($id);
199704a815fSMichael Große        $n = 0;
200704a815fSMichael Große        do {
201704a815fSMichael Große            $rev = $pagelog->getRevisions($n++, 1);
2025983e241SAndreas Gohr            $rev = ($rev !== []) ? $rev[0] : null;
203704a815fSMichael Große        } while (!is_null($rev) && $rev > $lastupdate);
204704a815fSMichael Große
205704a815fSMichael Große        // TODO I'm not happy with the following line and passing $this->mailer around. Not sure how to solve it better
206704a815fSMichael Große        $pageSubSender = new PageSubscriptionSender($this->mailer);
207704a815fSMichael Große        return $pageSubSender->sendPageDiff(
208704a815fSMichael Große            $subscriber_mail,
209704a815fSMichael Große            'subscr_digest',
210704a815fSMichael Große            $id,
211704a815fSMichael Große            $rev
212704a815fSMichael Große        );
213704a815fSMichael Große    }
214704a815fSMichael Große
215704a815fSMichael Große    /**
216704a815fSMichael Große     * Send a list mail
217704a815fSMichael Große     *
218704a815fSMichael Große     * Sends a list mail showing a list of changed pages.
219704a815fSMichael Große     *
220704a815fSMichael Große     * @param string $subscriber_mail The target mail address
221704a815fSMichael Große     * @param array  $ids             Array of ids
222704a815fSMichael Große     * @param string $ns_id           The id of the namespace
223704a815fSMichael Große     *
224704a815fSMichael Große     * @return bool true if a mail was sent
225704a815fSMichael Große     * @author Adrian Lang <lang@cosmocode.de>
226704a815fSMichael Große     *
227704a815fSMichael Große     */
228704a815fSMichael Große    protected function sendList($subscriber_mail, $ids, $ns_id)
229704a815fSMichael Große    {
2305983e241SAndreas Gohr        if ($ids === []) {
231704a815fSMichael Große            return false;
232704a815fSMichael Große        }
233704a815fSMichael Große
234704a815fSMichael Große        $tlist = '';
235704a815fSMichael Große        $hlist = '<ul>';
236704a815fSMichael Große        foreach ($ids as $id) {
237704a815fSMichael Große            $link = wl($id, [], true);
238704a815fSMichael Große            $tlist .= '* ' . $link . NL;
239704a815fSMichael Große            $hlist .= '<li><a href="' . $link . '">' . hsc($id) . '</a></li>' . NL;
240704a815fSMichael Große        }
241704a815fSMichael Große        $hlist .= '</ul>';
242704a815fSMichael Große
243704a815fSMichael Große        $id = prettyprint_id($ns_id);
244704a815fSMichael Große        $trep = [
245704a815fSMichael Große            'DIFF' => rtrim($tlist),
246704a815fSMichael Große            'PAGE' => $id,
247704a815fSMichael Große            'SUBSCRIBE' => wl($id, ['do' => 'subscribe'], true, '&'),
248704a815fSMichael Große        ];
249704a815fSMichael Große        $hrep = [
250704a815fSMichael Große            'DIFF' => $hlist,
251704a815fSMichael Große        ];
252704a815fSMichael Große
253704a815fSMichael Große        return $this->send(
254704a815fSMichael Große            $subscriber_mail,
255704a815fSMichael Große            'subscribe_list',
256704a815fSMichael Große            $ns_id,
257704a815fSMichael Große            'subscr_list',
258704a815fSMichael Große            $trep,
259704a815fSMichael Große            $hrep
260704a815fSMichael Große        );
261704a815fSMichael Große    }
262704a815fSMichael Große}
263