1<?php 2 3 4namespace dokuwiki\Subscriptions; 5 6 7use dokuwiki\ChangeLog\PageChangeLog; 8use dokuwiki\Input\Input; 9use DokuWiki_Auth_Plugin; 10 11class BulkSubscriptionSender extends SubscriptionSender 12{ 13 14 /** 15 * Send digest and list subscriptions 16 * 17 * This sends mails to all subscribers that have a subscription for namespaces above 18 * the given page if the needed $conf['subscribe_time'] has passed already. 19 * 20 * This function is called form lib/exe/indexer.php 21 * 22 * @param string $page 23 * 24 * @return int number of sent mails 25 */ 26 public function sendBulk($page) 27 { 28 $subscriberManager = new SubscriberManager(); 29 if (!$subscriberManager->isenabled()) { 30 return 0; 31 } 32 33 /** @var DokuWiki_Auth_Plugin $auth */ 34 global $auth; 35 global $conf; 36 global $USERINFO; 37 /** @var Input $INPUT */ 38 global $INPUT; 39 $count = 0; 40 41 $subscriptions = $subscriberManager->subscribers($page, null, ['digest', 'list']); 42 43 // remember current user info 44 $olduinfo = $USERINFO; 45 $olduser = $INPUT->server->str('REMOTE_USER'); 46 47 foreach ($subscriptions as $target => $users) { 48 if (!$this->lock($target)) { 49 continue; 50 } 51 52 foreach ($users as $user => $info) { 53 list($style, $lastupdate) = $info; 54 55 $lastupdate = (int)$lastupdate; 56 if ($lastupdate + $conf['subscribe_time'] > time()) { 57 // Less than the configured time period passed since last 58 // update. 59 continue; 60 } 61 62 // Work as the user to make sure ACLs apply correctly 63 $USERINFO = $auth->getUserData($user); 64 $INPUT->server->set('REMOTE_USER', $user); 65 if ($USERINFO === false) { 66 continue; 67 } 68 if (!$USERINFO['mail']) { 69 continue; 70 } 71 72 if (substr($target, -1, 1) === ':') { 73 // subscription target is a namespace, get all changes within 74 $changes = getRecentsSince($lastupdate, null, getNS($target)); 75 } else { 76 // single page subscription, check ACL ourselves 77 if (auth_quickaclcheck($target) < AUTH_READ) { 78 continue; 79 } 80 $meta = p_get_metadata($target); 81 $changes = [$meta['last_change']]; 82 } 83 84 // Filter out pages only changed in small and own edits 85 $change_ids = []; 86 foreach ($changes as $rev) { 87 $n = 0; 88 while (!is_null($rev) && $rev['date'] >= $lastupdate && 89 ($INPUT->server->str('REMOTE_USER') === $rev['user'] || 90 $rev['type'] === DOKU_CHANGE_TYPE_MINOR_EDIT)) { 91 $pagelog = new PageChangeLog($rev['id']); 92 $rev = $pagelog->getRevisions($n++, 1); 93 $rev = (count($rev) > 0) ? $rev[0] : null; 94 } 95 96 if (!is_null($rev) && $rev['date'] >= $lastupdate) { 97 // Some change was not a minor one and not by myself 98 $change_ids[] = $rev['id']; 99 } 100 } 101 102 // send it 103 if ($style === 'digest') { 104 foreach ($change_ids as $change_id) { 105 $this->sendDigest( 106 $USERINFO['mail'], 107 $change_id, 108 $lastupdate 109 ); 110 $count++; 111 } 112 } else { 113 if ($style === 'list') { 114 $this->sendList($USERINFO['mail'], $change_ids, $target); 115 $count++; 116 } 117 } 118 // TODO: Handle duplicate subscriptions. 119 120 // Update notification time. 121 $subscriberManager->add($target, $user, $style, time()); 122 } 123 $this->unlock($target); 124 } 125 126 // restore current user info 127 $USERINFO = $olduinfo; 128 $INPUT->server->set('REMOTE_USER', $olduser); 129 return $count; 130 } 131 132 /** 133 * Lock subscription info 134 * 135 * We don't use io_lock() her because we do not wait for the lock and use a larger stale time 136 * 137 * @param string $id The target page or namespace, specified by id; Namespaces 138 * are identified by appending a colon. 139 * 140 * @return bool true, if you got a succesful lock 141 * @author Adrian Lang <lang@cosmocode.de> 142 */ 143 protected function lock($id) 144 { 145 global $conf; 146 147 $lock = $conf['lockdir'] . '/_subscr_' . md5($id) . '.lock'; 148 149 if (is_dir($lock) && time() - @filemtime($lock) > 60 * 5) { 150 // looks like a stale lock - remove it 151 @rmdir($lock); 152 } 153 154 // try creating the lock directory 155 if (!@mkdir($lock, $conf['dmode'])) { 156 return false; 157 } 158 159 if (!empty($conf['dperm'])) { 160 chmod($lock, $conf['dperm']); 161 } 162 return true; 163 } 164 165 /** 166 * Unlock subscription info 167 * 168 * @param string $id The target page or namespace, specified by id; Namespaces 169 * are identified by appending a colon. 170 * 171 * @return bool 172 * @author Adrian Lang <lang@cosmocode.de> 173 */ 174 protected function unlock($id) 175 { 176 global $conf; 177 $lock = $conf['lockdir'] . '/_subscr_' . md5($id) . '.lock'; 178 return @rmdir($lock); 179 } 180 181 /** 182 * Send a digest mail 183 * 184 * Sends a digest mail showing a bunch of changes of a single page. Basically the same as sendPageDiff() 185 * but determines the last known revision first 186 * 187 * @param string $subscriber_mail The target mail address 188 * @param string $id The ID 189 * @param int $lastupdate Time of the last notification 190 * 191 * @return bool 192 * @author Adrian Lang <lang@cosmocode.de> 193 * 194 */ 195 protected function sendDigest($subscriber_mail, $id, $lastupdate) 196 { 197 $pagelog = new PageChangeLog($id); 198 $n = 0; 199 do { 200 $rev = $pagelog->getRevisions($n++, 1); 201 $rev = (count($rev) > 0) ? $rev[0] : null; 202 } while (!is_null($rev) && $rev > $lastupdate); 203 204 // TODO I'm not happy with the following line and passing $this->mailer around. Not sure how to solve it better 205 $pageSubSender = new PageSubscriptionSender($this->mailer); 206 return $pageSubSender->sendPageDiff( 207 $subscriber_mail, 208 'subscr_digest', 209 $id, 210 $rev 211 ); 212 } 213 214 /** 215 * Send a list mail 216 * 217 * Sends a list mail showing a list of changed pages. 218 * 219 * @param string $subscriber_mail The target mail address 220 * @param array $ids Array of ids 221 * @param string $ns_id The id of the namespace 222 * 223 * @return bool true if a mail was sent 224 * @author Adrian Lang <lang@cosmocode.de> 225 * 226 */ 227 protected function sendList($subscriber_mail, $ids, $ns_id) 228 { 229 if (count($ids) === 0) { 230 return false; 231 } 232 233 $tlist = ''; 234 $hlist = '<ul>'; 235 foreach ($ids as $id) { 236 $link = wl($id, [], true); 237 $tlist .= '* ' . $link . NL; 238 $hlist .= '<li><a href="' . $link . '">' . hsc($id) . '</a></li>' . NL; 239 } 240 $hlist .= '</ul>'; 241 242 $id = prettyprint_id($ns_id); 243 $trep = [ 244 'DIFF' => rtrim($tlist), 245 'PAGE' => $id, 246 'SUBSCRIBE' => wl($id, ['do' => 'subscribe'], true, '&'), 247 ]; 248 $hrep = [ 249 'DIFF' => $hlist, 250 ]; 251 252 return $this->send( 253 $subscriber_mail, 254 'subscribe_list', 255 $ns_id, 256 'subscr_list', 257 $trep, 258 $hrep 259 ); 260 } 261} 262