]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/MailNotify.php
Improve messages
[SourceForge/phpwiki.git] / lib / MailNotify.php
1 <?php
2 // $Id$
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     function MailNotify($pagename) {
56         $this->pagename = $pagename; /* which page */
57         $this->emails  = array();    /* to which addresses */
58         $this->userids = array();    /* corresponding array of displayed names,
59                                         don't display the email addresses */
60         /* From: from whom the mail appears to be */
61         $this->from = $this->fromId();
62     }
63
64     function fromId() {
65         global $request;
66         if (FUSIONFORGE) {
67             return $request->_user->getId();
68         } else {
69             return $request->_user->getId() . '@' .  $request->get('REMOTE_HOST');
70         }
71     }
72
73     function fromEmail() {
74         global $request;
75         return $this->userEmail($request->_user->getId(), false);
76     }
77
78     function userEmail($userid, $doverify = true) {
79         global $request;
80
81         // Disable verification of emails for corporate env.
82         if (FUSIONFORGE) {
83             $doverify = false;
84         }
85
86         $u = $request->getUser();
87         if ($u->UserName() == $userid) { // lucky: current user
88             $prefs = $u->getPreferences();
89             $email = $prefs->get('email');
90             // do a dynamic emailVerified check update
91             if ($doverify and !$request->_prefs->get('emailVerified')) {
92                 $email = '';
93             }
94         } else {  // not current user
95             if (ENABLE_USER_NEW) {
96                 $u = WikiUser($userid);
97                 $u->getPreferences();
98                 $prefs = &$u->_prefs;
99             } else {
100                 $u = new WikiUser($request, $userid);
101                 $prefs = $u->getPreferences();
102             }
103             $email = $prefs->get('email');
104             if ($doverify and !$prefs->get('emailVerified')) {
105                 $email = '';
106             }
107         }
108         return $email;
109     }
110
111     /**
112      * getPageChangeEmails($notify)
113      * @param  $notify: hash ( page => (userid => userhash) )
114      * @return array
115      *         unique array of ($emails, $userids)
116      */
117     function getPageChangeEmails($notify) {
118         global $request;
119         $emails = array(); $userids = array();
120         foreach ($notify as $page => $users) {
121             if (glob_match($page, $this->pagename)) {
122
123                 global $request;
124                 $curuser = $request->getUser();
125                 $curusername = $curuser->UserName();
126                 $curuserprefs = $curuser->getPreferences();
127                 $curuserprefsemail = $curuserprefs->get('email');
128                 $ownModifications = $curuserprefs->get('ownModifications');
129                 $majorModificationsOnly = $curuserprefs->get('majorModificationsOnly');
130
131                 foreach ($users as $userid => $user) {
132
133                     $usermail = $user['email'];
134
135                     if (($usermail == $curuserprefsemail)
136                         and ($ownModifications)) {
137                         // It's my own modification
138                         // and I do not want to receive it
139                         continue;
140                     }
141
142                     if ($majorModificationsOnly) {
143                         $backend = &$request->_dbi->_backend;
144                         $version = $backend->get_latest_version($this->pagename);
145                         $versiondata = $backend->get_versiondata($this->pagename, $version, true);
146                         if ($versiondata['is_minor_edit']) {
147                             // It's a minor modification
148                             // and I do not want to receive it
149                             continue;
150                         }
151                     }
152
153                     if (!$user) { // handle the case for ModeratePage:
154                               // no prefs, just userid's.
155                         $emails[] = $this->userEmail($userid, false);
156                         $userids[] = $userid;
157                     } else {
158                         if (!empty($user['verified']) and !empty($user['email'])) {
159                             $emails[]  = $user['email'];
160                             $userids[] = $userid;
161                         } elseif (!empty($user['email'])) {
162                             // do a dynamic emailVerified check update
163                             $email = $this->userEmail($userid, true);
164                             if ($email) {
165                                 $notify[$page][$userid]['verified'] = 1;
166                                 $request->_dbi->set('notify', $notify);
167                                 $emails[] = $email;
168                                 $userids[] = $userid;
169                             }
170                         }
171                         // ignore verification
172                         /*
173                         if (DEBUG) {
174                             if (!in_array($user['email'], $emails))
175                                 $emails[] = $user['email'];
176                         }
177                         */
178                     }
179                 }
180             }
181         }
182         $this->emails = array_unique($emails);
183         $this->userids = array_unique($userids);
184         return array($this->emails, $this->userids);
185     }
186
187     function sendMail($subject,
188                       $content,
189                       $notice = false,
190                       $silent = true)
191     {
192         // Add WIKI_NAME to Subject
193         $subject = "[".WIKI_NAME."] ".$subject;
194         // Encode $subject if needed
195         $encoded_subject = $this->subject_encode($subject);
196         $emails = $this->emails;
197         // Do not send if modification is from FusionForge admin
198         if (FUSIONFORGE and ($this->fromId() == ADMIN_USER)) {
199             return;
200         }
201         if (!$notice) {
202             $notice = _("PageChange Notification of %s");
203         }
204         $from = $this->fromEmail();
205         $headers = "From: $from\r\n" .
206                    "Bcc: ".join(',', $emails)."\r\n" .
207                    "MIME-Version: 1.0\r\n" .
208                    "Content-Type: text/plain; charset=".CHARSET."; format=flowed\r\n" .
209                    "Content-Transfer-Encoding: 8bit";
210
211         $ok = mail(($to = array_shift($emails)),
212                    $encoded_subject,
213            $subject."\n".$content,
214            $headers
215            );
216         if (MAILER_LOG and is_writable(MAILER_LOG)) {
217             global $ErrorManager;
218
219             $f = fopen(MAILER_LOG, "a");
220             fwrite($f, "\n\nX-MailSentOK: " . $ok ? 'OK' : 'FAILED');
221
222             if (!$ok && isset($ErrorManager->_postponed_errors[count($ErrorManager->_postponed_errors)-1])) {
223                 // get last error message
224                 $last_err = $ErrorManager->_postponed_errors[count($ErrorManager->_postponed_errors)-1];
225                 fwrite($f, "\nX-MailFailure: " .
226                            "errno: " . $last_err->errno . ", " .
227                            "errstr: " . $last_err->errstr . ", " .
228                            "errfile: " . $last_err->errfile . ", " .
229                            "errline: " . $last_err->errline);
230             }
231             fwrite($f, "\nDate: " . CTime());
232             fwrite($f, "\nSubject: $encoded_subject");
233             fwrite($f, "\nFrom: $from");
234             fwrite($f, "\nTo: $to");
235             fwrite($f, "\nBcc: ".join(',', $emails));
236             fwrite($f, "\n\n". $content);
237             fclose($f);
238         }
239         if ($ok) {
240             if (!$silent)
241                 trigger_error(sprintf($notice, $this->pagename)
242                               . " "
243                               . sprintf(_("sent to %s"), join(',',$this->userids)),
244                               E_USER_NOTICE);
245             return true;
246         } else {
247             trigger_error(sprintf($notice, $this->pagename)
248                           . " "
249                           . sprintf(_("Error: Couldn't send %s to %s"),
250                                    $subject."\n".$content, join(',',$this->userids)),
251                           E_USER_WARNING);
252             return false;
253         }
254     }
255
256     /**
257      * Send udiff for a changed page to multiple users.
258      * See rename and remove methods also
259      */
260     function sendPageChangeNotification(&$wikitext, $version, &$meta) {
261
262         global $request;
263
264         if (isset($request->_deferredPageChangeNotification)) {
265             // collapse multiple changes (loaddir) into one email
266             $request->_deferredPageChangeNotification[] =
267                 array($this->pagename, $this->emails, $this->userids);
268             return;
269         }
270         $backend = &$request->_dbi->_backend;
271         $previous = $backend->get_previous_version($this->pagename, $version);
272         if (!isset($meta['mtime'])) {
273             $meta['mtime'] = time();
274         }
275         if ($previous) {
276             // Page existed, and was modified
277             $subject = _("Page change").' '.($this->pagename);
278             $difflink = WikiURL($this->pagename, array('action'=>'diff'), true);
279             $cache = &$request->_dbi->_cache;
280             $this_content = explode("\n", $wikitext);
281             $prevdata = $cache->get_versiondata($this->pagename, $previous, true);
282             if (empty($prevdata['%content'])) {
283                 $prevdata = $backend->get_versiondata($this->pagename, $previous, true);
284             }
285             $other_content = explode("\n", $prevdata['%content']);
286
287             include_once("lib/difflib.php");
288             $diff2 = new Diff($other_content, $this_content);
289             //$context_lines = max(4, count($other_content) + 1,
290             //                     count($this_content) + 1);
291             $fmt = new UnifiedDiffFormatter(/*$context_lines*/);
292             $content  = $this->pagename . " " . $previous . " " .
293                 Iso8601DateTime($prevdata['mtime']) . "\n";
294             $content .= $this->pagename . " " . $version . " " .
295                 Iso8601DateTime($meta['mtime']) . "\n";
296             $content .= $fmt->format($diff2);
297             $editedby = sprintf(_("Edited by: %s"), $this->fromId());
298         } else {
299             // Page did not exist, and was created
300             $subject = _("Page creation").' '.($this->pagename);
301             $difflink = WikiURL($this->pagename,array(),true);
302             $content = $this->pagename . " " . $version . " " .
303                 Iso8601DateTime($meta['mtime']) . "\n";
304             $content .= _("New page");
305             $content .= "\n\n";
306             $content .= $wikitext;
307             $editedby = sprintf(_("Created by: %s"), $this->fromId());
308         }
309         $summary = sprintf(_("Summary: %s"), $meta['summary']);
310         $this->sendMail($subject,
311                         $editedby."\n".$summary."\n".$difflink."\n\n".$content);
312     }
313
314     /**
315      * Support mass rename / remove (TBD)
316      */
317     function sendPageRenameNotification ($to, &$meta) {
318         $pagename = $this->pagename;
319         $editedby = sprintf(_("Renamed by: %s"), $this->fromId());
320         $subject = sprintf(_("Page rename %s to %s"), $pagename, $to);
321         $link = WikiURL($to, true);
322         $this->sendMail($subject,
323                         $editedby."\n".$link."\n\n"."Renamed $pagename to $to");
324     }
325
326     /**
327      * The handlers:
328      */
329     function onChangePage (&$wikidb, &$wikitext, $version, &$meta) {
330         $result = true;
331         if (!isa($GLOBALS['request'],'MockRequest')) {
332             $notify = $wikidb->get('notify');
333             /* Generate notification emails? */
334             if (!empty($notify) and is_array($notify)) {
335                 if (empty($this->pagename))
336                     $this->pagename = $meta['pagename'];
337                 // TODO: Should be used for ModeratePage and RSS2 Cloud xml-rpc also.
338                 $this->getPageChangeEmails($notify);
339                 if (!empty($this->emails)) {
340                     $result = $this->sendPageChangeNotification($wikitext, $version, $meta);
341                 }
342             }
343         }
344         return $result;
345     }
346
347     function onDeletePage (&$wikidb, $pagename) {
348         $result = true;
349         /* Generate notification emails? */
350         if (! $wikidb->isWikiPage($pagename) and !isa($GLOBALS['request'],'MockRequest')) {
351             $notify = $wikidb->get('notify');
352             if (!empty($notify) and is_array($notify)) {
353                 //TODO: deferr it (quite a massive load if you remove some pages).
354                 $this->getPageChangeEmails($notify);
355                 if (!empty($this->emails)) {
356                     $subject = sprintf(_("User %s removed page %s"), $this->fromId(), $pagename);
357                     $result = $this->sendMail($subject, $subject."\n\n");
358                 }
359             }
360         }
361         return $result;
362     }
363
364     function onRenamePage (&$wikidb, $oldpage, $new_pagename) {
365         $result = true;
366         if (!isa($GLOBALS['request'], 'MockRequest')) {
367             $notify = $wikidb->get('notify');
368             if (!empty($notify) and is_array($notify)) {
369                 $this->getPageChangeEmails($notify);
370                 if (!empty($this->emails)) {
371                     $newpage = $wikidb->getPage($new_pagename);
372                     $current = $newpage->getCurrentRevision();
373                     $meta = $current->_data;
374                     $this->pagename = $oldpage;
375                     $result = $this->sendPageRenameNotification($new_pagename, $meta);
376                 }
377             }
378         }
379     }
380
381     /**
382      * Send mail to user and store the cookie in the db
383      * wikiurl?action=ConfirmEmail&id=bla
384      */
385     function sendEmailConfirmation ($email, $userid) {
386         $id = rand_ascii_readable(16);
387         $wikidb = $GLOBALS['request']->getDbh();
388         $data = $wikidb->get('ConfirmEmail');
389         while(!empty($data[$id])) { // id collision
390             $id = rand_ascii_readable(16);
391         }
392         $subject = _("E-mail address confirmation");
393         $ip = $request->get('REMOTE_HOST');
394         $expire_date = time() + 7*86400;
395         $content = fmt("Someone, probably you from IP address %s, has registered an
396 account \"%s\" with this e-mail address on %s.
397
398 To confirm that this account really does belong to you and activate
399 e-mail features on %s, open this link in your browser:
400
401 %s
402
403 If this is *not* you, don't follow the link. This confirmation code
404 will expire at %s.",
405                        $ip, $userid, WIKI_NAME, WIKI_NAME,
406                        WikiURL(HOME_PAGE, array('action' => 'ConfirmEmail',
407                                                 'id' => $id),
408                                true),
409                        CTime($expire_date));
410         $this->sendMail($subject, $content, "", true);
411         $data[$id] = array('email' => $email,
412                            'userid' => $userid,
413                            'expire' => $expire_date);
414         $wikidb->set('ConfirmEmail', $data);
415         return '';
416     }
417
418     function checkEmailConfirmation () {
419         global $request;
420         $wikidb = $request->getDbh();
421         $data = $wikidb->get('ConfirmEmail');
422         $id = $request->getArg('id');
423         if (empty($data[$id])) { // id not found
424             return HTML(HTML::h1("Confirm E-mail address"),
425                         HTML::h1("Sorry! Wrong URL"));
426         }
427         // upgrade the user
428         $userid = $data['userid'];
429         $email = $data['email'];
430         $u = $request->getUser();
431         if ($u->UserName() == $userid) { // lucky: current user (session)
432             $prefs = $u->getPreferences();
433             $request->_user->_level = WIKIAUTH_USER;
434             $request->_prefs->set('emailVerified', true);
435         } else {  // not current user
436             if (ENABLE_USER_NEW) {
437                 $u = WikiUser($userid);
438                 $u->getPreferences();
439                 $prefs = &$u->_prefs;
440             } else {
441                 $u = new WikiUser($request, $userid);
442                 $prefs = $u->getPreferences();
443             }
444             $u->_level = WIKIAUTH_USER;
445             $request->setUser($u);
446             $request->_prefs->set('emailVerified', true);
447         }
448         unset($data[$id]);
449         $wikidb->set('ConfirmEmail', $data);
450         return HTML(HTML::h1("Confirm E-mail address"),
451                     HTML::p("Your e-mail address has now been confirmed."));
452     }
453
454     function subject_encode ($subject) {
455         // We need to encode the subject if it contains non-ASCII characters
456         // The page name may contain non-ASCII characters, as well as
457         // the translation of the messages, e.g. _("PageChange Notification of %s");
458
459         // If all characters are ASCII, do nothing
460         if (isAsciiString($subject)) {
461             return $subject;
462         }
463
464         // Let us try quoted printable first
465         if (function_exists('quoted_printable_encode')) { // PHP 5.3
466             return "=?UTF-8?Q?".quoted_printable_encode($subject)."?=";
467         }
468
469         // If not, encode in base64 (less human-readable)
470         return "=?UTF-8?B?".base64_encode($subject)."?=";
471     }
472 }
473
474 // Local Variables:
475 // mode: php
476 // tab-width: 8
477 // c-basic-offset: 4
478 // c-hanging-comment-ender-p: nil
479 // indent-tabs-mode: nil
480 // End:
481 ?>