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