3 /* Copyright (C) 2006-2007 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
19 * along with PhpWiki; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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');
53 function MailNotify($pagename) {
54 $this->pagename = $pagename; /* which page */
55 $this->emails = array(); /* to which addresses */
56 $this->userids = array(); /* corresponding array of displayed names,
57 don't display the email addresses */
58 /* From: from whom the mail appears to be */
59 $this->from = $this->fromId();
64 if (defined('GFORGE') and GFORGE) {
65 return $request->_user->getId();
67 return $request->_user->getId() . '@' . $request->get('REMOTE_HOST');
71 function userEmail($userid, $doverify = true) {
74 // Disable verification of emails for corporate env.
75 if (defined('GFORGE') and GFORGE) {
79 $u = $request->getUser();
80 if ($u->UserName() == $userid) { // lucky: current user
81 $prefs = $u->getPreferences();
82 $email = $prefs->get('email');
83 // do a dynamic emailVerified check update
84 if ($doverify and !$request->_prefs->get('emailVerified'))
86 } else { // not current user
87 if (ENABLE_USER_NEW) {
88 $u = WikiUser($userid);
92 $u = new WikiUser($request, $userid);
93 $prefs = $u->getPreferences();
95 $email = $prefs->get('email');
96 if ($doverify and !$prefs->get('emailVerified')) {
104 * getPageChangeEmails($notify)
105 * @param $notify: hash ( page => (userid => userhash) )
107 * unique array of ($emails, $userids)
109 function getPageChangeEmails($notify) {
111 $emails = array(); $userids = array();
112 foreach ($notify as $page => $users) {
113 if (glob_match($page, $this->pagename)) {
116 $curuser = $request->getUser();
117 $curusername = $curuser->UserName();
118 $curuserprefs = $curuser->getPreferences();
119 $curuserprefsemail = $curuserprefs->get('email');
120 $ownModifications = $curuserprefs->get('ownModifications');
121 $majorModificationsOnly = $curuserprefs->get('majorModificationsOnly');
123 foreach ($users as $userid => $user) {
125 $usermail = $user['email'];
127 if (($usermail == $curuserprefsemail)
128 and ($ownModifications)) {
129 // It's my own modification
130 // and I do not want to receive it
134 if ($majorModificationsOnly) {
135 $backend = &$request->_dbi->_backend;
136 $version = $backend->get_latest_version($this->pagename);
137 $versiondata = $backend->get_versiondata($this->pagename, $version, true);
138 if ($versiondata['is_minor_edit']) {
139 // It's a minor modification
140 // and I do not want to receive it
145 if (!$user) { // handle the case for ModeratePage:
146 // no prefs, just userid's.
147 $emails[] = $this->userEmail($userid, false);
148 $userids[] = $userid;
150 if (!empty($user['verified']) and !empty($user['email'])) {
151 $emails[] = $user['email'];
152 $userids[] = $userid;
153 } elseif (!empty($user['email'])) {
154 // do a dynamic emailVerified check update
155 $email = $this->userEmail($userid, true);
157 $notify[$page][$userid]['verified'] = 1;
158 $request->_dbi->set('notify', $notify);
160 $userids[] = $userid;
163 // ignore verification
166 if (!in_array($user['email'], $emails))
167 $emails[] = $user['email'];
174 $this->emails = array_unique($emails);
175 $this->userids = array_unique($userids);
176 return array($this->emails, $this->userids);
179 function sendMail($subject,
184 if (defined('GFORGE') and GFORGE) {
185 // Add WIKI_NAME to Subject
186 $subject = "[".WIKI_NAME."] ".$subject;
188 // Encode $subject if needed
189 $encoded_subject = $this->subject_encode($subject);
190 $emails = $this->emails;
192 // Do not send if modification is from Gforge admin
193 if (defined('GFORGE') and GFORGE) {
194 if ($from == ADMIN_USER) {
198 if (!$notice) $notice = _("PageChange Notification of %s");
199 $headers = "From: $from\r\n" .
200 "Bcc: ".join(',', $emails)."\r\n" .
201 "MIME-Version: 1.0\r\n" .
202 "Content-Type: text/plain; charset=".CHARSET."; format=flowed\r\n" .
203 "Content-Transfer-Encoding: 8bit";
205 $ok = mail(($to = array_shift($emails)),
207 $subject."\n".$content,
210 if (MAILER_LOG and is_writable(MAILER_LOG)) {
211 $f = fopen(MAILER_LOG, "a");
212 fwrite($f, "\n\nX-MailSentOK: " . $ok ? 'OK' : 'FAILED');
214 global $ErrorManager;
215 // get last error message
216 $last_err = $ErrorManager->_postponed_errors[count($ErrorHandler->_postponed_errors)-1];
217 fwrite($f, "\nX-MailFailure: " . $last_err);
219 fwrite($f, "\nDate: " . CTime());
220 fwrite($f, "\nSubject: $encoded_subject");
221 fwrite($f, "\nFrom: $from");
222 fwrite($f, "\nTo: $to");
223 fwrite($f, "\nBcc: ".join(',', $emails));
224 fwrite($f, "\n\n". $content);
229 trigger_error(sprintf($notice, $this->pagename)
231 . sprintf(_("sent to %s"), join(',',$this->userids)),
235 trigger_error(sprintf($notice, $this->pagename)
237 . sprintf(_("Error: Couldn't send %s to %s"),
238 $subject."\n".$content, join(',',$this->userids)),
245 * Send udiff for a changed page to multiple users.
246 * See rename and remove methods also
248 function sendPageChangeNotification(&$wikitext, $version, &$meta) {
252 if (@is_array($request->_deferredPageChangeNotification)) {
253 // collapse multiple changes (loaddir) into one email
254 $request->_deferredPageChangeNotification[] =
255 array($this->pagename, $this->emails, $this->userids);
258 $backend = &$request->_dbi->_backend;
259 $subject = _("Page change").' '.($this->pagename);
260 $previous = $backend->get_previous_version($this->pagename, $version);
261 if (!isset($meta['mtime'])) $meta['mtime'] = time();
263 $difflink = WikiURL($this->pagename, array('action'=>'diff'), true);
264 $cache = &$request->_dbi->_cache;
265 $this_content = explode("\n", $wikitext);
266 $prevdata = $cache->get_versiondata($this->pagename, $previous, true);
267 if (empty($prevdata['%content']))
268 $prevdata = $backend->get_versiondata($this->pagename, $previous, true);
269 $other_content = explode("\n", $prevdata['%content']);
271 include_once("lib/difflib.php");
272 $diff2 = new Diff($other_content, $this_content);
273 //$context_lines = max(4, count($other_content) + 1,
274 // count($this_content) + 1);
275 $fmt = new UnifiedDiffFormatter(/*$context_lines*/);
276 $content = $this->pagename . " " . $previous . " " .
277 Iso8601DateTime($prevdata['mtime']) . "\n";
278 $content .= $this->pagename . " " . $version . " " .
279 Iso8601DateTime($meta['mtime']) . "\n";
280 $content .= $fmt->format($diff2);
283 $difflink = WikiURL($this->pagename,array(),true);
284 $content = $this->pagename . " " . $version . " " .
285 Iso8601DateTime($meta['mtime']) . "\n";
286 $content .= _("New page");
288 $content .= $wikitext;
290 $editedby = sprintf(_("Edited by: %s"), $this->from);
291 //$editedby = sprintf(_("Edited by: %s"), $meta['author']);
292 $this->sendMail($subject,
293 $editedby."\n".$difflink."\n\n".$content);
297 * Support mass rename / remove (not yet tested)
299 function sendPageRenameNotification ($to, &$meta) {
302 if (@is_array($request->_deferredPageRenameNotification)) {
303 $request->_deferredPageRenameNotification[] =
304 array($this->pagename, $to, $meta, $this->emails, $this->userids);
306 $pagename = $this->pagename;
307 //$editedby = sprintf(_("Edited by: %s"), $meta['author']) . ' ' . $meta['author_id'];
308 $editedby = sprintf(_("Edited by: %s"), $this->from);
309 $subject = sprintf(_("Page rename %s to %s"), $pagename, $to);
310 $link = WikiURL($to, true);
311 $this->sendMail($subject,
312 $editedby."\n".$link."\n\n"."Renamed $pagename to $to");
319 function onChangePage (&$wikidb, &$wikitext, $version, &$meta) {
321 if (!isa($GLOBALS['request'],'MockRequest')) {
322 $notify = $wikidb->get('notify');
323 /* Generate notification emails? */
324 if (!empty($notify) and is_array($notify)) {
325 if (empty($this->pagename))
326 $this->pagename = $meta['pagename'];
327 // TODO: Should be used for ModeratePage and RSS2 Cloud xml-rpc also.
328 $this->getPageChangeEmails($notify);
329 if (!empty($this->emails)) {
330 $result = $this->sendPageChangeNotification($wikitext, $version, $meta);
337 function onDeletePage (&$wikidb, $pagename) {
339 /* Generate notification emails? */
340 if (! $wikidb->isWikiPage($pagename) and !isa($GLOBALS['request'],'MockRequest')) {
341 $notify = $wikidb->get('notify');
342 if (!empty($notify) and is_array($notify)) {
343 //TODO: deferr it (quite a massive load if you remove some pages).
344 $this->getPageChangeEmails($notify);
345 if (!empty($this->emails)) {
346 $editedby = sprintf(_("Removed by: %s"), $this->from); // Todo: host_id
347 //$emails = join(',', $this->emails);
348 $subject = sprintf(_("Page removed %s"), $pagename);
349 $page = $wikidb->getPage($pagename);
350 $rev = $page->getCurrentRevision(true);
351 $content = $rev->getPackedContent();
352 $result = $this->sendMail($subject,
353 $editedby."\n"."Deleted $pagename"."\n\n".$content);
357 //How to create a RecentChanges entry with explaining summary? Dynamically
359 $page = $this->getPage($pagename);
360 $current = $page->getCurrentRevision();
361 $meta = $current->_data;
362 $version = $current->getVersion();
363 $meta['summary'] = _("removed");
364 $page->save($current->getPackedContent(), $version + 1, $meta);
369 function onRenamePage (&$wikidb, $oldpage, $new_pagename) {
371 if (!isa($GLOBALS['request'], 'MockRequest')) {
372 $notify = $wikidb->get('notify');
373 if (!empty($notify) and is_array($notify)) {
374 $this->getPageChangeEmails($notify);
375 if (!empty($this->emails)) {
376 $newpage = $wikidb->getPage($new_pagename);
377 $current = $newpage->getCurrentRevision();
378 $meta = $current->_data;
379 $this->pagename = $oldpage;
380 $result = $this->sendPageRenameNotification($new_pagename, $meta);
387 * Send mail to user and store the cookie in the db
388 * wikiurl?action=ConfirmEmail&id=bla
390 function sendEmailConfirmation ($email, $userid) {
391 $id = rand_ascii_readable(16);
392 $wikidb = $GLOBALS['request']->getDbh();
393 $data = $wikidb->get('ConfirmEmail');
394 while(!empty($data[$id])) { // id collision
395 $id = rand_ascii_readable(16);
397 $subject = WIKI_NAME . " " . _("e-mail address confirmation");
398 $ip = $request->get('REMOTE_HOST');
399 $expire_date = time() + 7*86400;
400 $content = fmt("Someone, probably you from IP address %s, has registered an
401 account \"%s\" with this e-mail address on %s.
403 To confirm that this account really does belong to you and activate
404 e-mail features on %s, open this link in your browser:
408 If this is *not* you, don't follow the link. This confirmation code
410 $ip, $userid, WIKI_NAME, WIKI_NAME,
411 WikiURL(HOME_PAGE, array('action' => 'ConfirmEmail',
414 CTime($expire_date));
415 $this->sendMail($subject, $content, "", true);
416 $data[$id] = array('email' => $email,
418 'expire' => $expire_date);
419 $wikidb->set('ConfirmEmail', $data);
423 function checkEmailConfirmation () {
425 $wikidb = $request->getDbh();
426 $data = $wikidb->get('ConfirmEmail');
427 $id = $request->getArg('id');
428 if (empty($data[$id])) { // id not found
429 return HTML(HTML::h1("Confirm E-mail address"),
430 HTML::h1("Sorry! Wrong URL"));
433 $userid = $data['userid'];
434 $email = $data['email'];
435 $u = $request->getUser();
436 if ($u->UserName() == $userid) { // lucky: current user (session)
437 $prefs = $u->getPreferences();
438 $request->_user->_level = WIKIAUTH_USER;
439 $request->_prefs->set('emailVerified', true);
440 } else { // not current user
441 if (ENABLE_USER_NEW) {
442 $u = WikiUser($userid);
443 $u->getPreferences();
444 $prefs = &$u->_prefs;
446 $u = new WikiUser($request, $userid);
447 $prefs = $u->getPreferences();
449 $u->_level = WIKIAUTH_USER;
450 $request->setUser($u);
451 $request->_prefs->set('emailVerified', true);
454 $wikidb->set('ConfirmEmail', $data);
455 return HTML(HTML::h1("Confirm E-mail address"),
456 HTML::p("Your e-mail address has now been confirmed."));
459 function subject_encode ($subject) {
460 // We need to encode the subject if it contains non-ASCII characters
461 // The page name may contain non-ASCII characters, as well as
462 // the translation of the messages, e.g. _("PageChange Notification of %s");
464 // If all characters are ASCII, do nothing
465 if (isAsciiString($subject)) {
469 // Let us try quoted printable first
470 if (function_exists('quoted_printable_encode')) { // PHP 5.3
471 return quoted_printable_encode($subject);
474 // If not, encode in base64 (less human-readable)
475 return base64_encode($subject);
483 // c-hanging-comment-ender-p: nil
484 // indent-tabs-mode: nil