splitbrain /
dokuwiki
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
| 1 | <?php |
||
| 2 | |||
| 3 | use dokuwiki\ChangeLog\PageChangeLog; |
||
|
0 ignored issues
–
show
|
|||
| 4 | |||
| 5 | /** |
||
| 6 | * Class for handling (email) subscriptions |
||
| 7 | * |
||
| 8 | * @author Adrian Lang <[email protected]> |
||
| 9 | * @author Andreas Gohr <[email protected]> |
||
| 10 | * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) |
||
| 11 | */ |
||
| 12 | class Subscription { |
||
| 13 | |||
| 14 | /** |
||
| 15 | * Check if subscription system is enabled |
||
| 16 | * |
||
| 17 | * @return bool |
||
| 18 | */ |
||
| 19 | public function isenabled() { |
||
| 20 | return actionOK('subscribe'); |
||
| 21 | } |
||
| 22 | |||
| 23 | /** |
||
| 24 | * Return the subscription meta file for the given ID |
||
| 25 | * |
||
| 26 | * @author Adrian Lang <[email protected]> |
||
| 27 | * |
||
| 28 | * @param string $id The target page or namespace, specified by id; Namespaces |
||
| 29 | * are identified by appending a colon. |
||
| 30 | * @return string |
||
| 31 | */ |
||
| 32 | protected function file($id) { |
||
| 33 | $meta_fname = '.mlist'; |
||
| 34 | if((substr($id, -1, 1) === ':')) { |
||
| 35 | $meta_froot = getNS($id); |
||
| 36 | $meta_fname = '/'.$meta_fname; |
||
| 37 | } else { |
||
| 38 | $meta_froot = $id; |
||
| 39 | } |
||
| 40 | return metaFN((string) $meta_froot, $meta_fname); |
||
| 41 | } |
||
| 42 | |||
| 43 | /** |
||
| 44 | * Lock subscription info |
||
| 45 | * |
||
| 46 | * We don't use io_lock() her because we do not wait for the lock and use a larger stale time |
||
| 47 | * |
||
| 48 | * @author Adrian Lang <[email protected]> |
||
| 49 | * @param string $id The target page or namespace, specified by id; Namespaces |
||
| 50 | * are identified by appending a colon. |
||
| 51 | * @return bool true, if you got a succesful lock |
||
| 52 | */ |
||
| 53 | protected function lock($id) { |
||
| 54 | global $conf; |
||
| 55 | |||
| 56 | $lock = $conf['lockdir'].'/_subscr_'.md5($id).'.lock'; |
||
| 57 | |||
| 58 | if(is_dir($lock) && time() - @filemtime($lock) > 60 * 5) { |
||
| 59 | // looks like a stale lock - remove it |
||
| 60 | @rmdir($lock); |
||
| 61 | } |
||
| 62 | |||
| 63 | // try creating the lock directory |
||
| 64 | if(!@mkdir($lock, $conf['dmode'])) { |
||
| 65 | return false; |
||
| 66 | } |
||
| 67 | |||
| 68 | if(!empty($conf['dperm'])) chmod($lock, $conf['dperm']); |
||
| 69 | return true; |
||
| 70 | } |
||
| 71 | |||
| 72 | /** |
||
| 73 | * Unlock subscription info |
||
| 74 | * |
||
| 75 | * @author Adrian Lang <[email protected]> |
||
| 76 | * @param string $id The target page or namespace, specified by id; Namespaces |
||
| 77 | * are identified by appending a colon. |
||
| 78 | * @return bool |
||
| 79 | */ |
||
| 80 | protected function unlock($id) { |
||
| 81 | global $conf; |
||
| 82 | $lock = $conf['lockdir'].'/_subscr_'.md5($id).'.lock'; |
||
| 83 | return @rmdir($lock); |
||
| 84 | } |
||
| 85 | |||
| 86 | /** |
||
| 87 | * Construct a regular expression for parsing a subscription definition line |
||
| 88 | * |
||
| 89 | * @author Andreas Gohr <[email protected]> |
||
| 90 | * |
||
| 91 | * @param string|array $user |
||
| 92 | * @param string|array $style |
||
| 93 | * @param string|array $data |
||
| 94 | * @return string complete regexp including delimiters |
||
| 95 | * @throws Exception when no data is passed |
||
| 96 | */ |
||
| 97 | protected function buildregex($user = null, $style = null, $data = null) { |
||
| 98 | // always work with arrays |
||
| 99 | $user = (array) $user; |
||
| 100 | $style = (array) $style; |
||
| 101 | $data = (array) $data; |
||
| 102 | |||
| 103 | // clean |
||
| 104 | $user = array_filter(array_map('trim', $user)); |
||
| 105 | $style = array_filter(array_map('trim', $style)); |
||
| 106 | $data = array_filter(array_map('trim', $data)); |
||
| 107 | |||
| 108 | // user names are encoded |
||
| 109 | $user = array_map('auth_nameencode', $user); |
||
| 110 | |||
| 111 | // quote |
||
| 112 | $user = array_map('preg_quote_cb', $user); |
||
| 113 | $style = array_map('preg_quote_cb', $style); |
||
| 114 | $data = array_map('preg_quote_cb', $data); |
||
| 115 | |||
| 116 | // join |
||
| 117 | $user = join('|', $user); |
||
| 118 | $style = join('|', $style); |
||
| 119 | $data = join('|', $data); |
||
| 120 | |||
| 121 | // any data at all? |
||
| 122 | if($user.$style.$data === '') throw new Exception('no data passed'); |
||
| 123 | |||
| 124 | // replace empty values, set which ones are optional |
||
| 125 | $sopt = ''; |
||
| 126 | $dopt = ''; |
||
| 127 | if($user === '') { |
||
| 128 | $user = '\S+'; |
||
| 129 | } |
||
| 130 | if($style === '') { |
||
| 131 | $style = '\S+'; |
||
| 132 | $sopt = '?'; |
||
| 133 | } |
||
| 134 | if($data === '') { |
||
| 135 | $data = '\S+'; |
||
| 136 | $dopt = '?'; |
||
| 137 | } |
||
| 138 | |||
| 139 | // assemble |
||
| 140 | return "/^($user)(?:\\s+($style))$sopt(?:\\s+($data))$dopt$/"; |
||
| 141 | } |
||
| 142 | |||
| 143 | /** |
||
| 144 | * Recursively search for matching subscriptions |
||
| 145 | * |
||
| 146 | * This function searches all relevant subscription files for a page or |
||
| 147 | * namespace. |
||
| 148 | * |
||
| 149 | * @author Adrian Lang <[email protected]> |
||
| 150 | * |
||
| 151 | * @param string $page The target object’s (namespace or page) id |
||
| 152 | * @param string|array $user |
||
| 153 | * @param string|array $style |
||
| 154 | * @param string|array $data |
||
| 155 | * @return array |
||
| 156 | */ |
||
| 157 | public function subscribers($page, $user = null, $style = null, $data = null) { |
||
| 158 | if(!$this->isenabled()) return array(); |
||
| 159 | |||
| 160 | // Construct list of files which may contain relevant subscriptions. |
||
| 161 | $files = array(':' => $this->file(':')); |
||
| 162 | do { |
||
| 163 | $files[$page] = $this->file($page); |
||
| 164 | $page = getNS(rtrim($page, ':')).':'; |
||
| 165 | } while($page !== ':'); |
||
| 166 | |||
| 167 | $re = $this->buildregex($user, $style, $data); |
||
| 168 | |||
| 169 | // Handle files. |
||
| 170 | $result = array(); |
||
| 171 | foreach($files as $target => $file) { |
||
| 172 | if(!file_exists($file)) continue; |
||
| 173 | |||
| 174 | $lines = file($file); |
||
| 175 | foreach($lines as $line) { |
||
| 176 | // fix old style subscription files |
||
| 177 | if(strpos($line, ' ') === false) $line = trim($line)." every\n"; |
||
| 178 | |||
| 179 | // check for matching entries |
||
| 180 | if(!preg_match($re, $line, $m)) continue; |
||
| 181 | |||
| 182 | $u = rawurldecode($m[1]); // decode the user name |
||
| 183 | if(!isset($result[$target])) $result[$target] = array(); |
||
| 184 | $result[$target][$u] = array($m[2], $m[3]); // add to result |
||
| 185 | } |
||
| 186 | } |
||
| 187 | return array_reverse($result); |
||
| 188 | } |
||
| 189 | |||
| 190 | /** |
||
| 191 | * Adds a new subscription for the given page or namespace |
||
| 192 | * |
||
| 193 | * This will automatically overwrite any existent subscription for the given user on this |
||
| 194 | * *exact* page or namespace. It will *not* modify any subscription that may exist in higher namespaces. |
||
| 195 | * |
||
| 196 | * @param string $id The target page or namespace, specified by id; Namespaces |
||
| 197 | * are identified by appending a colon. |
||
| 198 | * @param string $user |
||
| 199 | * @param string $style |
||
| 200 | * @param string $data |
||
| 201 | * @throws Exception when user or style is empty |
||
| 202 | * @return bool |
||
| 203 | */ |
||
| 204 | public function add($id, $user, $style, $data = '') { |
||
| 205 | if(!$this->isenabled()) return false; |
||
| 206 | |||
| 207 | // delete any existing subscription |
||
| 208 | $this->remove($id, $user); |
||
| 209 | |||
| 210 | $user = auth_nameencode(trim($user)); |
||
| 211 | $style = trim($style); |
||
| 212 | $data = trim($data); |
||
| 213 | |||
| 214 | if(!$user) throw new Exception('no subscription user given'); |
||
| 215 | if(!$style) throw new Exception('no subscription style given'); |
||
| 216 | if(!$data) $data = time(); //always add current time for new subscriptions |
||
| 217 | |||
| 218 | $line = "$user $style $data\n"; |
||
| 219 | $file = $this->file($id); |
||
| 220 | return io_saveFile($file, $line, true); |
||
| 221 | } |
||
| 222 | |||
| 223 | /** |
||
| 224 | * Removes a subscription for the given page or namespace |
||
| 225 | * |
||
| 226 | * This removes all subscriptions matching the given criteria on the given page or |
||
| 227 | * namespace. It will *not* modify any subscriptions that may exist in higher |
||
| 228 | * namespaces. |
||
| 229 | * |
||
| 230 | * @param string $id The target object’s (namespace or page) id |
||
| 231 | * @param string|array $user |
||
| 232 | * @param string|array $style |
||
| 233 | * @param string|array $data |
||
| 234 | * @return bool |
||
| 235 | */ |
||
| 236 | public function remove($id, $user = null, $style = null, $data = null) { |
||
| 237 | if(!$this->isenabled()) return false; |
||
| 238 | |||
| 239 | $file = $this->file($id); |
||
| 240 | if(!file_exists($file)) return true; |
||
| 241 | |||
| 242 | $re = $this->buildregex($user, $style, $data); |
||
| 243 | return io_deleteFromFile($file, $re, true); |
||
| 244 | } |
||
| 245 | |||
| 246 | /** |
||
| 247 | * Get data for $INFO['subscribed'] |
||
| 248 | * |
||
| 249 | * $INFO['subscribed'] is either false if no subscription for the current page |
||
| 250 | * and user is in effect. Else it contains an array of arrays with the fields |
||
| 251 | * “target”, “style”, and optionally “data”. |
||
| 252 | * |
||
| 253 | * @param string $id Page ID, defaults to global $ID |
||
| 254 | * @param string $user User, defaults to $_SERVER['REMOTE_USER'] |
||
| 255 | * @return array|false |
||
| 256 | * @author Adrian Lang <[email protected]> |
||
| 257 | */ |
||
| 258 | public function user_subscription($id = '', $user = '') { |
||
| 259 | if(!$this->isenabled()) return false; |
||
| 260 | |||
| 261 | global $ID; |
||
| 262 | /** @var Input $INPUT */ |
||
| 263 | global $INPUT; |
||
| 264 | if(!$id) $id = $ID; |
||
| 265 | if(!$user) $user = $INPUT->server->str('REMOTE_USER'); |
||
| 266 | |||
| 267 | $subs = $this->subscribers($id, $user); |
||
| 268 | if(!count($subs)) return false; |
||
| 269 | |||
| 270 | $result = array(); |
||
| 271 | foreach($subs as $target => $info) { |
||
| 272 | $result[] = array( |
||
| 273 | 'target' => $target, |
||
| 274 | 'style' => $info[$user][0], |
||
| 275 | 'data' => $info[$user][1] |
||
| 276 | ); |
||
| 277 | } |
||
| 278 | |||
| 279 | return $result; |
||
| 280 | } |
||
| 281 | |||
| 282 | /** |
||
| 283 | * Send digest and list subscriptions |
||
| 284 | * |
||
| 285 | * This sends mails to all subscribers that have a subscription for namespaces above |
||
| 286 | * the given page if the needed $conf['subscribe_time'] has passed already. |
||
| 287 | * |
||
| 288 | * This function is called form lib/exe/indexer.php |
||
| 289 | * |
||
| 290 | * @param string $page |
||
| 291 | * @return int number of sent mails |
||
| 292 | */ |
||
| 293 | public function send_bulk($page) { |
||
| 294 | if(!$this->isenabled()) return 0; |
||
| 295 | |||
| 296 | /** @var DokuWiki_Auth_Plugin $auth */ |
||
| 297 | global $auth; |
||
| 298 | global $conf; |
||
| 299 | global $USERINFO; |
||
| 300 | /** @var Input $INPUT */ |
||
| 301 | global $INPUT; |
||
| 302 | $count = 0; |
||
| 303 | |||
| 304 | $subscriptions = $this->subscribers($page, null, array('digest', 'list')); |
||
| 305 | |||
| 306 | // remember current user info |
||
| 307 | $olduinfo = $USERINFO; |
||
| 308 | $olduser = $INPUT->server->str('REMOTE_USER'); |
||
| 309 | |||
| 310 | foreach($subscriptions as $target => $users) { |
||
| 311 | if(!$this->lock($target)) continue; |
||
| 312 | |||
| 313 | foreach($users as $user => $info) { |
||
| 314 | list($style, $lastupdate) = $info; |
||
| 315 | |||
| 316 | $lastupdate = (int) $lastupdate; |
||
| 317 | if($lastupdate + $conf['subscribe_time'] > time()) { |
||
| 318 | // Less than the configured time period passed since last |
||
| 319 | // update. |
||
| 320 | continue; |
||
| 321 | } |
||
| 322 | |||
| 323 | // Work as the user to make sure ACLs apply correctly |
||
| 324 | $USERINFO = $auth->getUserData($user); |
||
| 325 | $INPUT->server->set('REMOTE_USER',$user); |
||
| 326 | if($USERINFO === false) continue; |
||
| 327 | if(!$USERINFO['mail']) continue; |
||
| 328 | |||
| 329 | if(substr($target, -1, 1) === ':') { |
||
| 330 | // subscription target is a namespace, get all changes within |
||
| 331 | $changes = getRecentsSince($lastupdate, null, getNS($target)); |
||
| 332 | } else { |
||
| 333 | // single page subscription, check ACL ourselves |
||
| 334 | if(auth_quickaclcheck($target) < AUTH_READ) continue; |
||
| 335 | $meta = p_get_metadata($target); |
||
| 336 | $changes = array($meta['last_change']); |
||
| 337 | } |
||
| 338 | |||
| 339 | // Filter out pages only changed in small and own edits |
||
| 340 | $change_ids = array(); |
||
| 341 | foreach($changes as $rev) { |
||
| 342 | $n = 0; |
||
| 343 | while(!is_null($rev) && $rev['date'] >= $lastupdate && |
||
| 344 | ($INPUT->server->str('REMOTE_USER') === $rev['user'] || |
||
| 345 | $rev['type'] === DOKU_CHANGE_TYPE_MINOR_EDIT)) { |
||
| 346 | $pagelog = new PageChangeLog($rev['id']); |
||
| 347 | $rev = $pagelog->getRevisions($n++, 1); |
||
| 348 | $rev = (count($rev) > 0) ? $rev[0] : null; |
||
| 349 | } |
||
| 350 | |||
| 351 | if(!is_null($rev) && $rev['date'] >= $lastupdate) { |
||
| 352 | // Some change was not a minor one and not by myself |
||
| 353 | $change_ids[] = $rev['id']; |
||
| 354 | } |
||
| 355 | } |
||
| 356 | |||
| 357 | // send it |
||
| 358 | if($style === 'digest') { |
||
| 359 | foreach($change_ids as $change_id) { |
||
| 360 | $this->send_digest( |
||
| 361 | $USERINFO['mail'], $change_id, |
||
| 362 | $lastupdate |
||
| 363 | ); |
||
| 364 | $count++; |
||
| 365 | } |
||
| 366 | } elseif($style === 'list') { |
||
| 367 | $this->send_list($USERINFO['mail'], $change_ids, $target); |
||
| 368 | $count++; |
||
| 369 | } |
||
| 370 | // TODO: Handle duplicate subscriptions. |
||
| 371 | |||
| 372 | // Update notification time. |
||
| 373 | $this->add($target, $user, $style, time()); |
||
| 374 | } |
||
| 375 | $this->unlock($target); |
||
| 376 | } |
||
| 377 | |||
| 378 | // restore current user info |
||
| 379 | $USERINFO = $olduinfo; |
||
| 380 | $INPUT->server->set('REMOTE_USER',$olduser); |
||
| 381 | return $count; |
||
| 382 | } |
||
| 383 | |||
| 384 | /** |
||
| 385 | * Send the diff for some page change |
||
| 386 | * |
||
| 387 | * @param string $subscriber_mail The target mail address |
||
| 388 | * @param string $template Mail template ('subscr_digest', 'subscr_single', 'mailtext', ...) |
||
| 389 | * @param string $id Page for which the notification is |
||
| 390 | * @param int|null $rev Old revision if any |
||
| 391 | * @param string $summary Change summary if any |
||
| 392 | * @return bool true if successfully sent |
||
| 393 | */ |
||
| 394 | public function send_diff($subscriber_mail, $template, $id, $rev = null, $summary = '') { |
||
| 395 | global $DIFF_INLINESTYLES; |
||
| 396 | |||
| 397 | // prepare replacements (keys not set in hrep will be taken from trep) |
||
| 398 | $trep = array( |
||
| 399 | 'PAGE' => $id, |
||
| 400 | 'NEWPAGE' => wl($id, '', true, '&'), |
||
| 401 | 'SUMMARY' => $summary, |
||
| 402 | 'SUBSCRIBE' => wl($id, array('do' => 'subscribe'), true, '&') |
||
| 403 | ); |
||
| 404 | $hrep = array(); |
||
| 405 | |||
| 406 | if($rev) { |
||
| 407 | $subject = 'changed'; |
||
| 408 | $trep['OLDPAGE'] = wl($id, "rev=$rev", true, '&'); |
||
| 409 | |||
| 410 | $old_content = rawWiki($id, $rev); |
||
| 411 | $new_content = rawWiki($id); |
||
| 412 | |||
| 413 | $df = new Diff(explode("\n", $old_content), |
||
| 414 | explode("\n", $new_content)); |
||
| 415 | $dformat = new UnifiedDiffFormatter(); |
||
| 416 | $tdiff = $dformat->format($df); |
||
| 417 | |||
| 418 | $DIFF_INLINESTYLES = true; |
||
| 419 | $df = new Diff(explode("\n", $old_content), |
||
| 420 | explode("\n", $new_content)); |
||
| 421 | $dformat = new InlineDiffFormatter(); |
||
| 422 | $hdiff = $dformat->format($df); |
||
| 423 | $hdiff = '<table>'.$hdiff.'</table>'; |
||
| 424 | $DIFF_INLINESTYLES = false; |
||
| 425 | } else { |
||
| 426 | $subject = 'newpage'; |
||
| 427 | $trep['OLDPAGE'] = '---'; |
||
| 428 | $tdiff = rawWiki($id); |
||
| 429 | $hdiff = nl2br(hsc($tdiff)); |
||
| 430 | } |
||
| 431 | |||
| 432 | $trep['DIFF'] = $tdiff; |
||
| 433 | $hrep['DIFF'] = $hdiff; |
||
| 434 | |||
| 435 | $headers = array('Message-Id' => $this->getMessageID($id)); |
||
| 436 | if ($rev) { |
||
| 437 | $headers['In-Reply-To'] = $this->getMessageID($id, $rev); |
||
| 438 | } |
||
| 439 | |||
| 440 | return $this->send( |
||
| 441 | $subscriber_mail, $subject, $id, |
||
| 442 | $template, $trep, $hrep, $headers |
||
| 443 | ); |
||
| 444 | } |
||
| 445 | |||
| 446 | /** |
||
| 447 | * Send the diff for some media change |
||
| 448 | * |
||
| 449 | * @fixme this should embed thumbnails of images in HTML version |
||
| 450 | * |
||
| 451 | * @param string $subscriber_mail The target mail address |
||
| 452 | * @param string $template Mail template ('uploadmail', ...) |
||
| 453 | * @param string $id Media file for which the notification is |
||
| 454 | * @param int|bool $rev Old revision if any |
||
| 455 | */ |
||
| 456 | public function send_media_diff($subscriber_mail, $template, $id, $rev = false) { |
||
| 457 | global $conf; |
||
| 458 | |||
| 459 | $file = mediaFN($id); |
||
| 460 | list($mime, /* $ext */) = mimetype($id); |
||
| 461 | |||
| 462 | $trep = array( |
||
| 463 | 'MIME' => $mime, |
||
| 464 | 'MEDIA' => ml($id,'',true,'&',true), |
||
| 465 | 'SIZE' => filesize_h(filesize($file)), |
||
| 466 | ); |
||
| 467 | |||
| 468 | if ($rev && $conf['mediarevisions']) { |
||
| 469 | $trep['OLD'] = ml($id, "rev=$rev", true, '&', true); |
||
| 470 | } else { |
||
| 471 | $trep['OLD'] = '---'; |
||
| 472 | } |
||
| 473 | |||
| 474 | $headers = array('Message-Id' => $this->getMessageID($id, @filemtime($file))); |
||
| 475 | if ($rev) { |
||
| 476 | $headers['In-Reply-To'] = $this->getMessageID($id, $rev); |
||
| 477 | } |
||
| 478 | |||
| 479 | $this->send($subscriber_mail, 'upload', $id, $template, $trep, null, $headers); |
||
| 480 | |||
| 481 | } |
||
| 482 | |||
| 483 | /** |
||
| 484 | * Send a notify mail on new registration |
||
| 485 | * |
||
| 486 | * @author Andreas Gohr <[email protected]> |
||
| 487 | * |
||
| 488 | * @param string $login login name of the new user |
||
| 489 | * @param string $fullname full name of the new user |
||
| 490 | * @param string $email email address of the new user |
||
| 491 | * @return bool true if a mail was sent |
||
| 492 | */ |
||
| 493 | public function send_register($login, $fullname, $email) { |
||
| 494 | global $conf; |
||
| 495 | if(empty($conf['registernotify'])) return false; |
||
| 496 | |||
| 497 | $trep = array( |
||
| 498 | 'NEWUSER' => $login, |
||
| 499 | 'NEWNAME' => $fullname, |
||
| 500 | 'NEWEMAIL' => $email, |
||
| 501 | ); |
||
| 502 | |||
| 503 | return $this->send( |
||
| 504 | $conf['registernotify'], |
||
| 505 | 'new_user', |
||
| 506 | $login, |
||
| 507 | 'registermail', |
||
| 508 | $trep |
||
| 509 | ); |
||
| 510 | } |
||
| 511 | |||
| 512 | /** |
||
| 513 | * Send a digest mail |
||
| 514 | * |
||
| 515 | * Sends a digest mail showing a bunch of changes of a single page. Basically the same as send_diff() |
||
| 516 | * but determines the last known revision first |
||
| 517 | * |
||
| 518 | * @author Adrian Lang <[email protected]> |
||
| 519 | * |
||
| 520 | * @param string $subscriber_mail The target mail address |
||
| 521 | * @param string $id The ID |
||
| 522 | * @param int $lastupdate Time of the last notification |
||
| 523 | * @return bool |
||
| 524 | */ |
||
| 525 | protected function send_digest($subscriber_mail, $id, $lastupdate) { |
||
| 526 | $pagelog = new PageChangeLog($id); |
||
| 527 | $n = 0; |
||
| 528 | do { |
||
| 529 | $rev = $pagelog->getRevisions($n++, 1); |
||
| 530 | $rev = (count($rev) > 0) ? $rev[0] : null; |
||
| 531 | } while(!is_null($rev) && $rev > $lastupdate); |
||
| 532 | |||
| 533 | return $this->send_diff( |
||
| 534 | $subscriber_mail, |
||
| 535 | 'subscr_digest', |
||
| 536 | $id, $rev |
||
| 537 | ); |
||
| 538 | } |
||
| 539 | |||
| 540 | /** |
||
| 541 | * Send a list mail |
||
| 542 | * |
||
| 543 | * Sends a list mail showing a list of changed pages. |
||
| 544 | * |
||
| 545 | * @author Adrian Lang <[email protected]> |
||
| 546 | * |
||
| 547 | * @param string $subscriber_mail The target mail address |
||
| 548 | * @param array $ids Array of ids |
||
| 549 | * @param string $ns_id The id of the namespace |
||
| 550 | * @return bool true if a mail was sent |
||
| 551 | */ |
||
| 552 | protected function send_list($subscriber_mail, $ids, $ns_id) { |
||
| 553 | if(count($ids) === 0) return false; |
||
| 554 | |||
| 555 | $tlist = ''; |
||
| 556 | $hlist = '<ul>'; |
||
| 557 | foreach($ids as $id) { |
||
| 558 | $link = wl($id, array(), true); |
||
| 559 | $tlist .= '* '.$link.NL; |
||
| 560 | $hlist .= '<li><a href="'.$link.'">'.hsc($id).'</a></li>'.NL; |
||
| 561 | } |
||
| 562 | $hlist .= '</ul>'; |
||
| 563 | |||
| 564 | $id = prettyprint_id($ns_id); |
||
| 565 | $trep = array( |
||
| 566 | 'DIFF' => rtrim($tlist), |
||
| 567 | 'PAGE' => $id, |
||
| 568 | 'SUBSCRIBE' => wl($id, array('do' => 'subscribe'), true, '&') |
||
| 569 | ); |
||
| 570 | $hrep = array( |
||
| 571 | 'DIFF' => $hlist |
||
| 572 | ); |
||
| 573 | |||
| 574 | return $this->send( |
||
| 575 | $subscriber_mail, |
||
| 576 | 'subscribe_list', |
||
| 577 | $ns_id, |
||
| 578 | 'subscr_list', $trep, $hrep |
||
| 579 | ); |
||
| 580 | } |
||
| 581 | |||
| 582 | /** |
||
| 583 | * Helper function for sending a mail |
||
| 584 | * |
||
| 585 | * @author Adrian Lang <[email protected]> |
||
| 586 | * |
||
| 587 | * @param string $subscriber_mail The target mail address |
||
| 588 | * @param string $subject The lang id of the mail subject (without the |
||
| 589 | * prefix “mail_”) |
||
| 590 | * @param string $context The context of this mail, eg. page or namespace id |
||
| 591 | * @param string $template The name of the mail template |
||
| 592 | * @param array $trep Predefined parameters used to parse the |
||
| 593 | * template (in text format) |
||
| 594 | * @param array $hrep Predefined parameters used to parse the |
||
| 595 | * template (in HTML format), null to default to $trep |
||
| 596 | * @param array $headers Additional mail headers in the form 'name' => 'value' |
||
| 597 | * @return bool |
||
| 598 | */ |
||
| 599 | protected function send($subscriber_mail, $subject, $context, $template, $trep, $hrep = null, $headers = array()) { |
||
| 600 | global $lang; |
||
| 601 | global $conf; |
||
| 602 | |||
| 603 | $text = rawLocale($template); |
||
| 604 | $subject = $lang['mail_'.$subject].' '.$context; |
||
| 605 | $mail = new Mailer(); |
||
| 606 | $mail->bcc($subscriber_mail); |
||
| 607 | $mail->subject($subject); |
||
| 608 | $mail->setBody($text, $trep, $hrep); |
||
| 609 | if(in_array($template, array('subscr_list', 'subscr_digest'))){ |
||
| 610 | $mail->from($conf['mailfromnobody']); |
||
| 611 | } |
||
| 612 | if(isset($trep['SUBSCRIBE'])) { |
||
| 613 | $mail->setHeader('List-Unsubscribe', '<'.$trep['SUBSCRIBE'].'>', false); |
||
| 614 | } |
||
| 615 | |||
| 616 | foreach ($headers as $header => $value) { |
||
| 617 | $mail->setHeader($header, $value); |
||
| 618 | } |
||
| 619 | |||
| 620 | return $mail->send(); |
||
| 621 | } |
||
| 622 | |||
| 623 | /** |
||
| 624 | * Get a valid message id for a certain $id and revision (or the current revision) |
||
| 625 | * |
||
| 626 | * @param string $id The id of the page (or media file) the message id should be for |
||
| 627 | * @param string $rev The revision of the page, set to the current revision of the page $id if not set |
||
| 628 | * @return string |
||
| 629 | */ |
||
| 630 | protected function getMessageID($id, $rev = null) { |
||
| 631 | static $listid = null; |
||
| 632 | if (is_null($listid)) { |
||
| 633 | $server = parse_url(DOKU_URL, PHP_URL_HOST); |
||
| 634 | $listid = join('.', array_reverse(explode('/', DOKU_BASE))).$server; |
||
| 635 | $listid = urlencode($listid); |
||
| 636 | $listid = strtolower(trim($listid, '.')); |
||
| 637 | } |
||
| 638 | |||
| 639 | if (is_null($rev)) { |
||
| 640 | $rev = @filemtime(wikiFN($id)); |
||
| 641 | } |
||
| 642 | |||
| 643 | return "<$id?rev=$rev@$listid>"; |
||
| 644 | } |
||
| 645 | |||
| 646 | /** |
||
| 647 | * Default callback for COMMON_NOTIFY_ADDRESSLIST |
||
| 648 | * |
||
| 649 | * Aggregates all email addresses of user who have subscribed the given page with 'every' style |
||
| 650 | * |
||
| 651 | * @author Steven Danz <[email protected]> |
||
| 652 | * @author Adrian Lang <[email protected]> |
||
| 653 | * |
||
| 654 | * @todo move the whole functionality into this class, trigger SUBSCRIPTION_NOTIFY_ADDRESSLIST instead, |
||
| 655 | * use an array for the addresses within it |
||
| 656 | * |
||
| 657 | * @param array &$data Containing the entries: |
||
| 658 | * - $id (the page id), |
||
| 659 | * - $self (whether the author should be notified, |
||
| 660 | * - $addresslist (current email address list) |
||
| 661 | * - $replacements (array of additional string substitutions, @KEY@ to be replaced by value) |
||
| 662 | */ |
||
| 663 | public function notifyaddresses(&$data) { |
||
| 664 | if(!$this->isenabled()) return; |
||
| 665 | |||
| 666 | /** @var DokuWiki_Auth_Plugin $auth */ |
||
| 667 | global $auth; |
||
| 668 | global $conf; |
||
| 669 | /** @var Input $INPUT */ |
||
| 670 | global $INPUT; |
||
| 671 | |||
| 672 | $id = $data['id']; |
||
| 673 | $self = $data['self']; |
||
| 674 | $addresslist = $data['addresslist']; |
||
| 675 | |||
| 676 | $subscriptions = $this->subscribers($id, null, 'every'); |
||
| 677 | |||
| 678 | $result = array(); |
||
| 679 | foreach($subscriptions as $target => $users) { |
||
| 680 | foreach($users as $user => $info) { |
||
| 681 | $userinfo = $auth->getUserData($user); |
||
| 682 | if($userinfo === false) continue; |
||
| 683 | if(!$userinfo['mail']) continue; |
||
| 684 | if(!$self && $user == $INPUT->server->str('REMOTE_USER')) continue; //skip our own changes |
||
| 685 | |||
| 686 | $level = auth_aclcheck($id, $user, $userinfo['grps']); |
||
| 687 | if($level >= AUTH_READ) { |
||
| 688 | if(strcasecmp($userinfo['mail'], $conf['notify']) != 0) { //skip user who get notified elsewhere |
||
| 689 | $result[$user] = $userinfo['mail']; |
||
| 690 | } |
||
| 691 | } |
||
| 692 | } |
||
| 693 | } |
||
| 694 | $data['addresslist'] = trim($addresslist.','.implode(',', $result), ','); |
||
| 695 | } |
||
| 696 | } |
||
| 697 |
Let’s assume that you have a directory layout like this:
. |-- OtherDir | |-- Bar.php | `-- Foo.php `-- SomeDir `-- Foo.phpand let’s assume the following content of
Bar.php:If both files
OtherDir/Foo.phpandSomeDir/Foo.phpare loaded in the same runtime, you will see a PHP error such as the following:PHP Fatal error: Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.phpHowever, as
OtherDir/Foo.phpdoes not necessarily have to be loaded and the error is only triggered if it is loaded beforeOtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias: