xref: /dokuwiki/inc/Subscriptions/BulkSubscriptionSender.php (revision 704a815fe827ce918bab0973cde563705c65c698)
1*704a815fSMichael Große<?php
2*704a815fSMichael Große
3*704a815fSMichael Große
4*704a815fSMichael Großenamespace dokuwiki\Subscriptions;
5*704a815fSMichael Große
6*704a815fSMichael Große
7*704a815fSMichael Großeuse dokuwiki\ChangeLog\PageChangeLog;
8*704a815fSMichael Großeuse dokuwiki\Input\Input;
9*704a815fSMichael Großeuse DokuWiki_Auth_Plugin;
10*704a815fSMichael Große
11*704a815fSMichael Großeclass BulkSubscriptionSender extends SubscriptionSender
12*704a815fSMichael Große{
13*704a815fSMichael Große
14*704a815fSMichael Große    /**
15*704a815fSMichael Große     * Send digest and list subscriptions
16*704a815fSMichael Große     *
17*704a815fSMichael Große     * This sends mails to all subscribers that have a subscription for namespaces above
18*704a815fSMichael Große     * the given page if the needed $conf['subscribe_time'] has passed already.
19*704a815fSMichael Große     *
20*704a815fSMichael Große     * This function is called form lib/exe/indexer.php
21*704a815fSMichael Große     *
22*704a815fSMichael Große     * @param string $page
23*704a815fSMichael Große     *
24*704a815fSMichael Große     * @return int number of sent mails
25*704a815fSMichael Große     */
26*704a815fSMichael Große    public function sendBulk($page)
27*704a815fSMichael Große    {
28*704a815fSMichael Große        $subscriberManager = new SubscriberManager();
29*704a815fSMichael Große        if (!$subscriberManager->isenabled()) {
30*704a815fSMichael Große            return 0;
31*704a815fSMichael Große        }
32*704a815fSMichael Große
33*704a815fSMichael Große        /** @var DokuWiki_Auth_Plugin $auth */
34*704a815fSMichael Große        global $auth;
35*704a815fSMichael Große        global $conf;
36*704a815fSMichael Große        global $USERINFO;
37*704a815fSMichael Große        /** @var Input $INPUT */
38*704a815fSMichael Große        global $INPUT;
39*704a815fSMichael Große        $count = 0;
40*704a815fSMichael Große
41*704a815fSMichael Große        $subscriptions = $subscriberManager->subscribers($page, null, ['digest', 'list']);
42*704a815fSMichael Große
43*704a815fSMichael Große        // remember current user info
44*704a815fSMichael Große        $olduinfo = $USERINFO;
45*704a815fSMichael Große        $olduser = $INPUT->server->str('REMOTE_USER');
46*704a815fSMichael Große
47*704a815fSMichael Große        foreach ($subscriptions as $target => $users) {
48*704a815fSMichael Große            if (!$this->lock($target)) {
49*704a815fSMichael Große                continue;
50*704a815fSMichael Große            }
51*704a815fSMichael Große
52*704a815fSMichael Große            foreach ($users as $user => $info) {
53*704a815fSMichael Große                list($style, $lastupdate) = $info;
54*704a815fSMichael Große
55*704a815fSMichael Große                $lastupdate = (int)$lastupdate;
56*704a815fSMichael Große                if ($lastupdate + $conf['subscribe_time'] > time()) {
57*704a815fSMichael Große                    // Less than the configured time period passed since last
58*704a815fSMichael Große                    // update.
59*704a815fSMichael Große                    continue;
60*704a815fSMichael Große                }
61*704a815fSMichael Große
62*704a815fSMichael Große                // Work as the user to make sure ACLs apply correctly
63*704a815fSMichael Große                $USERINFO = $auth->getUserData($user);
64*704a815fSMichael Große                $INPUT->server->set('REMOTE_USER', $user);
65*704a815fSMichael Große                if ($USERINFO === false) {
66*704a815fSMichael Große                    continue;
67*704a815fSMichael Große                }
68*704a815fSMichael Große                if (!$USERINFO['mail']) {
69*704a815fSMichael Große                    continue;
70*704a815fSMichael Große                }
71*704a815fSMichael Große
72*704a815fSMichael Große                if (substr($target, -1, 1) === ':') {
73*704a815fSMichael Große                    // subscription target is a namespace, get all changes within
74*704a815fSMichael Große                    $changes = getRecentsSince($lastupdate, null, getNS($target));
75*704a815fSMichael Große                } else {
76*704a815fSMichael Große                    // single page subscription, check ACL ourselves
77*704a815fSMichael Große                    if (auth_quickaclcheck($target) < AUTH_READ) {
78*704a815fSMichael Große                        continue;
79*704a815fSMichael Große                    }
80*704a815fSMichael Große                    $meta = p_get_metadata($target);
81*704a815fSMichael Große                    $changes = [$meta['last_change']];
82*704a815fSMichael Große                }
83*704a815fSMichael Große
84*704a815fSMichael Große                // Filter out pages only changed in small and own edits
85*704a815fSMichael Große                $change_ids = [];
86*704a815fSMichael Große                foreach ($changes as $rev) {
87*704a815fSMichael Große                    $n = 0;
88*704a815fSMichael Große                    while (!is_null($rev) && $rev['date'] >= $lastupdate &&
89*704a815fSMichael Große                        ($INPUT->server->str('REMOTE_USER') === $rev['user'] ||
90*704a815fSMichael Große                            $rev['type'] === DOKU_CHANGE_TYPE_MINOR_EDIT)) {
91*704a815fSMichael Große                        $pagelog = new PageChangeLog($rev['id']);
92*704a815fSMichael Große                        $rev = $pagelog->getRevisions($n++, 1);
93*704a815fSMichael Große                        $rev = (count($rev) > 0) ? $rev[0] : null;
94*704a815fSMichael Große                    }
95*704a815fSMichael Große
96*704a815fSMichael Große                    if (!is_null($rev) && $rev['date'] >= $lastupdate) {
97*704a815fSMichael Große                        // Some change was not a minor one and not by myself
98*704a815fSMichael Große                        $change_ids[] = $rev['id'];
99*704a815fSMichael Große                    }
100*704a815fSMichael Große                }
101*704a815fSMichael Große
102*704a815fSMichael Große                // send it
103*704a815fSMichael Große                if ($style === 'digest') {
104*704a815fSMichael Große                    foreach ($change_ids as $change_id) {
105*704a815fSMichael Große                        $this->sendDigest(
106*704a815fSMichael Große                            $USERINFO['mail'],
107*704a815fSMichael Große                            $change_id,
108*704a815fSMichael Große                            $lastupdate
109*704a815fSMichael Große                        );
110*704a815fSMichael Große                        $count++;
111*704a815fSMichael Große                    }
112*704a815fSMichael Große                } else {
113*704a815fSMichael Große                    if ($style === 'list') {
114*704a815fSMichael Große                        $this->sendList($USERINFO['mail'], $change_ids, $target);
115*704a815fSMichael Große                        $count++;
116*704a815fSMichael Große                    }
117*704a815fSMichael Große                }
118*704a815fSMichael Große                // TODO: Handle duplicate subscriptions.
119*704a815fSMichael Große
120*704a815fSMichael Große                // Update notification time.
121*704a815fSMichael Große                $subscriberManager->add($target, $user, $style, time());
122*704a815fSMichael Große            }
123*704a815fSMichael Große            $this->unlock($target);
124*704a815fSMichael Große        }
125*704a815fSMichael Große
126*704a815fSMichael Große        // restore current user info
127*704a815fSMichael Große        $USERINFO = $olduinfo;
128*704a815fSMichael Große        $INPUT->server->set('REMOTE_USER', $olduser);
129*704a815fSMichael Große        return $count;
130*704a815fSMichael Große    }
131*704a815fSMichael Große
132*704a815fSMichael Große    /**
133*704a815fSMichael Große     * Lock subscription info
134*704a815fSMichael Große     *
135*704a815fSMichael Große     * We don't use io_lock() her because we do not wait for the lock and use a larger stale time
136*704a815fSMichael Große     *
137*704a815fSMichael Große     * @param string $id The target page or namespace, specified by id; Namespaces
138*704a815fSMichael Große     *                   are identified by appending a colon.
139*704a815fSMichael Große     *
140*704a815fSMichael Große     * @return bool true, if you got a succesful lock
141*704a815fSMichael Große     * @author Adrian Lang <lang@cosmocode.de>
142*704a815fSMichael Große     */
143*704a815fSMichael Große    protected function lock($id)
144*704a815fSMichael Große    {
145*704a815fSMichael Große        global $conf;
146*704a815fSMichael Große
147*704a815fSMichael Große        $lock = $conf['lockdir'] . '/_subscr_' . md5($id) . '.lock';
148*704a815fSMichael Große
149*704a815fSMichael Große        if (is_dir($lock) && time() - @filemtime($lock) > 60 * 5) {
150*704a815fSMichael Große            // looks like a stale lock - remove it
151*704a815fSMichael Große            @rmdir($lock);
152*704a815fSMichael Große        }
153*704a815fSMichael Große
154*704a815fSMichael Große        // try creating the lock directory
155*704a815fSMichael Große        if (!@mkdir($lock, $conf['dmode'])) {
156*704a815fSMichael Große            return false;
157*704a815fSMichael Große        }
158*704a815fSMichael Große
159*704a815fSMichael Große        if (!empty($conf['dperm'])) {
160*704a815fSMichael Große            chmod($lock, $conf['dperm']);
161*704a815fSMichael Große        }
162*704a815fSMichael Große        return true;
163*704a815fSMichael Große    }
164*704a815fSMichael Große
165*704a815fSMichael Große    /**
166*704a815fSMichael Große     * Unlock subscription info
167*704a815fSMichael Große     *
168*704a815fSMichael Große     * @param string $id The target page or namespace, specified by id; Namespaces
169*704a815fSMichael Große     *                   are identified by appending a colon.
170*704a815fSMichael Große     *
171*704a815fSMichael Große     * @return bool
172*704a815fSMichael Große     * @author Adrian Lang <lang@cosmocode.de>
173*704a815fSMichael Große     */
174*704a815fSMichael Große    protected function unlock($id)
175*704a815fSMichael Große    {
176*704a815fSMichael Große        global $conf;
177*704a815fSMichael Große        $lock = $conf['lockdir'] . '/_subscr_' . md5($id) . '.lock';
178*704a815fSMichael Große        return @rmdir($lock);
179*704a815fSMichael Große    }
180*704a815fSMichael Große
181*704a815fSMichael Große    /**
182*704a815fSMichael Große     * Send a digest mail
183*704a815fSMichael Große     *
184*704a815fSMichael Große     * Sends a digest mail showing a bunch of changes of a single page. Basically the same as sendPageDiff()
185*704a815fSMichael Große     * but determines the last known revision first
186*704a815fSMichael Große     *
187*704a815fSMichael Große     * @param string $subscriber_mail The target mail address
188*704a815fSMichael Große     * @param string $id              The ID
189*704a815fSMichael Große     * @param int    $lastupdate      Time of the last notification
190*704a815fSMichael Große     *
191*704a815fSMichael Große     * @return bool
192*704a815fSMichael Große     * @author Adrian Lang <lang@cosmocode.de>
193*704a815fSMichael Große     *
194*704a815fSMichael Große     */
195*704a815fSMichael Große    protected function sendDigest($subscriber_mail, $id, $lastupdate)
196*704a815fSMichael Große    {
197*704a815fSMichael Große        $pagelog = new PageChangeLog($id);
198*704a815fSMichael Große        $n = 0;
199*704a815fSMichael Große        do {
200*704a815fSMichael Große            $rev = $pagelog->getRevisions($n++, 1);
201*704a815fSMichael Große            $rev = (count($rev) > 0) ? $rev[0] : null;
202*704a815fSMichael Große        } while (!is_null($rev) && $rev > $lastupdate);
203*704a815fSMichael Große
204*704a815fSMichael Große        // TODO I'm not happy with the following line and passing $this->mailer around. Not sure how to solve it better
205*704a815fSMichael Große        $pageSubSender = new PageSubscriptionSender($this->mailer);
206*704a815fSMichael Große        return $pageSubSender->sendPageDiff(
207*704a815fSMichael Große            $subscriber_mail,
208*704a815fSMichael Große            'subscr_digest',
209*704a815fSMichael Große            $id,
210*704a815fSMichael Große            $rev
211*704a815fSMichael Große        );
212*704a815fSMichael Große    }
213*704a815fSMichael Große
214*704a815fSMichael Große    /**
215*704a815fSMichael Große     * Send a list mail
216*704a815fSMichael Große     *
217*704a815fSMichael Große     * Sends a list mail showing a list of changed pages.
218*704a815fSMichael Große     *
219*704a815fSMichael Große     * @param string $subscriber_mail The target mail address
220*704a815fSMichael Große     * @param array  $ids             Array of ids
221*704a815fSMichael Große     * @param string $ns_id           The id of the namespace
222*704a815fSMichael Große     *
223*704a815fSMichael Große     * @return bool true if a mail was sent
224*704a815fSMichael Große     * @author Adrian Lang <lang@cosmocode.de>
225*704a815fSMichael Große     *
226*704a815fSMichael Große     */
227*704a815fSMichael Große    protected function sendList($subscriber_mail, $ids, $ns_id)
228*704a815fSMichael Große    {
229*704a815fSMichael Große        if (count($ids) === 0) {
230*704a815fSMichael Große            return false;
231*704a815fSMichael Große        }
232*704a815fSMichael Große
233*704a815fSMichael Große        $tlist = '';
234*704a815fSMichael Große        $hlist = '<ul>';
235*704a815fSMichael Große        foreach ($ids as $id) {
236*704a815fSMichael Große            $link = wl($id, [], true);
237*704a815fSMichael Große            $tlist .= '* ' . $link . NL;
238*704a815fSMichael Große            $hlist .= '<li><a href="' . $link . '">' . hsc($id) . '</a></li>' . NL;
239*704a815fSMichael Große        }
240*704a815fSMichael Große        $hlist .= '</ul>';
241*704a815fSMichael Große
242*704a815fSMichael Große        $id = prettyprint_id($ns_id);
243*704a815fSMichael Große        $trep = [
244*704a815fSMichael Große            'DIFF' => rtrim($tlist),
245*704a815fSMichael Große            'PAGE' => $id,
246*704a815fSMichael Große            'SUBSCRIBE' => wl($id, ['do' => 'subscribe'], true, '&'),
247*704a815fSMichael Große        ];
248*704a815fSMichael Große        $hrep = [
249*704a815fSMichael Große            'DIFF' => $hlist,
250*704a815fSMichael Große        ];
251*704a815fSMichael Große
252*704a815fSMichael Große        return $this->send(
253*704a815fSMichael Große            $subscriber_mail,
254*704a815fSMichael Große            'subscribe_list',
255*704a815fSMichael Große            $ns_id,
256*704a815fSMichael Große            'subscr_list',
257*704a815fSMichael Große            $trep,
258*704a815fSMichael Große            $hrep
259*704a815fSMichael Große        );
260*704a815fSMichael Große    }
261*704a815fSMichael Große}
262