3 /* Copyright (C) 2006-2007,2009 Reini Urban
4 * Copyright (C) 2009 Marc-Etienne Vargenau, Alcatel-Lucent
6 * This file is part of PhpWiki.
8 * PhpWiki is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * PhpWiki is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License along
19 * with PhpWiki; if not, write to the Free Software Foundation, Inc.,
20 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24 * Handle the pagelist pref[notifyPages] logic for users
25 * and notify => hash ( page => (userid => userhash) ) for pages.
26 * Generate notification emails.
28 * We add WikiDB handlers and register ourself there:
29 * onChangePage, onDeletePage, onRenamePage
30 * Administrative actions:
31 * [Watch] WatchPage - add a page, or delete watch handlers into the users
32 * pref[notifyPages] slot.
33 * My WatchList - view or edit list/regex of pref[notifyPages].
34 * EMailConfirm methods: send and verify
39 * ? handle emailed confirmation links (EmailSignup, ModeratedPage)
45 if (!defined("MAILER_LOG")) {
47 define("MAILER_LOG", 'c:/wikimail.log');
49 define("MAILER_LOG", '/var/log/wikimail.log');
56 function MailNotify($pagename)
58 $this->pagename = $pagename; /* which page */
59 $this->emails = array(); /* to which addresses */
60 $this->userids = array(); /* corresponding array of displayed names,
61 don't display the email addresses */
62 /* From: from whom the mail appears to be */
63 $this->from = $this->fromId();
70 return $request->_user->getId();
72 return $request->_user->getId() . '@' . $request->get('REMOTE_HOST');
79 return $this->userEmail($request->_user->getId(), false);
82 function userEmail($userid, $doverify = true)
86 // Disable verification of emails for corporate env.
91 $u = $request->getUser();
92 if ($u->UserName() == $userid) { // lucky: current user
93 $prefs = $u->getPreferences();
94 $email = $prefs->get('email');
95 // do a dynamic emailVerified check update
96 if ($doverify and !$request->_prefs->get('emailVerified')) {
99 } else { // not current user
100 if (ENABLE_USER_NEW) {
101 $u = WikiUser($userid);
102 $u->getPreferences();
103 $prefs = &$u->_prefs;
105 $u = new WikiUser($request, $userid);
106 $prefs = $u->getPreferences();
108 $email = $prefs->get('email');
109 if ($doverify and !$prefs->get('emailVerified')) {
117 * getPageChangeEmails($notify)
118 * @param $notify: hash ( page => (userid => userhash) )
120 * unique array of ($emails, $userids)
122 function getPageChangeEmails($notify)
127 foreach ($notify as $page => $users) {
128 if (glob_match($page, $this->pagename)) {
129 $curuser = $request->getUser();
130 $curusername = $curuser->UserName();
131 $curuserprefs = $curuser->getPreferences();
132 $curuserprefsemail = $curuserprefs->get('email');
133 $ownModifications = $curuserprefs->get('ownModifications');
134 $majorModificationsOnly = $curuserprefs->get('majorModificationsOnly');
136 foreach ($users as $userid => $user) {
138 $usermail = $user['email'];
140 if (($usermail == $curuserprefsemail)
141 and ($ownModifications)
143 // It's my own modification
144 // and I do not want to receive it
148 if ($majorModificationsOnly) {
149 $backend = &$request->_dbi->_backend;
150 $version = $backend->get_latest_version($this->pagename);
151 $versiondata = $backend->get_versiondata($this->pagename, $version, true);
152 if ($versiondata['is_minor_edit']) {
153 // It's a minor modification
154 // and I do not want to receive it
159 if (!$user) { // handle the case for ModeratePage:
160 // no prefs, just userid's.
161 $emails[] = $this->userEmail($userid, false);
162 $userids[] = $userid;
164 if (!empty($user['verified']) and !empty($user['email'])) {
165 $emails[] = $user['email'];
166 $userids[] = $userid;
167 } elseif (!empty($user['email'])) {
168 // do a dynamic emailVerified check update
169 $email = $this->userEmail($userid, true);
171 $notify[$page][$userid]['verified'] = 1;
172 $request->_dbi->set('notify', $notify);
174 $userids[] = $userid;
177 // ignore verification
180 if (!in_array($user['email'], $emails))
181 $emails[] = $user['email'];
188 $this->emails = array_unique($emails);
189 $this->userids = array_unique($userids);
190 return array($this->emails, $this->userids);
193 function sendMail($subject,
198 // Add WIKI_NAME to Subject
199 $subject = "[" . WIKI_NAME . "] " . $subject;
200 // Encode $subject if needed
201 $encoded_subject = $this->subject_encode($subject);
202 $emails = $this->emails;
203 // Do not send if modification is from FusionForge admin
204 if (FUSIONFORGE and ($this->fromId() == ADMIN_USER)) {
208 $notice = _("PageChange Notification of %s");
210 $from = $this->fromEmail();
211 $headers = "From: $from\r\n" .
212 "Bcc: " . join(',', $emails) . "\r\n" .
213 "MIME-Version: 1.0\r\n" .
214 "Content-Type: text/plain; charset=" . CHARSET . "; format=flowed\r\n" .
215 "Content-Transfer-Encoding: 8bit";
217 $ok = mail(($to = array_shift($emails)),
219 $subject . "\n" . $content,
222 if (MAILER_LOG and is_writable(MAILER_LOG)) {
223 global $ErrorManager;
225 $f = fopen(MAILER_LOG, "a");
226 fwrite($f, "\n\nX-MailSentOK: " . $ok ? 'OK' : 'FAILED');
228 if (!$ok && isset($ErrorManager->_postponed_errors[count($ErrorManager->_postponed_errors) - 1])) {
229 // get last error message
230 $last_err = $ErrorManager->_postponed_errors[count($ErrorManager->_postponed_errors) - 1];
231 fwrite($f, "\nX-MailFailure: " .
232 "errno: " . $last_err->errno . ", " .
233 "errstr: " . $last_err->errstr . ", " .
234 "errfile: " . $last_err->errfile . ", " .
235 "errline: " . $last_err->errline);
237 fwrite($f, "\nDate: " . CTime());
238 fwrite($f, "\nSubject: $encoded_subject");
239 fwrite($f, "\nFrom: $from");
240 fwrite($f, "\nTo: $to");
241 fwrite($f, "\nBcc: " . join(',', $emails));
242 fwrite($f, "\n\n" . $content);
247 trigger_error(sprintf($notice, $this->pagename)
249 . sprintf(_("sent to %s"), join(',', $this->userids)),
253 trigger_error(sprintf($notice, $this->pagename)
255 . sprintf(_("Error: Couldn't send %s to %s"),
256 $subject . "\n" . $content, join(',', $this->userids)),
263 * Send udiff for a changed page to multiple users.
264 * See rename and remove methods also
266 function sendPageChangeNotification(&$wikitext, $version, &$meta)
271 if (isset($request->_deferredPageChangeNotification)) {
272 // collapse multiple changes (loaddir) into one email
273 $request->_deferredPageChangeNotification[] =
274 array($this->pagename, $this->emails, $this->userids);
277 $backend = &$request->_dbi->_backend;
278 $previous = $backend->get_previous_version($this->pagename, $version);
279 if (!isset($meta['mtime'])) {
280 $meta['mtime'] = time();
283 // Page existed, and was modified
284 $subject = _("Page change") . ' ' . ($this->pagename);
285 $difflink = WikiURL($this->pagename, array('action' => 'diff'), true);
286 $cache = &$request->_dbi->_cache;
287 $this_content = explode("\n", $wikitext);
288 $prevdata = $cache->get_versiondata($this->pagename, $previous, true);
289 if (empty($prevdata['%content'])) {
290 $prevdata = $backend->get_versiondata($this->pagename, $previous, true);
292 $other_content = explode("\n", $prevdata['%content']);
294 include_once 'lib/difflib.php';
295 $diff2 = new Diff($other_content, $this_content);
296 //$context_lines = max(4, count($other_content) + 1,
297 // count($this_content) + 1);
298 $fmt = new UnifiedDiffFormatter( /*$context_lines*/);
299 $content = $this->pagename . " " . $previous . " " .
300 Iso8601DateTime($prevdata['mtime']) . "\n";
301 $content .= $this->pagename . " " . $version . " " .
302 Iso8601DateTime($meta['mtime']) . "\n";
303 $content .= $fmt->format($diff2);
304 $editedby = sprintf(_("Edited by: %s"), $this->fromId());
306 // Page did not exist, and was created
307 $subject = _("Page creation") . ' ' . ($this->pagename);
308 $difflink = WikiURL($this->pagename, array(), true);
309 $content = $this->pagename . " " . $version . " " .
310 Iso8601DateTime($meta['mtime']) . "\n";
311 $content .= _("New page");
313 $content .= $wikitext;
314 $editedby = sprintf(_("Created by: %s"), $this->fromId());
316 $summary = sprintf(_("Summary: %s"), $meta['summary']);
317 $this->sendMail($subject,
318 $editedby . "\n" . $summary . "\n" . $difflink . "\n\n" . $content);
322 * Support mass rename / remove (TBD)
324 function sendPageRenameNotification($to, &$meta)
326 $pagename = $this->pagename;
327 $editedby = sprintf(_("Renamed by: %s"), $this->fromId());
328 $subject = sprintf(_("Page rename %s to %s"), $pagename, $to);
329 $link = WikiURL($to, true);
330 $this->sendMail($subject,
331 $editedby . "\n" . $link . "\n\n" . "Renamed $pagename to $to");
337 function onChangePage(&$wikidb, &$wikitext, $version, &$meta)
339 if (!isa($GLOBALS['request'], 'MockRequest')) {
340 $notify = $wikidb->get('notify');
341 /* Generate notification emails? */
342 if (!empty($notify) and is_array($notify)) {
343 if (empty($this->pagename))
344 $this->pagename = $meta['pagename'];
345 // TODO: Should be used for ModeratePage and RSS2 Cloud xml-rpc also.
346 $this->getPageChangeEmails($notify);
347 if (!empty($this->emails)) {
348 $this->sendPageChangeNotification($wikitext, $version, $meta);
354 function onDeletePage(&$wikidb, $pagename)
357 /* Generate notification emails? */
358 if (!$wikidb->isWikiPage($pagename) and !isa($GLOBALS['request'], 'MockRequest')) {
359 $notify = $wikidb->get('notify');
360 if (!empty($notify) and is_array($notify)) {
361 //TODO: deferr it (quite a massive load if you remove some pages).
362 $this->getPageChangeEmails($notify);
363 if (!empty($this->emails)) {
364 $subject = sprintf(_("User %s removed page %s"), $this->fromId(), $pagename);
365 $result = $this->sendMail($subject, $subject . "\n\n");
372 function onRenamePage(&$wikidb, $oldpage, $new_pagename)
374 if (!isa($GLOBALS['request'], 'MockRequest')) {
375 $notify = $wikidb->get('notify');
376 if (!empty($notify) and is_array($notify)) {
377 $this->getPageChangeEmails($notify);
378 if (!empty($this->emails)) {
379 $newpage = $wikidb->getPage($new_pagename);
380 $current = $newpage->getCurrentRevision();
381 $meta = $current->_data;
382 $this->pagename = $oldpage;
383 $this->sendPageRenameNotification($new_pagename, $meta);
390 * Send mail to user and store the cookie in the db
391 * wikiurl?action=ConfirmEmail&id=bla
393 function sendEmailConfirmation($email, $userid)
396 $id = rand_ascii_readable(16);
397 $wikidb = $request->getDbh();
398 $data = $wikidb->get('ConfirmEmail');
399 while (!empty($data[$id])) { // id collision
400 $id = rand_ascii_readable(16);
402 $subject = _("E-mail address confirmation");
403 $ip = $request->get('REMOTE_HOST');
404 $expire_date = time() + 7 * 86400;
405 $content = fmt("Someone, probably you from IP address %s, has registered an
406 account \"%s\" with this e-mail address on %s.
408 To confirm that this account really does belong to you and activate
409 e-mail features on %s, open this link in your browser:
413 If this is *not* you, don't follow the link. This confirmation code
415 $ip, $userid, WIKI_NAME, WIKI_NAME,
416 WikiURL(HOME_PAGE, array('action' => 'ConfirmEmail',
419 CTime($expire_date));
420 $this->sendMail($subject, $content, "", true);
421 $data[$id] = array('email' => $email,
423 'expire' => $expire_date);
424 $wikidb->set('ConfirmEmail', $data);
428 function checkEmailConfirmation()
431 $wikidb = $request->getDbh();
432 $data = $wikidb->get('ConfirmEmail');
433 $id = $request->getArg('id');
434 if (empty($data[$id])) { // id not found
435 return HTML(HTML::h1("Confirm E-mail address"),
436 HTML::h1("Sorry! Wrong URL"));
439 $userid = $data['userid'];
440 $email = $data['email'];
441 $u = $request->getUser();
442 if ($u->UserName() == $userid) { // lucky: current user (session)
443 $prefs = $u->getPreferences();
444 $request->_user->_level = WIKIAUTH_USER;
445 $request->_prefs->set('emailVerified', true);
446 } else { // not current user
447 if (ENABLE_USER_NEW) {
448 $u = WikiUser($userid);
449 $u->getPreferences();
450 $prefs = &$u->_prefs;
452 $u = new WikiUser($request, $userid);
453 $prefs = $u->getPreferences();
455 $u->_level = WIKIAUTH_USER;
456 $request->setUser($u);
457 $request->_prefs->set('emailVerified', true);
460 $wikidb->set('ConfirmEmail', $data);
461 return HTML(HTML::h1("Confirm E-mail address"),
462 HTML::p("Your e-mail address has now been confirmed."));
465 function subject_encode($subject)
467 // We need to encode the subject if it contains non-ASCII characters
468 // The page name may contain non-ASCII characters, as well as
469 // the translation of the messages, e.g. _("PageChange Notification of %s");
471 // If all characters are ASCII, do nothing
472 if (isAsciiString($subject)) {
476 // Let us try quoted printable first
477 if (function_exists('quoted_printable_encode')) { // PHP 5.3
478 // quoted_printable_encode inserts "\r\n" if line is too long, use "\n" only
479 return "=?UTF-8?Q?" . str_replace("\r\n", "\n", quoted_printable_encode($subject)) . "?=";
482 // If not, encode in base64 (less human-readable)
483 return "=?UTF-8?B?" . base64_encode($subject) . "?=";
491 // c-hanging-comment-ender-p: nil
492 // indent-tabs-mode: nil