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