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; 8*51ee2399SGerrit Uitslaguse dokuwiki\Extension\AuthPlugin; 9704a815fSMichael Großeuse dokuwiki\Input\Input; 10*51ee2399SGerrit 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 25*51ee2399SGerrit 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 34*51ee2399SGerrit 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']); 90704a815fSMichael Große while (!is_null($rev) && $rev['date'] >= $lastupdate && 91704a815fSMichael Große ($INPUT->server->str('REMOTE_USER') === $rev['user'] || 92252acce3SSatoshi Sahara $rev['type'] === DOKU_CHANGE_TYPE_MINOR_EDIT) 93252acce3SSatoshi Sahara ) { 94e2905cd4SAndreas Gohr $revisions = $pagelog->getRevisions($n++, 1); 955983e241SAndreas Gohr $rev = ($revisions !== []) ? $pagelog->getRevisionInfo($revisions[0]) : null; 96704a815fSMichael Große } 97704a815fSMichael Große 98704a815fSMichael Große if (!is_null($rev) && $rev['date'] >= $lastupdate) { 99704a815fSMichael Große // Some change was not a minor one and not by myself 100704a815fSMichael Große $change_ids[] = $rev['id']; 101704a815fSMichael Große } 102704a815fSMichael Große } 103704a815fSMichael Große 104704a815fSMichael Große // send it 105704a815fSMichael Große if ($style === 'digest') { 106704a815fSMichael Große foreach ($change_ids as $change_id) { 107704a815fSMichael Große $this->sendDigest( 108704a815fSMichael Große $USERINFO['mail'], 109704a815fSMichael Große $change_id, 110704a815fSMichael Große $lastupdate 111704a815fSMichael Große ); 112704a815fSMichael Große $count++; 113704a815fSMichael Große } 1145983e241SAndreas Gohr } elseif ($style === 'list') { 115704a815fSMichael Große $this->sendList($USERINFO['mail'], $change_ids, $target); 116704a815fSMichael Große $count++; 117704a815fSMichael Große } 118704a815fSMichael Große // TODO: Handle duplicate subscriptions. 119704a815fSMichael Große 120704a815fSMichael Große // Update notification time. 121704a815fSMichael Große $subscriberManager->add($target, $user, $style, time()); 122704a815fSMichael Große } 123704a815fSMichael Große $this->unlock($target); 124704a815fSMichael Große } 125704a815fSMichael Große 126704a815fSMichael Große // restore current user info 127704a815fSMichael Große $USERINFO = $olduinfo; 128704a815fSMichael Große $INPUT->server->set('REMOTE_USER', $olduser); 129704a815fSMichael Große return $count; 130704a815fSMichael Große } 131704a815fSMichael Große 132704a815fSMichael Große /** 133704a815fSMichael Große * Lock subscription info 134704a815fSMichael Große * 135704a815fSMichael Große * We don't use io_lock() her because we do not wait for the lock and use a larger stale time 136704a815fSMichael Große * 137704a815fSMichael Große * @param string $id The target page or namespace, specified by id; Namespaces 138704a815fSMichael Große * are identified by appending a colon. 139704a815fSMichael Große * 140704a815fSMichael Große * @return bool true, if you got a succesful lock 141704a815fSMichael Große * @author Adrian Lang <lang@cosmocode.de> 142704a815fSMichael Große */ 143704a815fSMichael Große protected function lock($id) 144704a815fSMichael Große { 145704a815fSMichael Große global $conf; 146704a815fSMichael Große 147704a815fSMichael Große $lock = $conf['lockdir'] . '/_subscr_' . md5($id) . '.lock'; 148704a815fSMichael Große 149704a815fSMichael Große if (is_dir($lock) && time() - @filemtime($lock) > 60 * 5) { 150704a815fSMichael Große // looks like a stale lock - remove it 151704a815fSMichael Große @rmdir($lock); 152704a815fSMichael Große } 153704a815fSMichael Große 154704a815fSMichael Große // try creating the lock directory 155bd539124SAndreas Gohr if (!@mkdir($lock)) { 156704a815fSMichael Große return false; 157704a815fSMichael Große } 158704a815fSMichael Große 15923420346SDamien Regad if ($conf['dperm']) { 160704a815fSMichael Große chmod($lock, $conf['dperm']); 161704a815fSMichael Große } 162704a815fSMichael Große return true; 163704a815fSMichael Große } 164704a815fSMichael Große 165704a815fSMichael Große /** 166704a815fSMichael Große * Unlock subscription info 167704a815fSMichael Große * 168704a815fSMichael Große * @param string $id The target page or namespace, specified by id; Namespaces 169704a815fSMichael Große * are identified by appending a colon. 170704a815fSMichael Große * 171704a815fSMichael Große * @return bool 172704a815fSMichael Große * @author Adrian Lang <lang@cosmocode.de> 173704a815fSMichael Große */ 174704a815fSMichael Große protected function unlock($id) 175704a815fSMichael Große { 176704a815fSMichael Große global $conf; 177704a815fSMichael Große $lock = $conf['lockdir'] . '/_subscr_' . md5($id) . '.lock'; 178704a815fSMichael Große return @rmdir($lock); 179704a815fSMichael Große } 180704a815fSMichael Große 181704a815fSMichael Große /** 182704a815fSMichael Große * Send a digest mail 183704a815fSMichael Große * 184704a815fSMichael Große * Sends a digest mail showing a bunch of changes of a single page. Basically the same as sendPageDiff() 185704a815fSMichael Große * but determines the last known revision first 186704a815fSMichael Große * 187704a815fSMichael Große * @param string $subscriber_mail The target mail address 188704a815fSMichael Große * @param string $id The ID 189704a815fSMichael Große * @param int $lastupdate Time of the last notification 190704a815fSMichael Große * 191704a815fSMichael Große * @return bool 192704a815fSMichael Große * @author Adrian Lang <lang@cosmocode.de> 193704a815fSMichael Große * 194704a815fSMichael Große */ 195704a815fSMichael Große protected function sendDigest($subscriber_mail, $id, $lastupdate) 196704a815fSMichael Große { 197704a815fSMichael Große $pagelog = new PageChangeLog($id); 198704a815fSMichael Große $n = 0; 199704a815fSMichael Große do { 200704a815fSMichael Große $rev = $pagelog->getRevisions($n++, 1); 2015983e241SAndreas Gohr $rev = ($rev !== []) ? $rev[0] : null; 202704a815fSMichael Große } while (!is_null($rev) && $rev > $lastupdate); 203704a815fSMichael Große 204704a815fSMichael Große // TODO I'm not happy with the following line and passing $this->mailer around. Not sure how to solve it better 205704a815fSMichael Große $pageSubSender = new PageSubscriptionSender($this->mailer); 206704a815fSMichael Große return $pageSubSender->sendPageDiff( 207704a815fSMichael Große $subscriber_mail, 208704a815fSMichael Große 'subscr_digest', 209704a815fSMichael Große $id, 210704a815fSMichael Große $rev 211704a815fSMichael Große ); 212704a815fSMichael Große } 213704a815fSMichael Große 214704a815fSMichael Große /** 215704a815fSMichael Große * Send a list mail 216704a815fSMichael Große * 217704a815fSMichael Große * Sends a list mail showing a list of changed pages. 218704a815fSMichael Große * 219704a815fSMichael Große * @param string $subscriber_mail The target mail address 220704a815fSMichael Große * @param array $ids Array of ids 221704a815fSMichael Große * @param string $ns_id The id of the namespace 222704a815fSMichael Große * 223704a815fSMichael Große * @return bool true if a mail was sent 224704a815fSMichael Große * @author Adrian Lang <lang@cosmocode.de> 225704a815fSMichael Große * 226704a815fSMichael Große */ 227704a815fSMichael Große protected function sendList($subscriber_mail, $ids, $ns_id) 228704a815fSMichael Große { 2295983e241SAndreas Gohr if ($ids === []) { 230704a815fSMichael Große return false; 231704a815fSMichael Große } 232704a815fSMichael Große 233704a815fSMichael Große $tlist = ''; 234704a815fSMichael Große $hlist = '<ul>'; 235704a815fSMichael Große foreach ($ids as $id) { 236704a815fSMichael Große $link = wl($id, [], true); 237704a815fSMichael Große $tlist .= '* ' . $link . NL; 238704a815fSMichael Große $hlist .= '<li><a href="' . $link . '">' . hsc($id) . '</a></li>' . NL; 239704a815fSMichael Große } 240704a815fSMichael Große $hlist .= '</ul>'; 241704a815fSMichael Große 242704a815fSMichael Große $id = prettyprint_id($ns_id); 243704a815fSMichael Große $trep = [ 244704a815fSMichael Große 'DIFF' => rtrim($tlist), 245704a815fSMichael Große 'PAGE' => $id, 246704a815fSMichael Große 'SUBSCRIBE' => wl($id, ['do' => 'subscribe'], true, '&'), 247704a815fSMichael Große ]; 248704a815fSMichael Große $hrep = [ 249704a815fSMichael Große 'DIFF' => $hlist, 250704a815fSMichael Große ]; 251704a815fSMichael Große 252704a815fSMichael Große return $this->send( 253704a815fSMichael Große $subscriber_mail, 254704a815fSMichael Große 'subscribe_list', 255704a815fSMichael Große $ns_id, 256704a815fSMichael Große 'subscr_list', 257704a815fSMichael Große $trep, 258704a815fSMichael Große $hrep 259704a815fSMichael Große ); 260704a815fSMichael Große } 261704a815fSMichael Große} 262