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 [$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 $pagelog = new PageChangeLog($rev['id']); 89 while (!is_null($rev) && $rev['date'] >= $lastupdate && 90 ($INPUT->server->str('REMOTE_USER') === $rev['user'] || 91 $rev['type'] === DOKU_CHANGE_TYPE_MINOR_EDIT) 92 ) { 93 $revisions = $pagelog->getRevisions($n++, 1); 94 $rev = ($revisions !== []) ? $pagelog->getRevisionInfo($revisions[0]) : null; 95 } 96 97 if (!is_null($rev) && $rev['date'] >= $lastupdate) { 98 // Some change was not a minor one and not by myself 99 $change_ids[] = $rev['id']; 100 } 101 } 102 103 // send it 104 if ($style === 'digest') { 105 foreach ($change_ids as $change_id) { 106 $this->sendDigest( 107 $USERINFO['mail'], 108 $change_id, 109 $lastupdate 110 ); 111 $count++; 112 } 113 } elseif ($style === 'list') { 114 $this->sendList($USERINFO['mail'], $change_ids, $target); 115 $count++; 116 } 117 // TODO: Handle duplicate subscriptions. 118 119 // Update notification time. 120 $subscriberManager->add($target, $user, $style, time()); 121 } 122 $this->unlock($target); 123 } 124 125 // restore current user info 126 $USERINFO = $olduinfo; 127 $INPUT->server->set('REMOTE_USER', $olduser); 128 return $count; 129 } 130 131 /** 132 * Lock subscription info 133 * 134 * We don't use io_lock() her because we do not wait for the lock and use a larger stale time 135 * 136 * @param string $id The target page or namespace, specified by id; Namespaces 137 * are identified by appending a colon. 138 * 139 * @return bool true, if you got a succesful lock 140 * @author Adrian Lang <lang@cosmocode.de> 141 */ 142 protected function lock($id) 143 { 144 global $conf; 145 146 $lock = $conf['lockdir'] . '/_subscr_' . md5($id) . '.lock'; 147 148 if (is_dir($lock) && time() - @filemtime($lock) > 60 * 5) { 149 // looks like a stale lock - remove it 150 @rmdir($lock); 151 } 152 153 // try creating the lock directory 154 if (!@mkdir($lock)) { 155 return false; 156 } 157 158 if ($conf['dperm']) { 159 chmod($lock, $conf['dperm']); 160 } 161 return true; 162 } 163 164 /** 165 * Unlock subscription info 166 * 167 * @param string $id The target page or namespace, specified by id; Namespaces 168 * are identified by appending a colon. 169 * 170 * @return bool 171 * @author Adrian Lang <lang@cosmocode.de> 172 */ 173 protected function unlock($id) 174 { 175 global $conf; 176 $lock = $conf['lockdir'] . '/_subscr_' . md5($id) . '.lock'; 177 return @rmdir($lock); 178 } 179 180 /** 181 * Send a digest mail 182 * 183 * Sends a digest mail showing a bunch of changes of a single page. Basically the same as sendPageDiff() 184 * but determines the last known revision first 185 * 186 * @param string $subscriber_mail The target mail address 187 * @param string $id The ID 188 * @param int $lastupdate Time of the last notification 189 * 190 * @return bool 191 * @author Adrian Lang <lang@cosmocode.de> 192 * 193 */ 194 protected function sendDigest($subscriber_mail, $id, $lastupdate) 195 { 196 $pagelog = new PageChangeLog($id); 197 $n = 0; 198 do { 199 $rev = $pagelog->getRevisions($n++, 1); 200 $rev = ($rev !== []) ? $rev[0] : null; 201 } while (!is_null($rev) && $rev > $lastupdate); 202 203 // TODO I'm not happy with the following line and passing $this->mailer around. Not sure how to solve it better 204 $pageSubSender = new PageSubscriptionSender($this->mailer); 205 return $pageSubSender->sendPageDiff( 206 $subscriber_mail, 207 'subscr_digest', 208 $id, 209 $rev 210 ); 211 } 212 213 /** 214 * Send a list mail 215 * 216 * Sends a list mail showing a list of changed pages. 217 * 218 * @param string $subscriber_mail The target mail address 219 * @param array $ids Array of ids 220 * @param string $ns_id The id of the namespace 221 * 222 * @return bool true if a mail was sent 223 * @author Adrian Lang <lang@cosmocode.de> 224 * 225 */ 226 protected function sendList($subscriber_mail, $ids, $ns_id) 227 { 228 if ($ids === []) { 229 return false; 230 } 231 232 $tlist = ''; 233 $hlist = '<ul>'; 234 foreach ($ids as $id) { 235 $link = wl($id, [], true); 236 $tlist .= '* ' . $link . NL; 237 $hlist .= '<li><a href="' . $link . '">' . hsc($id) . '</a></li>' . NL; 238 } 239 $hlist .= '</ul>'; 240 241 $id = prettyprint_id($ns_id); 242 $trep = [ 243 'DIFF' => rtrim($tlist), 244 'PAGE' => $id, 245 'SUBSCRIBE' => wl($id, ['do' => 'subscribe'], true, '&'), 246 ]; 247 $hrep = [ 248 'DIFF' => $hlist, 249 ]; 250 251 return $this->send( 252 $subscriber_mail, 253 'subscribe_list', 254 $ns_id, 255 'subscr_list', 256 $trep, 257 $hrep 258 ); 259 } 260} 261