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