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