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 (!is_null($rev) && $rev['date'] >= $lastupdate && 91 ($INPUT->server->str('REMOTE_USER') === $rev['user'] || 92 $rev['type'] === DOKU_CHANGE_TYPE_MINOR_EDIT) 93 ) { 94 $revisions = $pagelog->getRevisions($n++, 1); 95 $rev = ($revisions !== []) ? $pagelog->getRevisionInfo($revisions[0]) : null; 96 } 97 98 if (!is_null($rev) && $rev['date'] >= $lastupdate) { 99 // Some change was not a minor one and not by myself 100 $change_ids[] = $rev['id']; 101 } 102 } 103 104 // send it 105 if ($style === 'digest') { 106 foreach ($change_ids as $change_id) { 107 $this->sendDigest( 108 $USERINFO['mail'], 109 $change_id, 110 $lastupdate 111 ); 112 $count++; 113 } 114 } elseif ($style === 'list') { 115 $this->sendList($USERINFO['mail'], $change_ids, $target); 116 $count++; 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)) { 156 return false; 157 } 158 159 if ($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 = ($rev !== []) ? $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 ($ids === []) { 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