]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/MailNotify.php
Remove MockRequest
[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  *
35  * Helper functions:
36  *   getPageChangeEmails
37  *   MailAdmin
38  *   ? handle emailed confirmation links (EmailSignup, ModeratedPage)
39  *
40  * @package MailNotify
41  * @author  Reini Urban
42  */
43
44 if (!defined("MAILER_LOG")) {
45     if (isWindows()) {
46         define("MAILER_LOG", 'c:/wikimail.log');
47     } else {
48         define("MAILER_LOG", '/var/log/wikimail.log');
49     }
50 }
51
52 class MailNotify
53 {
54
55     function __construct($pagename)
56     {
57         $this->pagename = $pagename; /* which page */
58         $this->emails = array(); /* to which addresses */
59         $this->userids = array(); /* corresponding array of displayed names,
60                                         don't display the email addresses */
61         /* From: from whom the mail appears to be */
62         $this->from = $this->fromId();
63     }
64
65     private function fromId()
66     {
67         global $request;
68         if (defined('FUSIONFORGE') && FUSIONFORGE) {
69             return $request->_user->getId();
70         } else {
71             return $request->_user->getId() . '@' . $request->get('REMOTE_HOST');
72         }
73     }
74
75     private function fromEmail()
76     {
77         global $request;
78         return $this->userEmail($request->_user->getId(), false);
79     }
80
81     private function userEmail($userid, $doverify = true)
82     {
83         global $request;
84
85         // Disable verification of emails for corporate env.
86         if (defined('FUSIONFORGE') && FUSIONFORGE) {
87             $doverify = false;
88         }
89
90         $u = $request->getUser();
91         if ($u->UserName() == $userid) { // lucky: current user
92             $prefs = $u->getPreferences();
93             $email = $prefs->get('email');
94             // do a dynamic emailVerified check update
95             if ($doverify and !$request->_prefs->get('emailVerified')) {
96                 $email = '';
97             }
98         } else { // not current user
99             $u = WikiUser($userid);
100             $u->getPreferences();
101             $prefs = &$u->_prefs;
102             $email = $prefs->get('email');
103             if ($doverify and !$prefs->get('emailVerified')) {
104                 $email = '';
105             }
106         }
107         return $email;
108     }
109
110     /**
111      * getPageChangeEmails
112      * @param  array $notify  hash ( page => (userid => userhash) )
113      * @return array          unique array of ($emails, $userids)
114      */
115     public function getPageChangeEmails($notify)
116     {
117         /**
118          * @var WikiRequest $request
119          */
120         global $request;
121
122         $emails = array();
123         $userids = array();
124         foreach ($notify as $page => $users) {
125             if (glob_match($page, $this->pagename)) {
126                 $curuser = $request->getUser();
127                 $curuserprefs = $curuser->getPreferences();
128                 $curuserprefsemail = $curuserprefs->get('email');
129                 $ownModifications = $curuserprefs->get('ownModifications');
130                 $majorModificationsOnly = $curuserprefs->get('majorModificationsOnly');
131
132                 foreach ($users as $userid => $user) {
133
134                     $usermail = $user['email'];
135
136                     if (($usermail == $curuserprefsemail)
137                         and ($ownModifications)
138                     ) {
139                         // It's my own modification
140                         // and I do not want to receive it
141                         continue;
142                     }
143
144                     if ($majorModificationsOnly) {
145                         $backend = &$request->_dbi->_backend;
146                         $version = $backend->get_latest_version($this->pagename);
147                         $versiondata = $backend->get_versiondata($this->pagename, $version, true);
148                         if ($versiondata['is_minor_edit']) {
149                             // It's a minor modification
150                             // and I do not want to receive it
151                             continue;
152                         }
153                     }
154
155                     if (!$user) { // handle the case for ModeratePage:
156                         // no prefs, just userid's.
157                         $emails[] = $this->userEmail($userid, false);
158                         $userids[] = $userid;
159                     } else {
160                         if (!empty($user['verified']) and !empty($user['email'])) {
161                             $emails[] = $user['email'];
162                             $userids[] = $userid;
163                         } elseif (!empty($user['email'])) {
164                             // do a dynamic emailVerified check update
165                             $email = $this->userEmail($userid, true);
166                             if ($email) {
167                                 $notify[$page][$userid]['verified'] = 1;
168                                 $request->_dbi->set('notify', $notify);
169                                 $emails[] = $email;
170                                 $userids[] = $userid;
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     /**
183      * @param string $subject Subject of the e-mail
184      * @param string $content Content of the e-mail
185      * @param bool $notice    Message used when triggering error
186      * @return bool           Return false in case of error
187      */
188     public function sendMail($subject, $content, $notice = false)
189     {
190         // Add WIKI_NAME to Subject
191         $subject = "[" . WIKI_NAME . "] " . $subject;
192         // Encode $subject if needed
193         $encoded_subject = $this->subject_encode($subject);
194         $emails = $this->emails;
195         // Do not send if modification is from FusionForge admin
196         if ((defined('FUSIONFORGE') && FUSIONFORGE) && ($this->fromId() == ADMIN_USER)) {
197             return true;
198         }
199         if (!$notice) {
200             $notice = _("PageChange Notification of %s");
201         }
202         $from = $this->fromEmail();
203         $headers = "From: $from\r\n" .
204             "Bcc: " . join(',', $emails) . "\r\n" .
205             "MIME-Version: 1.0\r\n" .
206             "Content-Type: text/plain; charset=UTF-8; format=flowed\r\n" .
207             "Content-Transfer-Encoding: 8bit";
208
209         $ok = mail(($to = array_shift($emails)),
210             $encoded_subject,
211             $subject . "\n" . $content,
212             $headers
213         );
214         if (MAILER_LOG and is_writable(MAILER_LOG)) {
215             global $ErrorManager;
216
217             $f = fopen(MAILER_LOG, "a");
218             fwrite($f, "\n\nX-MailSentOK: " . $ok ? 'OK' : 'FAILED');
219
220             if (!$ok && isset($ErrorManager->_postponed_errors[count($ErrorManager->_postponed_errors) - 1])) {
221                 // get last error message
222                 $last_err = $ErrorManager->_postponed_errors[count($ErrorManager->_postponed_errors) - 1];
223                 fwrite($f, "\nX-MailFailure: " .
224                     "errno: " . $last_err->errno . ", " .
225                     "errstr: " . $last_err->errstr . ", " .
226                     "errfile: " . $last_err->errfile . ", " .
227                     "errline: " . $last_err->errline);
228             }
229             fwrite($f, "\nDate: " . CTime());
230             fwrite($f, "\nSubject: $encoded_subject");
231             fwrite($f, "\nFrom: $from");
232             fwrite($f, "\nTo: $to");
233             fwrite($f, "\nBcc: " . join(',', $emails));
234             fwrite($f, "\n\n" . $content);
235             fclose($f);
236         }
237         if ($ok) {
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     private function sendPageChangeNotification(&$wikitext, $version, &$meta)
254     {
255
256         global $request;
257
258         if (isset($request->_deferredPageChangeNotification)) {
259             // collapse multiple changes (loaddir) into one email
260             $request->_deferredPageChangeNotification[] =
261                 array($this->pagename, $this->emails, $this->userids);
262             return;
263         }
264         $backend = &$request->_dbi->_backend;
265         $previous = $backend->get_previous_version($this->pagename, $version);
266         if (!isset($meta['mtime'])) {
267             $meta['mtime'] = time();
268         }
269         if ($previous) {
270             // Page existed, and was modified
271             $subject = _("Page change") . ' ' . ($this->pagename);
272             $difflink = WikiURL($this->pagename, array('action' => 'diff'), true);
273             $cache = &$request->_dbi->_cache;
274             $this_content = explode("\n", $wikitext);
275             $prevdata = $cache->get_versiondata($this->pagename, $previous, true);
276             if (empty($prevdata['%content'])) {
277                 $prevdata = $backend->get_versiondata($this->pagename, $previous, true);
278             }
279             $other_content = explode("\n", $prevdata['%content']);
280
281             include_once 'lib/difflib.php';
282             $diff2 = new Diff($other_content, $this_content);
283             //$context_lines = max(4, count($other_content) + 1,
284             //                     count($this_content) + 1);
285             $fmt = new UnifiedDiffFormatter( /*$context_lines*/);
286             $content = $this->pagename . " " . $previous . " " .
287                 Iso8601DateTime($prevdata['mtime']) . "\n";
288             $content .= $this->pagename . " " . $version . " " .
289                 Iso8601DateTime($meta['mtime']) . "\n";
290             $content .= $fmt->format($diff2);
291             $editedby = sprintf(_("Edited by: %s"), $this->fromId());
292         } else {
293             // Page did not exist, and was created
294             $subject = _("Page creation") . ' ' . ($this->pagename);
295             $difflink = WikiURL($this->pagename, array(), true);
296             $content = $this->pagename . " " . $version . " " .
297                 Iso8601DateTime($meta['mtime']) . "\n";
298             $content .= _("New page");
299             $content .= "\n\n";
300             $content .= $wikitext;
301             $editedby = sprintf(_("Created by: %s"), $this->fromId());
302         }
303         $summary = sprintf(_("Summary: %s"), $meta['summary']);
304         $this->sendMail($subject,
305             $editedby . "\n" . $summary . "\n" . $difflink . "\n\n" . $content);
306     }
307
308     /**
309      * Support mass rename / remove (TBD)
310      * @param string $to New page name
311      */
312     private function sendPageRenameNotification($to)
313     {
314         $pagename = $this->pagename;
315         $editedby = sprintf(_("Renamed by: %s"), $this->fromId());
316         $subject = sprintf(_("Page rename %s to %s"), $pagename, $to);
317         $link = WikiURL($to, true);
318         $this->sendMail($subject, $editedby . "\n" . $link . "\n\n" . $subject);
319     }
320
321     /*
322      * The handlers:
323      */
324
325     /**
326      * @param WikiDB $wikidb
327      * @param string $wikitext
328      * @param string $version
329      * @param array $meta
330      */
331     public function onChangePage(&$wikidb, &$wikitext, $version, &$meta)
332     {
333         $notify = $wikidb->get('notify');
334         /* Generate notification emails? */
335         if (!empty($notify) and is_array($notify)) {
336             if (empty($this->pagename))
337                 $this->pagename = $meta['pagename'];
338             // TODO: Should be used for ModeratePage and RSS2 Cloud xml-rpc also.
339             $this->getPageChangeEmails($notify);
340             if (!empty($this->emails)) {
341                 $this->sendPageChangeNotification($wikitext, $version, $meta);
342             }
343         }
344     }
345
346     /**
347      * @param WikiDB $wikidb
348      * @param string $pagename
349      * @return bool
350      */
351     public function onDeletePage(&$wikidb, $pagename)
352     {
353         $result = true;
354         /* Generate notification emails? */
355         if (!$wikidb->isWikiPage($pagename)) {
356             $notify = $wikidb->get('notify');
357             if (!empty($notify) and is_array($notify)) {
358                 //TODO: deferr it (quite a massive load if you remove some pages).
359                 $this->getPageChangeEmails($notify);
360                 if (!empty($this->emails)) {
361                     $subject = sprintf(_("User %s removed page %s"), $this->fromId(), $pagename);
362                     $result = $this->sendMail($subject, $subject . "\n\n");
363                 }
364             }
365         }
366         return $result;
367     }
368
369     /**
370      * @param WikiDB $wikidb
371      * @param string $oldpage
372      * @param string $new_pagename
373      */
374     public function onRenamePage(&$wikidb, $oldpage, $new_pagename)
375     {
376         $notify = $wikidb->get('notify');
377         if (!empty($notify) and is_array($notify)) {
378             $this->getPageChangeEmails($notify);
379             if (!empty($this->emails)) {
380                 $this->pagename = $oldpage;
381                 $this->sendPageRenameNotification($new_pagename);
382             }
383         }
384     }
385
386     private function subject_encode($subject)
387     {
388         // We need to encode the subject if it contains non-ASCII characters
389         // The page name may contain non-ASCII characters, as well as
390         // the translation of the messages, e.g. _("PageChange Notification of %s");
391
392         // If all characters are ASCII, do nothing
393         if (isAsciiString($subject)) {
394             return $subject;
395         }
396
397         // quoted_printable_encode inserts "\r\n" if line is too long, use "\n" only
398         return "=?UTF-8?Q?" . str_replace("\r\n", "\n", quoted_printable_encode($subject)) . "?=";
399     }
400 }
401
402 // Local Variables:
403 // mode: php
404 // tab-width: 8
405 // c-basic-offset: 4
406 // c-hanging-comment-ender-p: nil
407 // indent-tabs-mode: nil
408 // End: