1704a815fSMichael Große<?php 2704a815fSMichael Große 3704a815fSMichael Großenamespace dokuwiki\Subscriptions; 4704a815fSMichael Große 5704a815fSMichael Großeuse dokuwiki\ChangeLog\PageChangeLog; 651ee2399SGerrit Uitslaguse dokuwiki\Extension\AuthPlugin; 7704a815fSMichael Großeuse dokuwiki\Input\Input; 851ee2399SGerrit Uitslaguse Exception; 9704a815fSMichael Große 10704a815fSMichael Großeclass BulkSubscriptionSender extends SubscriptionSender 11704a815fSMichael Große{ 12704a815fSMichael Große /** 13704a815fSMichael Große * Send digest and list subscriptions 14704a815fSMichael Große * 15704a815fSMichael Große * This sends mails to all subscribers that have a subscription for namespaces above 16704a815fSMichael Große * the given page if the needed $conf['subscribe_time'] has passed already. 17704a815fSMichael Große * 18704a815fSMichael Große * This function is called form lib/exe/indexer.php 19704a815fSMichael Große * 20704a815fSMichael Große * @param string $page 21704a815fSMichael Große * @return int number of sent mails 2251ee2399SGerrit Uitslag * @throws Exception 23704a815fSMichael Große */ 24704a815fSMichael Große public function sendBulk($page) 25704a815fSMichael Große { 26704a815fSMichael Große $subscriberManager = new SubscriberManager(); 27704a815fSMichael Große if (!$subscriberManager->isenabled()) { 28704a815fSMichael Große return 0; 29704a815fSMichael Große } 30704a815fSMichael Große 3151ee2399SGerrit Uitslag /** @var AuthPlugin $auth */ 32704a815fSMichael Große global $auth; 33704a815fSMichael Große global $conf; 34704a815fSMichael Große global $USERINFO; 35704a815fSMichael Große /** @var Input $INPUT */ 36704a815fSMichael Große global $INPUT; 37704a815fSMichael Große $count = 0; 38704a815fSMichael Große 39704a815fSMichael Große $subscriptions = $subscriberManager->subscribers($page, null, ['digest', 'list']); 40704a815fSMichael Große 41704a815fSMichael Große // remember current user info 42704a815fSMichael Große $olduinfo = $USERINFO; 43704a815fSMichael Große $olduser = $INPUT->server->str('REMOTE_USER'); 44704a815fSMichael Große 45704a815fSMichael Große foreach ($subscriptions as $target => $users) { 46704a815fSMichael Große if (!$this->lock($target)) { 47704a815fSMichael Große continue; 48704a815fSMichael Große } 49704a815fSMichael Große 50704a815fSMichael Große foreach ($users as $user => $info) { 515983e241SAndreas Gohr [$style, $lastupdate] = $info; 52704a815fSMichael Große 53704a815fSMichael Große $lastupdate = (int)$lastupdate; 54704a815fSMichael Große if ($lastupdate + $conf['subscribe_time'] > time()) { 55704a815fSMichael Große // Less than the configured time period passed since last 56704a815fSMichael Große // update. 57704a815fSMichael Große continue; 58704a815fSMichael Große } 59704a815fSMichael Große 60704a815fSMichael Große // Work as the user to make sure ACLs apply correctly 61704a815fSMichael Große $USERINFO = $auth->getUserData($user); 62704a815fSMichael Große $INPUT->server->set('REMOTE_USER', $user); 63704a815fSMichael Große if ($USERINFO === false) { 64704a815fSMichael Große continue; 65704a815fSMichael Große } 66704a815fSMichael Große if (!$USERINFO['mail']) { 67704a815fSMichael Große continue; 68704a815fSMichael Große } 69704a815fSMichael Große 706c16a3a9Sfiwswe if (str_ends_with($target, ':')) { 71704a815fSMichael Große // subscription target is a namespace, get all changes within 72704a815fSMichael Große $changes = getRecentsSince($lastupdate, null, getNS($target)); 73704a815fSMichael Große } else { 74704a815fSMichael Große // single page subscription, check ACL ourselves 75704a815fSMichael Große if (auth_quickaclcheck($target) < AUTH_READ) { 76704a815fSMichael Große continue; 77704a815fSMichael Große } 78704a815fSMichael Große $meta = p_get_metadata($target); 79704a815fSMichael Große $changes = [$meta['last_change']]; 80704a815fSMichael Große } 81704a815fSMichael Große 82704a815fSMichael Große // Filter out pages only changed in small and own edits 83704a815fSMichael Große $change_ids = []; 84704a815fSMichael Große foreach ($changes as $rev) { 85704a815fSMichael Große $n = 0; 86e2905cd4SAndreas Gohr $pagelog = new PageChangeLog($rev['id']); 877d34963bSAndreas Gohr while ( 887d34963bSAndreas Gohr !is_null($rev) && $rev['date'] >= $lastupdate && 89704a815fSMichael Große ($INPUT->server->str('REMOTE_USER') === $rev['user'] || 90252acce3SSatoshi Sahara $rev['type'] === DOKU_CHANGE_TYPE_MINOR_EDIT) 91252acce3SSatoshi Sahara ) { 92e2905cd4SAndreas Gohr $revisions = $pagelog->getRevisions($n++, 1); 935983e241SAndreas Gohr $rev = ($revisions !== []) ? $pagelog->getRevisionInfo($revisions[0]) : null; 94704a815fSMichael Große } 95704a815fSMichael Große 96704a815fSMichael Große if (!is_null($rev) && $rev['date'] >= $lastupdate) { 97704a815fSMichael Große // Some change was not a minor one and not by myself 98704a815fSMichael Große $change_ids[] = $rev['id']; 99704a815fSMichael Große } 100704a815fSMichael Große } 101704a815fSMichael Große 102704a815fSMichael Große // send it 103704a815fSMichael Große if ($style === 'digest') { 104704a815fSMichael Große foreach ($change_ids as $change_id) { 105704a815fSMichael Große $this->sendDigest( 106704a815fSMichael Große $USERINFO['mail'], 107704a815fSMichael Große $change_id, 108704a815fSMichael Große $lastupdate 109704a815fSMichael Große ); 110704a815fSMichael Große $count++; 111704a815fSMichael Große } 1125983e241SAndreas Gohr } elseif ($style === 'list') { 113*fcc2e27aSMichael Stapelberg $this->sendList($USERINFO['mail'], $change_ids, $target, $lastupdate); 114704a815fSMichael Große $count++; 115704a815fSMichael Große } 116704a815fSMichael Große // TODO: Handle duplicate subscriptions. 117704a815fSMichael Große 118704a815fSMichael Große // Update notification time. 119704a815fSMichael Große $subscriberManager->add($target, $user, $style, time()); 120704a815fSMichael Große } 121704a815fSMichael Große $this->unlock($target); 122704a815fSMichael Große } 123704a815fSMichael Große 124704a815fSMichael Große // restore current user info 125704a815fSMichael Große $USERINFO = $olduinfo; 126704a815fSMichael Große $INPUT->server->set('REMOTE_USER', $olduser); 127704a815fSMichael Große return $count; 128704a815fSMichael Große } 129704a815fSMichael Große 130704a815fSMichael Große /** 131704a815fSMichael Große * Lock subscription info 132704a815fSMichael Große * 133704a815fSMichael Große * We don't use io_lock() her because we do not wait for the lock and use a larger stale time 134704a815fSMichael Große * 135704a815fSMichael Große * @param string $id The target page or namespace, specified by id; Namespaces 136704a815fSMichael Große * are identified by appending a colon. 137704a815fSMichael Große * 138704a815fSMichael Große * @return bool true, if you got a succesful lock 139704a815fSMichael Große * @author Adrian Lang <lang@cosmocode.de> 140704a815fSMichael Große */ 141704a815fSMichael Große protected function lock($id) 142704a815fSMichael Große { 143704a815fSMichael Große global $conf; 144704a815fSMichael Große 145704a815fSMichael Große $lock = $conf['lockdir'] . '/_subscr_' . md5($id) . '.lock'; 146704a815fSMichael Große 147704a815fSMichael Große if (is_dir($lock) && time() - @filemtime($lock) > 60 * 5) { 148704a815fSMichael Große // looks like a stale lock - remove it 149704a815fSMichael Große @rmdir($lock); 150704a815fSMichael Große } 151704a815fSMichael Große 152704a815fSMichael Große // try creating the lock directory 153bd539124SAndreas Gohr if (!@mkdir($lock)) { 154704a815fSMichael Große return false; 155704a815fSMichael Große } 156704a815fSMichael Große 15723420346SDamien Regad if ($conf['dperm']) { 158704a815fSMichael Große chmod($lock, $conf['dperm']); 159704a815fSMichael Große } 160704a815fSMichael Große return true; 161704a815fSMichael Große } 162704a815fSMichael Große 163704a815fSMichael Große /** 164704a815fSMichael Große * Unlock subscription info 165704a815fSMichael Große * 166704a815fSMichael Große * @param string $id The target page or namespace, specified by id; Namespaces 167704a815fSMichael Große * are identified by appending a colon. 168704a815fSMichael Große * 169704a815fSMichael Große * @return bool 170704a815fSMichael Große * @author Adrian Lang <lang@cosmocode.de> 171704a815fSMichael Große */ 172704a815fSMichael Große protected function unlock($id) 173704a815fSMichael Große { 174704a815fSMichael Große global $conf; 175704a815fSMichael Große $lock = $conf['lockdir'] . '/_subscr_' . md5($id) . '.lock'; 176704a815fSMichael Große return @rmdir($lock); 177704a815fSMichael Große } 178704a815fSMichael Große 179704a815fSMichael Große /** 180*fcc2e27aSMichael Stapelberg * Return the last revision before lastupdate. 181*fcc2e27aSMichael Stapelberg * 182*fcc2e27aSMichael Stapelberg * @param string $id The target page or namespace, specified by id; Namespaces 183*fcc2e27aSMichael Stapelberg * are identified by appending a colon. 184*fcc2e27aSMichael Stapelberg * 185*fcc2e27aSMichael Stapelberg * @return int 186*fcc2e27aSMichael Stapelberg * @author Michael Stapelberg <stapelberg+dokuwiki@google.com> 187*fcc2e27aSMichael Stapelberg */ 188*fcc2e27aSMichael Stapelberg protected function lastRevBefore($id, $lastupdate) 189*fcc2e27aSMichael Stapelberg { 190*fcc2e27aSMichael Stapelberg $pagelog = new PageChangeLog($id); 191*fcc2e27aSMichael Stapelberg $n = 0; 192*fcc2e27aSMichael Stapelberg do { 193*fcc2e27aSMichael Stapelberg $rev = $pagelog->getRevisions($n++, 1); 194*fcc2e27aSMichael Stapelberg $rev = ($rev !== []) ? $rev[0] : null; 195*fcc2e27aSMichael Stapelberg } while (!is_null($rev) && $rev > $lastupdate); 196*fcc2e27aSMichael Stapelberg return $rev; 197*fcc2e27aSMichael Stapelberg } 198*fcc2e27aSMichael Stapelberg 199*fcc2e27aSMichael Stapelberg /** 200704a815fSMichael Große * Send a digest mail 201704a815fSMichael Große * 202704a815fSMichael Große * Sends a digest mail showing a bunch of changes of a single page. Basically the same as sendPageDiff() 203704a815fSMichael Große * but determines the last known revision first 204704a815fSMichael Große * 205704a815fSMichael Große * @param string $subscriber_mail The target mail address 206704a815fSMichael Große * @param string $id The ID 207704a815fSMichael Große * @param int $lastupdate Time of the last notification 208704a815fSMichael Große * 209704a815fSMichael Große * @return bool 210704a815fSMichael Große * @author Adrian Lang <lang@cosmocode.de> 211704a815fSMichael Große * 212704a815fSMichael Große */ 213704a815fSMichael Große protected function sendDigest($subscriber_mail, $id, $lastupdate) 214704a815fSMichael Große { 215*fcc2e27aSMichael Stapelberg $rev = $this->lastRevBefore($id, $lastupdate); 216704a815fSMichael Große 217704a815fSMichael Große // TODO I'm not happy with the following line and passing $this->mailer around. Not sure how to solve it better 218704a815fSMichael Große $pageSubSender = new PageSubscriptionSender($this->mailer); 219704a815fSMichael Große return $pageSubSender->sendPageDiff( 220704a815fSMichael Große $subscriber_mail, 221704a815fSMichael Große 'subscr_digest', 222704a815fSMichael Große $id, 223704a815fSMichael Große $rev 224704a815fSMichael Große ); 225704a815fSMichael Große } 226704a815fSMichael Große 227704a815fSMichael Große /** 228704a815fSMichael Große * Send a list mail 229704a815fSMichael Große * 230704a815fSMichael Große * Sends a list mail showing a list of changed pages. 231704a815fSMichael Große * 232704a815fSMichael Große * @param string $subscriber_mail The target mail address 233704a815fSMichael Große * @param array $ids Array of ids 234704a815fSMichael Große * @param string $ns_id The id of the namespace 235*fcc2e27aSMichael Stapelberg * @param int $lastupdate Time of the last notification 236704a815fSMichael Große * 237704a815fSMichael Große * @return bool true if a mail was sent 238704a815fSMichael Große * @author Adrian Lang <lang@cosmocode.de> 239704a815fSMichael Große * 240704a815fSMichael Große */ 241*fcc2e27aSMichael Stapelberg protected function sendList($subscriber_mail, $ids, $ns_id, $lastupdate) 242704a815fSMichael Große { 243*fcc2e27aSMichael Stapelberg global $lang; 244*fcc2e27aSMichael Stapelberg 2455983e241SAndreas Gohr if ($ids === []) { 246704a815fSMichael Große return false; 247704a815fSMichael Große } 248704a815fSMichael Große 249704a815fSMichael Große $tlist = ''; 250704a815fSMichael Große $hlist = '<ul>'; 251704a815fSMichael Große foreach ($ids as $id) { 252*fcc2e27aSMichael Stapelberg $last = $this->lastRevBefore($id, $lastupdate); 253704a815fSMichael Große $link = wl($id, [], true); 254704a815fSMichael Große $tlist .= '* ' . $link . NL; 255*fcc2e27aSMichael Stapelberg $hlist .= '<li>'; 256*fcc2e27aSMichael Stapelberg $hlist .= '<a href="' . $link . '">' . hsc($id) . '</a>'; 257*fcc2e27aSMichael Stapelberg if (!is_null($last)) { 258*fcc2e27aSMichael Stapelberg $difflink = wl($id, ['do' => 'diff', 'rev' => $last], true); 259*fcc2e27aSMichael Stapelberg $hlist .= ' (<a href="' . $difflink . '">' . $lang['diff'] . '</a>)'; 260*fcc2e27aSMichael Stapelberg } 261*fcc2e27aSMichael Stapelberg $hlist .= '</li>' . NL; 262704a815fSMichael Große } 263704a815fSMichael Große $hlist .= '</ul>'; 264704a815fSMichael Große 265704a815fSMichael Große $id = prettyprint_id($ns_id); 266704a815fSMichael Große $trep = [ 267704a815fSMichael Große 'DIFF' => rtrim($tlist), 268704a815fSMichael Große 'PAGE' => $id, 269704a815fSMichael Große 'SUBSCRIBE' => wl($id, ['do' => 'subscribe'], true, '&'), 270704a815fSMichael Große ]; 271704a815fSMichael Große $hrep = [ 272704a815fSMichael Große 'DIFF' => $hlist, 273704a815fSMichael Große ]; 274704a815fSMichael Große 275704a815fSMichael Große return $this->send( 276704a815fSMichael Große $subscriber_mail, 277704a815fSMichael Große 'subscribe_list', 278704a815fSMichael Große $ns_id, 279704a815fSMichael Große 'subscr_list', 280704a815fSMichael Große $trep, 281704a815fSMichael Große $hrep 282704a815fSMichael Große ); 283704a815fSMichael Große } 284704a815fSMichael Große} 285