3 /* Copyright (C) 2006-2007,2009 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 along
19 * with PhpWiki; if not, write to the Free Software Foundation, Inc.,
20 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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].
38 * ? handle emailed confirmation links (EmailSignup, ModeratedPage)
44 if (!defined("MAILER_LOG")) {
46 define("MAILER_LOG", 'c:/wikimail.log');
48 define("MAILER_LOG", '/var/log/wikimail.log');
55 function __construct($pagename)
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();
65 private function fromId()
68 if (defined('FUSIONFORGE') && FUSIONFORGE) {
69 return $request->_user->getId();
71 return $request->_user->getId() . '@' . $request->get('REMOTE_HOST');
75 private function fromEmail()
78 return $this->userEmail($request->_user->getId(), false);
81 private function userEmail($userid, $doverify = true)
85 // Disable verification of emails for corporate env.
86 if (defined('FUSIONFORGE') && FUSIONFORGE) {
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')) {
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')) {
111 * getPageChangeEmails
112 * @param array $notify hash ( page => (userid => userhash) )
113 * @return array unique array of ($emails, $userids)
115 public function getPageChangeEmails($notify)
118 * @var WikiRequest $request
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');
132 foreach ($users as $userid => $user) {
134 $usermail = $user['email'];
136 if (($usermail == $curuserprefsemail)
137 and ($ownModifications)
139 // It's my own modification
140 // and I do not want to receive it
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
155 if (!$user) { // handle the case for ModeratePage:
156 // no prefs, just userid's.
157 $emails[] = $this->userEmail($userid, false);
158 $userids[] = $userid;
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);
167 $notify[$page][$userid]['verified'] = 1;
168 $request->_dbi->set('notify', $notify);
170 $userids[] = $userid;
177 $this->emails = array_unique($emails);
178 $this->userids = array_unique($userids);
179 return array($this->emails, $this->userids);
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
188 public function sendMail($subject, $content, $notice = false)
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)) {
200 $notice = _("PageChange Notification of %s");
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";
209 $ok = mail(($to = array_shift($emails)),
211 $subject . "\n" . $content,
214 if (MAILER_LOG and is_writable(MAILER_LOG)) {
215 global $ErrorManager;
217 $f = fopen(MAILER_LOG, "a");
218 fwrite($f, "\n\nX-MailSentOK: " . $ok ? 'OK' : 'FAILED');
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);
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);
240 trigger_error(sprintf($notice, $this->pagename)
242 . sprintf(_("Error: Couldn't send %s to %s"),
243 $subject . "\n" . $content, join(',', $this->userids)),
250 * Send udiff for a changed page to multiple users.
251 * See rename and remove methods also
253 private function sendPageChangeNotification(&$wikitext, $version, &$meta)
258 if (isset($request->_deferredPageChangeNotification)) {
259 // collapse multiple changes (loaddir) into one email
260 $request->_deferredPageChangeNotification[] =
261 array($this->pagename, $this->emails, $this->userids);
264 $backend = &$request->_dbi->_backend;
265 $previous = $backend->get_previous_version($this->pagename, $version);
266 if (!isset($meta['mtime'])) {
267 $meta['mtime'] = time();
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);
279 $other_content = explode("\n", $prevdata['%content']);
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());
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");
300 $content .= $wikitext;
301 $editedby = sprintf(_("Created by: %s"), $this->fromId());
303 $summary = sprintf(_("Summary: %s"), $meta['summary']);
304 $this->sendMail($subject,
305 $editedby . "\n" . $summary . "\n" . $difflink . "\n\n" . $content);
309 * Support mass rename / remove (TBD)
310 * @param string $to New page name
312 private function sendPageRenameNotification($to)
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);
326 * @param WikiDB $wikidb
327 * @param string $wikitext
328 * @param string $version
331 public function onChangePage(&$wikidb, &$wikitext, $version, &$meta)
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);
347 * @param WikiDB $wikidb
348 * @param string $pagename
351 public function onDeletePage(&$wikidb, $pagename)
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");
370 * @param WikiDB $wikidb
371 * @param string $oldpage
372 * @param string $new_pagename
374 public function onRenamePage(&$wikidb, $oldpage, $new_pagename)
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);
386 private function subject_encode($subject)
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");
392 // If all characters are ASCII, do nothing
393 if (isAsciiString($subject)) {
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)) . "?=";
406 // c-hanging-comment-ender-p: nil
407 // indent-tabs-mode: nil