]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/MailNotify.php
Patch 1677950 by Erwann Penet
[SourceForge/phpwiki.git] / lib / MailNotify.php
1 <?php //-*-php-*-
2 rcs_id('$Id: MailNotify.php,v 1.9 2007-03-10 18:22:51 rurban Exp $');
3
4 /**
5  * Handle the pagelist pref[notifyPages] logic for users
6  * and notify => hash ( page => (userid => userhash) ) for pages.
7  * Generate notification emails.
8  *
9  * We add WikiDB handlers and register ourself there:
10  *   onChangePage, onDeletePage, onRenamePage
11  * Administrative actions:
12  *   [Watch] WatchPage - add a page, or delete watch handlers into the users 
13  *                       pref[notifyPages] slot.
14  *   My WatchList      - view or edit list/regex of pref[notifyPages].
15  *   EMailConfirm methods: send and verify
16  *
17  * Helper functions:
18  *   getPageChangeEmails
19  *   MailAdmin
20  *   ? handle emailed confirmation links (EmailSignup, ModeratedPage)
21  *
22  * @package MailNotify
23  * @author  Reini Urban
24  */
25
26 if (!defined("MAILER_LOG"))
27     if (isWindows())
28         define("MAILER_LOG", 'c:/wikimail.log');
29     else
30         define("MAILER_LOG", '/var/log/wikimail.log');
31
32 class MailNotify {
33
34     function MailNotify($pagename) {
35         $this->pagename = $pagename; /* which page */
36         $this->emails  = array();    /* to which addresses */
37         $this->userids = array();    /* corresponding array of displayed names, 
38                                         don't display the email addresses */
39         /* From: from whom the mail appears to be */
40         $this->from = $this->fromId();
41     }
42
43     function fromId() {
44         global $request;
45         return $request->_user->getId() . '@' .  $request->get('REMOTE_HOST');
46     }
47
48     function userEmail($userid, $doverify = true) {
49         global $request;
50         $u = $request->getUser();
51         if ($u->UserName() == $userid) { // lucky: current user
52             $prefs = $u->getPreferences();
53             $email = $prefs->get('email');
54             // do a dynamic emailVerified check update
55             if ($doverify and !$request->_prefs->get('emailVerified'))
56                 $email = '';
57         } else {  // not current user
58             if (ENABLE_USER_NEW) {
59                 $u = WikiUser($userid);
60                 $u->getPreferences();
61                 $prefs = &$u->_prefs;
62             } else {
63                 $u = new WikiUser($request, $userid);
64                 $prefs = $u->getPreferences();
65             }
66             $email = $prefs->get('email');
67             if ($doverify and !$prefs->get('emailVerified')) {
68                 $email = '';
69             }
70         }
71         return $email;
72     }
73
74     /**
75      * getPageChangeEmails($notify)
76      * @param  $notify: hash ( page => (userid => userhash) )
77      * @return array
78      *         unique array of ($emails, $userids)
79      */
80     function getPageChangeEmails($notify) {
81         global $request;
82         $emails = array(); $userids = array();
83         foreach ($notify as $page => $users) {
84             if (glob_match($page, $this->pagename)) {
85                 foreach ($users as $userid => $user) {
86                     if (!$user) { // handle the case for ModeratePage: 
87                                   // no prefs, just userid's.
88                         $emails[] = $this->userEmail($userid, false);
89                         $userids[] = $userid;
90                     } else {
91                         if (!empty($user['verified']) and !empty($user['email'])) {
92                             $emails[]  = $user['email'];
93                             $userids[] = $userid;
94                         } elseif (!empty($user['email'])) {
95                             // do a dynamic emailVerified check update
96                             $email = $this->userEmail($userid, true);
97                             if ($email) {
98                                 $notify[$page][$userid]['verified'] = 1;
99                                 $request->_dbi->set('notify', $notify);
100                                 $emails[] = $email;
101                                 $userids[] = $userid;
102                             }
103                         }
104                         // ignore verification
105                         /*
106                         if (DEBUG) {
107                             if (!in_array($user['email'], $emails))
108                                 $emails[] = $user['email'];
109                         }
110                         */
111                     }
112                 }
113             }
114         }
115         $this->emails = array_unique($emails);
116         $this->userids = array_unique($userids);
117         return array($this->emails, $this->userids);
118     }
119     
120     function sendMail($subject, $content, 
121                       $notice = false,
122                       $silent = false)
123     {
124         global $request;
125         $emails = $this->emails;
126         $from = $this->from;
127         if (!$notice) $notice = _("PageChange Notification of %s");
128         $ok = mail(($to = array_shift($emails)),
129                  "[".WIKI_NAME."] ".$subject, 
130                    $subject."\n".$content,
131                    "From: $from\r\nBcc: ".join(',', $emails)
132                    );
133         if (MAILER_LOG and is_writable(MAILER_LOG)) {
134             $f = fopen(MAILER_LOG, "a");
135             fwrite($f, "\n\nX-MailSentOK: " . $ok ? 'OK' : 'FAILED');
136             if (!$ok) {
137                 global $ErrorManager;
138                 // get last error message
139                 $last_err = $ErrorManager->_postponed_errors[count($ErrorHandler->_postponed_errors)-1];
140                 fwrite($f, "\nX-MailFailure: " . $last_err);
141             }
142             fwrite($f, "\nDate: " . CTime());
143             fwrite($f, "\nSubject: $subject");
144             fwrite($f, "\nFrom: $from");
145             fwrite($f, "\nTo: $to");
146             fwrite($f, "\nBcc: ".join(',', $emails));
147             fwrite($f, "\n\n". $content);
148             fclose($f);
149         }
150         if ($ok) {
151             if (!$silent)
152                 trigger_error(sprintf($notice, $this->pagename)
153                               . " "
154                               . sprintf(_("sent to %s"), join(',',$this->userids)),
155                               E_USER_NOTICE);
156             return true;
157         } else {
158             trigger_error(sprintf($notice, $this->pagename)
159                           . " "
160                           . sprintf(_("Error: Couldn't send %s to %s"), 
161                                    $subject."\n".$content, join(',',$this->userids)), 
162                           E_USER_WARNING);
163             return false;
164         }
165     }
166     
167     /**
168      * Send udiff for a changed page to multiple users.
169      * See rename and remove methods also
170      */
171     function sendPageChangeNotification(&$wikitext, $version, &$meta) {
172
173         global $request;
174
175         if (@is_array($request->_deferredPageChangeNotification)) {
176             // collapse multiple changes (loaddir) into one email
177             $request->_deferredPageChangeNotification[] = 
178                 array($this->pagename, $this->emails, $this->userids);
179             return;
180         }
181         $backend = &$request->_dbi->_backend;
182         $subject = _("Page change").' '.urlencode($this->pagename);
183         $previous = $backend->get_previous_version($this->pagename, $version);
184         if (!isset($meta['mtime'])) $meta['mtime'] = time();
185         if ($previous) {
186             $difflink = WikiURL($this->pagename, array('action'=>'diff'), true);
187             $dbh = &$request->getDbh();
188             $cache = &$dbh->_wikidb->_cache;
189             //$cache = &$request->_dbi->_cache;
190             $this_content = explode("\n", $wikitext);
191             $prevdata = $cache->get_versiondata($this->pagename, $previous, true);
192             if (empty($prevdata['%content']))
193                 $prevdata = $backend->get_versiondata($this->pagename, $previous, true);
194             $other_content = explode("\n", $prevdata['%content']);
195             
196             include_once("lib/difflib.php");
197             $diff2 = new Diff($other_content, $this_content);
198             //$context_lines = max(4, count($other_content) + 1,
199             //                     count($this_content) + 1);
200             $fmt = new UnifiedDiffFormatter(/*$context_lines*/);
201             $content  = $this->pagename . " " . $previous . " " . 
202                 Iso8601DateTime($prevdata['mtime']) . "\n";
203             $content .= $this->pagename . " " . $version . " " .  
204                 Iso8601DateTime($meta['mtime']) . "\n";
205             $content .= $fmt->format($diff2);
206             
207         } else {
208             $difflink = WikiURL($this->pagename,array(),true);
209             $content = $this->pagename . " " . $version . " " .  
210                 Iso8601DateTime($meta['mtime']) . "\n";
211             $content .= _("New page");
212         }
213         $editedby = sprintf(_("Edited by: %s"), $this->from);
214         //$editedby = sprintf(_("Edited by: %s"), $meta['author']);
215         $this->sendMail($subject, 
216                         $editedby."\n".$difflink."\n\n".$content);
217     }
218
219     /** 
220      * support mass rename / remove (not yet tested)
221      */
222     function sendPageRenameNotification ($to, &$meta) {
223         global $request;
224
225         if (@is_array($request->_deferredPageRenameNotification)) {
226             $request->_deferredPageRenameNotification[] = 
227                 array($this->pagename, $to, $meta, $this->emails, $this->userids);
228         } else {
229             $pagename = $this->pagename;
230             //$editedby = sprintf(_("Edited by: %s"), $meta['author']) . ' ' . $meta['author_id'];
231             $editedby = sprintf(_("Edited by: %s"), $this->from);
232             $subject = sprintf(_("Page rename %s to %s"), urlencode($pagename), urlencode($to));
233             $link = WikiURL($to, true);
234             $this->sendMail($subject, 
235                             $editedby."\n".$link."\n\n"."Renamed $pagename to $to");
236         }
237     }
238
239     /**
240      * The handlers:
241      */
242     function onChangePage (&$wikidb, &$wikitext, $version, &$meta) {
243         $result = true;
244         if (!isa($GLOBALS['request'],'MockRequest')) {
245             $notify = $wikidb->get('notify');
246             /* Generate notification emails? */
247             if (!empty($notify) and is_array($notify)) {
248                 if (empty($this->pagename))
249                     $this->pagename = $meta['pagename'];
250                 //TODO: defer it (quite a massive load if you MassRevert some pages).
251                 //TODO: notification class which catches all changes,
252                 //  and decides at the end of the request what to mail. 
253                 //  (type, page, who, what, users, emails)
254                 // Could be used for ModeratePage and RSS2 Cloud xml-rpc also.
255                 $this->getPageChangeEmails($notify);
256                 if (!empty($this->emails)) {
257                     $result = $this->sendPageChangeNotification($wikitext, $version, $meta);
258                 }
259             }
260         }
261         return $result;
262     }
263
264     function onDeletePage (&$wikidb, $pagename) {
265         $result = true;
266         /* Generate notification emails? */
267         if (! $wikidb->isWikiPage($pagename) and !isa($GLOBALS['request'],'MockRequest')) {
268             $notify = $wikidb->get('notify');
269             if (!empty($notify) and is_array($notify)) {
270                 //TODO: deferr it (quite a massive load if you remove some pages).
271                 $this->getPageChangeEmails($notify);
272                 if (!empty($this->emails)) {
273                     $editedby = sprintf(_("Removed by: %s"), $this->from); // Todo: host_id
274                     //$emails = join(',', $this->emails);
275                     $subject = sprintf(_("Page removed %s"), urlencode($pagename));
276                     $page = $wikidb->getPage($pagename);
277                     $rev = $page->getCurrentRevision(true);
278                     $content = $rev->getPackedContent();
279                     $result = $this->sendMail($subject, 
280                                               $editedby."\n"."Deleted $pagename"."\n\n".$content);
281                 }
282             }
283         }
284         //How to create a RecentChanges entry with explaining summary? Dynamically
285         /*
286           $page = $this->getPage($pagename);
287           $current = $page->getCurrentRevision();
288           $meta = $current->_data;
289           $version = $current->getVersion();
290           $meta['summary'] = _("removed");
291           $page->save($current->getPackedContent(), $version + 1, $meta);
292         */
293         return $result;
294     }
295
296     function onRenamePage (&$wikidb, $oldpage, $new_pagename) {
297         $result = true;
298         if (!isa($GLOBALS['request'], 'MockRequest')) {
299             $notify = $wikidb->get('notify');
300             if (!empty($notify) and is_array($notify)) {
301                 $this->getPageChangeEmails($notify);
302                 if (!empty($this->emails)) {
303                     $newpage = $wikidb->getPage($new_pagename);
304                     $current = $newpage->getCurrentRevision();
305                     $meta = $current->_data;
306                     $this->pagename = $oldpage;
307                     $result = $this->sendPageRenameNotification($new_pagename, $meta);
308                 }
309             }
310         }
311     }
312
313     /**
314      * Send mail to user and store the cookie in the db
315      * wikiurl?action=ConfirmEmail&id=bla
316      */
317     function sendEmailConfirmation ($email, $userid) {
318         $id = rand_ascii_readable(16);
319         $wikidb = $GLOBALS['request']->getDbh();
320         $data = $wikidb->get('ConfirmEmail');
321         while(!empty($data[$id])) { // id collision
322             $id = rand_ascii_readable(16);
323         }
324         $subject = WIKI_NAME . " " . _("e-mail address confirmation");
325         $ip = $request->get('REMOTE_HOST');
326         $expire_date = time() + 7*86400;
327         $content = fmt("Someone, probably you from IP address %s, has registered an
328 account \"%s\" with this e-mail address on %s.
329
330 To confirm that this account really does belong to you and activate
331 e-mail features on %s, open this link in your browser:
332
333 %s
334
335 If this is *not* you, don't follow the link. This confirmation code
336 will expire at %s.", 
337                        $ip, $userid, WIKI_NAME, WIKI_NAME, 
338                        WikiURL(HOME_PAGE, array('action' => 'ConfirmEmail',
339                                                 'id' => $id), 
340                                true),
341                        CTime($expire_date));
342         $this->sendMail($subject, $content, "", true);
343         $data[$id] = array('email' => $email,
344                            'userid' => $userid,
345                            'expire' => $expire_date);
346         $wikidb->set('ConfirmEmail', $data);
347         return '';
348     }
349
350     function checkEmailConfirmation () {
351         global $request;
352         $wikidb = $request->getDbh();
353         $data = $wikidb->get('ConfirmEmail');
354         $id = $request->getArg('id');
355         if (empty($data[$id])) { // id not found
356             return HTML(HTML::h1("Confirm E-mail address"),
357                         HTML::h1("Sorry! Wrong URL"));
358         }
359         // upgrade the user
360         $userid = $data['userid'];
361         $email = $data['email'];
362         $u = $request->getUser();
363         if ($u->UserName() == $userid) { // lucky: current user (session)
364             $prefs = $u->getPreferences();
365             $request->_user->_level = WIKIAUTH_USER;
366             $request->_prefs->set('emailVerified', true);
367         } else {  // not current user
368             if (ENABLE_USER_NEW) {
369                 $u = WikiUser($userid);
370                 $u->getPreferences();
371                 $prefs = &$u->_prefs;
372             } else {
373                 $u = new WikiUser($request, $userid);
374                 $prefs = $u->getPreferences();
375             }
376             $u->_level = WIKIAUTH_USER;
377             $request->setUser($u);
378             $request->_prefs->set('emailVerified', true);
379         }
380         unset($data[$id]);
381         $wikidb->set('ConfirmEmail', $data);
382         return HTML(HTML::h1("Confirm E-mail address"),
383                     HTML::p("Your e-mail address has now been confirmed."));
384     }
385 }
386
387
388 // $Log: not supported by cvs2svn $
389 // Revision 1.8  2007/01/25 07:41:54  rurban
390 // Add wikimail.log default on unix
391 //
392 // Revision 1.7  2007/01/20 11:25:38  rurban
393 // Fix onDeletePage warning and content
394 //
395 // Revision 1.6  2007/01/09 12:34:55  rurban
396 // Fix typo (syntax error)
397 //
398 // Revision 1.5  2007/01/07 18:42:58  rurban
399 // Add MAILER_LOG logfile
400 //
401 // Revision 1.4  2007/01/04 16:47:49  rurban
402 // improve text
403 //
404 // Revision 1.3  2006/12/24 13:35:43  rurban
405 // added experimental EMailConfirm auth. (not yet tested)
406 // requires actionpage ConfirmEmail
407 // TBD: purge expired cookies
408 //
409 // Revision 1.2  2006/12/23 11:50:45  rurban
410 // added missing result init
411 //
412 // Revision 1.1  2006/12/22 17:59:55  rurban
413 // Move mailer functions into seperate MailNotify.php
414 //
415
416 // Local Variables:
417 // mode: php
418 // tab-width: 8
419 // c-basic-offset: 4
420 // c-hanging-comment-ender-p: nil
421 // indent-tabs-mode: nil
422 // End:   
423 ?>