]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/MailNotify.php
Consistent returns
[SourceForge/phpwiki.git] / lib / MailNotify.php
1 <?php
2
3 /* Copyright (C) 2006-2007,2009 Reini Urban
4  * Copyright (C) 2009 Marc-Etienne Vargenau, Alcatel-Lucent
5  *
6  * This file is part of PhpWiki.
7  *
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.
12  *
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.
17  *
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.
21  */
22
23 /**
24  * Handle the pagelist pref[notifyPages] logic for users
25  * and notify => hash ( page => (userid => userhash) ) for pages.
26  * Generate notification emails.
27  *
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
35  *
36  * Helper functions:
37  *   getPageChangeEmails
38  *   MailAdmin
39  *   ? handle emailed confirmation links (EmailSignup, ModeratedPage)
40  *
41  * @package MailNotify
42  * @author  Reini Urban
43  */
44
45 if (!defined("MAILER_LOG")) {
46     if (isWindows()) {
47         define("MAILER_LOG", 'c:/wikimail.log');
48     } else {
49         define("MAILER_LOG", '/var/log/wikimail.log');
50     }
51 }
52
53 class MailNotify
54 {
55
56     function MailNotify($pagename)
57     {
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();
64     }
65
66     function fromId()
67     {
68         global $request;
69         if (FUSIONFORGE) {
70             return $request->_user->getId();
71         } else {
72             return $request->_user->getId() . '@' . $request->get('REMOTE_HOST');
73         }
74     }
75
76     function fromEmail()
77     {
78         global $request;
79         return $this->userEmail($request->_user->getId(), false);
80     }
81
82     function userEmail($userid, $doverify = true)
83     {
84         global $request;
85
86         // Disable verification of emails for corporate env.
87         if (FUSIONFORGE) {
88             $doverify = false;
89         }
90
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')) {
97                 $email = '';
98             }
99         } else { // not current user
100             if (ENABLE_USER_NEW) {
101                 $u = WikiUser($userid);
102                 $u->getPreferences();
103                 $prefs = &$u->_prefs;
104             } else {
105                 $u = new WikiUser($request, $userid);
106                 $prefs = $u->getPreferences();
107             }
108             $email = $prefs->get('email');
109             if ($doverify and !$prefs->get('emailVerified')) {
110                 $email = '';
111             }
112         }
113         return $email;
114     }
115
116     /**
117      * getPageChangeEmails($notify)
118      * @param  $notify: hash ( page => (userid => userhash) )
119      * @return array
120      *         unique array of ($emails, $userids)
121      */
122     function getPageChangeEmails($notify)
123     {
124         global $request;
125         $emails = array();
126         $userids = array();
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');
135
136                 foreach ($users as $userid => $user) {
137
138                     $usermail = $user['email'];
139
140                     if (($usermail == $curuserprefsemail)
141                         and ($ownModifications)
142                     ) {
143                         // It's my own modification
144                         // and I do not want to receive it
145                         continue;
146                     }
147
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
155                             continue;
156                         }
157                     }
158
159                     if (!$user) { // handle the case for ModeratePage:
160                         // no prefs, just userid's.
161                         $emails[] = $this->userEmail($userid, false);
162                         $userids[] = $userid;
163                     } else {
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);
170                             if ($email) {
171                                 $notify[$page][$userid]['verified'] = 1;
172                                 $request->_dbi->set('notify', $notify);
173                                 $emails[] = $email;
174                                 $userids[] = $userid;
175                             }
176                         }
177                         // ignore verification
178                         /*
179                         if (DEBUG) {
180                             if (!in_array($user['email'], $emails))
181                                 $emails[] = $user['email'];
182                         }
183                         */
184                     }
185                 }
186             }
187         }
188         $this->emails = array_unique($emails);
189         $this->userids = array_unique($userids);
190         return array($this->emails, $this->userids);
191     }
192
193     function sendMail($subject,
194                       $content,
195                       $notice = false,
196                       $silent = true)
197     {
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)) {
205             return true;
206         }
207         if (!$notice) {
208             $notice = _("PageChange Notification of %s");
209         }
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";
216
217         $ok = mail(($to = array_shift($emails)),
218             $encoded_subject,
219             $subject . "\n" . $content,
220             $headers
221         );
222         if (MAILER_LOG and is_writable(MAILER_LOG)) {
223             global $ErrorManager;
224
225             $f = fopen(MAILER_LOG, "a");
226             fwrite($f, "\n\nX-MailSentOK: " . $ok ? 'OK' : 'FAILED');
227
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);
236             }
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);
243             fclose($f);
244         }
245         if ($ok) {
246             if (!$silent)
247                 trigger_error(sprintf($notice, $this->pagename)
248                         . " "
249                         . sprintf(_("sent to %s"), join(',', $this->userids)),
250                     E_USER_NOTICE);
251             return true;
252         } else {
253             trigger_error(sprintf($notice, $this->pagename)
254                     . " "
255                     . sprintf(_("Error: Couldn't send %s to %s"),
256                         $subject . "\n" . $content, join(',', $this->userids)),
257                 E_USER_WARNING);
258             return false;
259         }
260     }
261
262     /**
263      * Send udiff for a changed page to multiple users.
264      * See rename and remove methods also
265      */
266     function sendPageChangeNotification(&$wikitext, $version, &$meta)
267     {
268
269         global $request;
270
271         if (isset($request->_deferredPageChangeNotification)) {
272             // collapse multiple changes (loaddir) into one email
273             $request->_deferredPageChangeNotification[] =
274                 array($this->pagename, $this->emails, $this->userids);
275             return;
276         }
277         $backend = &$request->_dbi->_backend;
278         $previous = $backend->get_previous_version($this->pagename, $version);
279         if (!isset($meta['mtime'])) {
280             $meta['mtime'] = time();
281         }
282         if ($previous) {
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);
291             }
292             $other_content = explode("\n", $prevdata['%content']);
293
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());
305         } else {
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");
312             $content .= "\n\n";
313             $content .= $wikitext;
314             $editedby = sprintf(_("Created by: %s"), $this->fromId());
315         }
316         $summary = sprintf(_("Summary: %s"), $meta['summary']);
317         $this->sendMail($subject,
318             $editedby . "\n" . $summary . "\n" . $difflink . "\n\n" . $content);
319     }
320
321     /**
322      * Support mass rename / remove (TBD)
323      */
324     function sendPageRenameNotification($to, &$meta)
325     {
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");
332     }
333
334     /**
335      * The handlers:
336      */
337     function onChangePage(&$wikidb, &$wikitext, $version, &$meta)
338     {
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);
349                 }
350             }
351         }
352     }
353
354     function onDeletePage(&$wikidb, $pagename)
355     {
356         $result = true;
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");
366                 }
367             }
368         }
369         return $result;
370     }
371
372     function onRenamePage(&$wikidb, $oldpage, $new_pagename)
373     {
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);
384                 }
385             }
386         }
387     }
388
389     /**
390      * Send mail to user and store the cookie in the db
391      * wikiurl?action=ConfirmEmail&id=bla
392      */
393     function sendEmailConfirmation($email, $userid)
394     {
395         global $request;
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);
401         }
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.
407
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:
410
411 %s
412
413 If this is *not* you, don't follow the link. This confirmation code
414 will expire at %s.",
415             $ip, $userid, WIKI_NAME, WIKI_NAME,
416             WikiURL(HOME_PAGE, array('action' => 'ConfirmEmail',
417                     'id' => $id),
418                 true),
419             CTime($expire_date));
420         $this->sendMail($subject, $content, "", true);
421         $data[$id] = array('email' => $email,
422             'userid' => $userid,
423             'expire' => $expire_date);
424         $wikidb->set('ConfirmEmail', $data);
425         return '';
426     }
427
428     function checkEmailConfirmation()
429     {
430         global $request;
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"));
437         }
438         // upgrade the user
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;
451             } else {
452                 $u = new WikiUser($request, $userid);
453                 $prefs = $u->getPreferences();
454             }
455             $u->_level = WIKIAUTH_USER;
456             $request->setUser($u);
457             $request->_prefs->set('emailVerified', true);
458         }
459         unset($data[$id]);
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."));
463     }
464
465     function subject_encode($subject)
466     {
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");
470
471         // If all characters are ASCII, do nothing
472         if (isAsciiString($subject)) {
473             return $subject;
474         }
475
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)) . "?=";
480         }
481
482         // If not, encode in base64 (less human-readable)
483         return "=?UTF-8?B?" . base64_encode($subject) . "?=";
484     }
485 }
486
487 // Local Variables:
488 // mode: php
489 // tab-width: 8
490 // c-basic-offset: 4
491 // c-hanging-comment-ender-p: nil
492 // indent-tabs-mode: nil
493 // End: