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 70*6c16a3a9Sfiwswe 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') { 113704a815fSMichael Große $this->sendList($USERINFO['mail'], $change_ids, $target); 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 /** 180704a815fSMichael Große * Send a digest mail 181704a815fSMichael Große * 182704a815fSMichael Große * Sends a digest mail showing a bunch of changes of a single page. Basically the same as sendPageDiff() 183704a815fSMichael Große * but determines the last known revision first 184704a815fSMichael Große * 185704a815fSMichael Große * @param string $subscriber_mail The target mail address 186704a815fSMichael Große * @param string $id The ID 187704a815fSMichael Große * @param int $lastupdate Time of the last notification 188704a815fSMichael Große * 189704a815fSMichael Große * @return bool 190704a815fSMichael Große * @author Adrian Lang <lang@cosmocode.de> 191704a815fSMichael Große * 192704a815fSMichael Große */ 193704a815fSMichael Große protected function sendDigest($subscriber_mail, $id, $lastupdate) 194704a815fSMichael Große { 195704a815fSMichael Große $pagelog = new PageChangeLog($id); 196704a815fSMichael Große $n = 0; 197704a815fSMichael Große do { 198704a815fSMichael Große $rev = $pagelog->getRevisions($n++, 1); 1995983e241SAndreas Gohr $rev = ($rev !== []) ? $rev[0] : null; 200704a815fSMichael Große } while (!is_null($rev) && $rev > $lastupdate); 201704a815fSMichael Große 202704a815fSMichael Große // TODO I'm not happy with the following line and passing $this->mailer around. Not sure how to solve it better 203704a815fSMichael Große $pageSubSender = new PageSubscriptionSender($this->mailer); 204704a815fSMichael Große return $pageSubSender->sendPageDiff( 205704a815fSMichael Große $subscriber_mail, 206704a815fSMichael Große 'subscr_digest', 207704a815fSMichael Große $id, 208704a815fSMichael Große $rev 209704a815fSMichael Große ); 210704a815fSMichael Große } 211704a815fSMichael Große 212704a815fSMichael Große /** 213704a815fSMichael Große * Send a list mail 214704a815fSMichael Große * 215704a815fSMichael Große * Sends a list mail showing a list of changed pages. 216704a815fSMichael Große * 217704a815fSMichael Große * @param string $subscriber_mail The target mail address 218704a815fSMichael Große * @param array $ids Array of ids 219704a815fSMichael Große * @param string $ns_id The id of the namespace 220704a815fSMichael Große * 221704a815fSMichael Große * @return bool true if a mail was sent 222704a815fSMichael Große * @author Adrian Lang <lang@cosmocode.de> 223704a815fSMichael Große * 224704a815fSMichael Große */ 225704a815fSMichael Große protected function sendList($subscriber_mail, $ids, $ns_id) 226704a815fSMichael Große { 2275983e241SAndreas Gohr if ($ids === []) { 228704a815fSMichael Große return false; 229704a815fSMichael Große } 230704a815fSMichael Große 231704a815fSMichael Große $tlist = ''; 232704a815fSMichael Große $hlist = '<ul>'; 233704a815fSMichael Große foreach ($ids as $id) { 234704a815fSMichael Große $link = wl($id, [], true); 235704a815fSMichael Große $tlist .= '* ' . $link . NL; 236704a815fSMichael Große $hlist .= '<li><a href="' . $link . '">' . hsc($id) . '</a></li>' . NL; 237704a815fSMichael Große } 238704a815fSMichael Große $hlist .= '</ul>'; 239704a815fSMichael Große 240704a815fSMichael Große $id = prettyprint_id($ns_id); 241704a815fSMichael Große $trep = [ 242704a815fSMichael Große 'DIFF' => rtrim($tlist), 243704a815fSMichael Große 'PAGE' => $id, 244704a815fSMichael Große 'SUBSCRIBE' => wl($id, ['do' => 'subscribe'], true, '&'), 245704a815fSMichael Große ]; 246704a815fSMichael Große $hrep = [ 247704a815fSMichael Große 'DIFF' => $hlist, 248704a815fSMichael Große ]; 249704a815fSMichael Große 250704a815fSMichael Große return $this->send( 251704a815fSMichael Große $subscriber_mail, 252704a815fSMichael Große 'subscribe_list', 253704a815fSMichael Große $ns_id, 254704a815fSMichael Große 'subscr_list', 255704a815fSMichael Große $trep, 256704a815fSMichael Große $hrep 257704a815fSMichael Große ); 258704a815fSMichael Große } 259704a815fSMichael Große} 260