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