* Someone forgot to global the variable
[lhc/web/wiklou.git] / includes / UserMailer.php
1 <?php
2 /**
3 * UserMailer.php
4 * Copyright (C) 2004 Thomas Gries <mail@tgries.de>
5 * http://www.mediawiki.org/
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20 * http://www.gnu.org/copyleft/gpl.html
21 *
22 * @author <brion@pobox.com>
23 * @author <mail@tgries.de>
24 *
25 * @package MediaWiki
26 */
27
28 require_once( 'WikiError.php' );
29
30 /**
31 * Provide mail capabilities
32 * @param string $string ????
33 *
34 * The function takes formats like "name <emailaddr>" into account, for which
35 * the partial string "name" will be quoted-printable converted but not
36 * "<emailaddr>".
37 *
38 * The php mail() function does not accept the email address partial string to
39 * be quotedprintable, it does not accept inputs as
40 * quotedprintable("name <emailaddr>") .
41 *
42 * case 1:
43 * input: "name <emailaddr> rest"
44 * output: "quoted-printable(name) <emailaddr>"
45 *
46 * case 2: (should not happen)
47 * input: "<emailaddr> rest"
48 * output: "<emailaddr>"
49 *
50 * case 3: (should not happen)
51 * input: "name"
52 * output: "quoted-printable(name)"
53 */
54 function wfQuotedPrintable_name_and_emailaddr( $string ) {
55
56 /* do not quote printable for email address string <emailaddr>, but only for the (leading) string, usually the name */
57 if( preg_match( '/^([^<]*)?(<([A-z0-9_.-]+([A-z0-9_.-]+)*\@[A-z0-9_-]+([A-z0-9_.-]+)*([A-z.]{2,})+)>)?$/', $string, $part ) ) {
58 wfDebug( "wfQuotedPrintable_name_and_emailaddr: '$string' " . serialize( $part ) . "\n" );
59 if ( !isset($part[2]) ) return wfQuotedprintable($part[1]);
60 return wfQuotedprintable($part[1]) . $part[2] ;
61 } else {
62 # What have we been given??
63 wfDebug( "wfQuotedPrintable_name_and_emailaddr: got confused by '$string'\n" );
64 return wfQuotedprintable( $string );
65 }
66 }
67
68 /**
69 * This function will perform a direct (authenticated) login to
70 * a SMTP Server to use for mail relaying if 'wgSMTP' specifies an
71 * array of parameters. It requires PEAR:Mail to do that.
72 * Otherwise it just uses the standard PHP 'mail' function.
73 *
74 * @param string $to recipient's email
75 * @param string $from sender's email
76 * @param string $subject email's subject
77 * @param string $body email's text
78 * @param string $replyto optional reply-to email (default : false)
79 */
80 function userMailer( $to, $from, $subject, $body, $replyto=false ) {
81 global $wgUser, $wgSMTP, $wgOutputEncoding, $wgErrorString, $wgEmergencyContact;
82
83 $qto = wfQuotedPrintable_name_and_emailaddr( $to );
84
85 if (is_array( $wgSMTP )) {
86 require_once( 'Mail.php' );
87
88 $timestamp = time();
89
90 $headers['From'] = $from;
91 /* removing to: field as it should be set by the send() function below
92 UNTESTED - Hashar */
93 // $headers['To'] = $qto;
94 /* Reply-To:
95 UNTESTED - Tom Gries */
96 $headers['Reply-To'] = $replyto;
97 $headers['Subject'] = $subject;
98 $headers['MIME-Version'] = '1.0';
99 $headers['Content-type'] = 'text/plain; charset='.$wgOutputEncoding;
100 $headers['Content-transfer-encoding'] = '8bit';
101 $headers['Message-ID'] = "<{$timestamp}" . $wgUser->getName() . '@' . $wgSMTP['IDHost'] . '>';
102 $headers['X-Mailer'] = 'MediaWiki mailer';
103
104 // Create the mail object using the Mail::factory method
105 $mail_object =& Mail::factory('smtp', $wgSMTP);
106
107 $mailResult =& $mail_object->send($to, $headers, $body);
108
109 # Based on the result return an error string,
110 if ($mailResult === true)
111 return '';
112 else if (is_object($mailResult))
113 return $mailResult->getMessage();
114 else
115 return 'Mail object return unknown error.';
116 } else {
117 # In the following $headers = expression we removed "Reply-To: {$from}\r\n" , because it is treated differently
118 # (fifth parameter of the PHP mail function, see some lines below)
119 $headers =
120 "MIME-Version: 1.0\n" .
121 "Content-type: text/plain; charset={$wgOutputEncoding}\n" .
122 "Content-Transfer-Encoding: 8bit\n" .
123 "X-Mailer: MediaWiki mailer\n".
124 'From:' . wfQuotedPrintable_name_and_emailaddr($from) . "\n";
125 if ($replyto) {
126 $headers .= 'Reply-To: '.wfQuotedPrintable_name_and_emailaddr($replyto)."\n";
127 }
128
129 $wgErrorString = '';
130 set_error_handler( 'mailErrorHandler' );
131 # added -f parameter, see PHP manual for the fifth parameter when using the mail function
132 mail( wfQuotedPrintable_name_and_emailaddr($to), $subject, $body, $headers, "-f{$wgEmergencyContact}\n");
133 restore_error_handler();
134
135 return $wgErrorString;
136 }
137 }
138
139 /**
140 * @todo document
141 */
142 function mailErrorHandler( $code, $string ) {
143 global $wgErrorString;
144 $wgErrorString = preg_replace( "/^mail\(\): /", "", $string );
145 }
146
147
148 /**
149 * This module processes the email notifications when the current page is
150 * changed. It looks up the table watchlist to find out which users are watching
151 * that page.
152 *
153 * The current implementation sends independent emails to each watching user for
154 * the following reason:
155 *
156 * - Each watching user will be notified about the page edit time expressed in
157 * his/her local time (UTC is shown additionally). To achieve this, we need to
158 * find the individual timeoffset of each watching user from the preferences..
159 *
160 * Suggested improvement to slack down the number of sent emails: We could think
161 * of sending out bulk mails (bcc:user1,user2...) for all these users having the
162 * same timeoffset in their preferences.
163 *
164 * Visit the documentation pages under http://meta.wikipedia.com/Enotif
165 *
166 * @package MediaWiki
167 *
168 */
169 class EmailNotification {
170 /**#@+
171 * @access private
172 */
173 var $to, $subject, $body, $replyto, $from;
174 /**#@-*/
175
176 /**
177 * @todo document
178 * @param $currentUser
179 * @param $currentPage
180 * @param $currentNs
181 * @param $timestamp
182 * @param $currentSummary
183 * @param $currentMinorEdit
184 * @param $oldid (default: false)
185 */
186 function NotifyOnPageChange($currentUser, $currentPage, $currentNs, $timestamp, $currentSummary, $currentMinorEdit, $oldid=false) {
187
188 # we use $wgEmergencyContact as sender's address
189 global $wgUser, $wgLang, $wgEmergencyContact;
190 global $wgEmailNotificationForWatchlistPages, $wgEmailNotificationForMinorEdits;
191 global $wgEmailNotificationSystembeep, $wgEmailNotificationForUserTalkPages;
192 global $wgEmailNotificationRevealPageEditorAddress;
193 global $wgEmailNotificationMailsSentFromPageEditor;
194 global $wgEmailAuthentication;
195 global $beeped;
196
197 # The following code is only run, if several conditions are met:
198 # 1. EmailNotification for pages (other than user_talk pages) must be enabled
199 # 2. minor edits (changes) are only regarded if the global flag indicates so
200
201 $isUserTalkPage = ($currentNs == NS_USER_TALK);
202 $enotifusertalkpage = ($isUserTalkPage && $wgEmailNotificationForUserTalkPages);
203 $enotifwatchlistpage = (!$isUserTalkPage && $wgEmailNotificationForWatchlistPages);
204
205 if ( ($enotifusertalkpage || $enotifwatchlistpage)
206 && (!$currentMinorEdit || $wgEmailNotificationForMinorEdits) ) {
207
208 $dbr =& wfGetDB( DB_MASTER );
209 extract( $dbr->tableNames( 'watchlist' ) );
210 $sql = "SELECT wl_user FROM $watchlist
211 WHERE wl_title='" . $dbr->strencode($currentPage)."' AND wl_namespace = " . $currentNs .
212 " AND wl_user <>" . $currentUser . " AND wl_notificationtimestamp <= 1";
213 $res = $dbr->query( $sql,'UserMailer::NotifyOnChange');
214
215 # if anyone is watching ... set up the email message text which is
216 # common for all receipients ...
217 if ( $dbr->numRows( $res ) > 0 ) {
218
219 # This is a switch for one beep on the server when sending notification mails
220 $beeped = false;
221
222 $article->mTimestamp = $timestamp;
223 $article->mSummary = $currentSummary;
224 $article->mMinorEdit = $currentMinorEdit;
225 $article->mNamespace = $currentNs;
226 $article->mTitle = $currentPage;
227 $article->mOldid = $oldid;
228
229 $mail = $this->composeCommonMailtext( $wgUser, $article );
230 $watchingUser = new User();
231
232 # ... now do for all watching users ... if the options fit
233 for ($i = 1; $i <= $dbr->numRows( $res ); $i++) {
234
235 $wuser = $dbr->fetchObject( $res );
236 $watchingUser->setID($wuser->wl_user);
237 if ( ( $enotifwatchlistpage && $watchingUser->getOption('enotifwatchlistpages') ) ||
238 ( $enotifusertalkpage && $watchingUser->getOption('enotifusertalkpages') )
239 && (!$currentMinorEdit || ($wgEmailNotificationForMinorEdits && $watchingUser->getOption('enotifminoredits') ) )
240 && ($watchingUser->isEmailConfirmed() ) ) {
241 # ... adjust remaining text and page edit time placeholders
242 # which needs to be personalized for each user
243 $sent = $this->composeAndSendPersonalisedMail( $watchingUser, $mail, $article );
244 /* the beep here beeps once when a watched-listed page is changed */
245 if ($sent && !$beeped && ($wgEmailNotificationSystembeep != '') ) {
246 $last_line = system($wgEmailNotificationSystembeep);
247 $beeped=true;
248 }
249 } # if the watching user has an email address in the preferences
250 # mark the changed watch-listed page with a timestamp, so that the page is listed with an "updated since your last visit" icon in the watch list, ...
251 # ... no matter, if the watching user has or has not indicated an email address in his/her preferences.
252 # We memorise the event of sending out a notification and use this as a flag to suppress further mails for changes on the same page for that watching user
253 $dbw =& wfGetDB( DB_MASTER );
254 $succes = $dbw->update( 'watchlist',
255 array( /* SET */
256 'wl_notificationtimestamp' => $article->mTimestamp
257 ), array( /* WHERE */
258 'wl_title' => $currentPage,
259 'wl_namespace' => $currentNs,
260 'wl_user' => $wuser->wl_user
261 ), 'UserMailer::NotifyOnChange'
262 );
263 } # for every watching user
264 } # if anyone is watching
265 } # if $wgEmailNotificationForWatchlistPages = true
266 } # function NotifyOnChange
267
268 /**
269 * @param User $pageeditorUser
270 * @param Article $article
271 * @access private
272 */
273 function composeCommonMailtext( $pageeditorUser, $article ) {
274 global $wgLang, $wgEmergencyContact;
275 global $wgEmailNotificationRevealPageEditorAddress;
276 global $wgEmailNotificationMailsSentFromPageEditor;
277 global $wgNoReplyAddress;
278
279 $summary = ($article->mSummary == '') ? ' - ' : $article->mSummary;
280 $medit = ($article->mMinorEdit) ? wfMsg( 'minoredit' ) : '';
281
282 # You as the WikiAdmin and Sysops can make use of plenty of
283 # named variables when composing your notification emails while
284 # simply editing the Meta pages
285
286 $to = wfMsg( 'email_notification_to' );
287 $subject = wfMsg( 'email_notification_subject' );
288 $body = wfMsg( 'email_notification_body' );
289 $from = ''; /* fail safe */
290 $replyto = ''; /* fail safe */
291 $keys = array();
292
293 # regarding the use of oldid as an indicator for the last visited version, see also
294 # http://bugzilla.wikipeda.org/show_bug.cgi?id=603 "Delete + undelete cycle doesn't preserve old_id"
295 # However, in the case of a new page which is already watched, we have no previous version to compare
296 if( $article->mOldid ) {
297 $keys['$NEWPAGE'] = wfMsg( 'email_notification_lastvisitedrevisiontext' );
298 $keys['$OLDID'] = $article->mOldid;
299 } else {
300 $keys['$NEWPAGE'] = wfMsg( 'email_notification_newpagetext' );
301 # clear $OLDID placeholder in the message template
302 $keys['$OLDID'] = '';
303 }
304
305 $body = strtr( $body, $keys );
306
307 $pagetitle = $article->mTitle;
308 if( $article->mNamespace != NS_MAIN ) {
309 $pagetitle = $wgLang->getNsText( $article->mNamespace ) . ':' . $pagetitle;
310 }
311 $subject = str_replace( '$PAGETITLE', str_replace( '_', ' ', $pagetitle ) , $subject);
312 $keys['%24PAGETITLE_RAWURL'] = wfUrlencode( $pagetitle );
313 $keys['$PAGETITLE_RAWURL'] = wfUrlencode( $pagetitle );
314 $keys['%24PAGETITLE'] = $pagetitle; # needed for the {{localurl:$PAGETITLE}} in the messagetext, "$" appears here as "%24"
315 $keys['$PAGETITLE'] = $pagetitle;
316 $keys['$PAGETIMESTAMP'] = $article->mTimestamp; # this is the raw internal timestamp - can be useful, too
317 $keys['$PAGEEDITDATEUTC'] = $wgLang->timeanddate( $article->mTimestamp, false, false, false );
318 $keys['$PAGEMINOREDIT'] = $medit;
319 $keys['$PAGESUMMARY'] = $summary;
320
321
322 # Reveal the page editor's address as REPLY-TO address only if
323 # the user has not opted-out and the option is enabled at the
324 # global configuration level.
325 $name = $pageeditorUser->getName();
326 $adminAddress = 'WikiAdmin <' . $wgEmergencyContact . '>';
327 $editorAddress = $name . ' <' . $pageeditorUser->getEmail() . '>';
328 if( $wgEmailNotificationRevealPageEditorAddress
329 && ( $pageeditorUser->getEmail() != '' )
330 && $pageeditorUser->getOption( 'enotifrevealaddr' ) ) {
331 if( $wgEmailNotificationMailsSentFromPageEditor ) {
332 $from = $editorAddress;
333 } else {
334 $from = $adminAddress;
335 $replyto = $editorAddress;
336 }
337 $keys['$PAGEEDITORNAMEANDEMAILADDR'] = $editorAddress;
338 } else {
339 $from = $adminAddress;
340 $replyto = $wgNoReplyAddress;
341 $keys['$PAGEEDITORNAMEANDEMAILADDR'] = $replyto;
342 }
343
344 if( $pageeditorUser->isIP( $name ) ) {
345 #real anon (user:xxx.xxx.xxx.xxx)
346 $anon = $name . ' (anonymous user)';
347 $anonUrl = wfUrlencode( $name ) . ' (anonymous user)';
348 $subject = str_replace('$PAGEEDITOR', 'anonymous user '. $name, $subject);
349
350 $keys['$PAGEEDITOR_RAWURL'] = $anonUrl;
351 $keys['%24PAGEEDITOR_RAWURL'] = $anonUrl;
352 $keys['%24PAGEEDITORE'] = $anon;
353 $keys['$PAGEEDITORE'] = $anon;
354 $keys['$PAGEEDITOR'] = 'anonymous user ' . $name;
355 } else {
356 $subject = str_replace('$PAGEEDITOR', $name, $subject);
357 $keys['$PAGEEDITOR_RAWURL'] = wfUrlencode( $name );
358 $keys['%24PAGEEDITOR_RAWURL'] = wfUrlencode( $name );
359 $keys['%24PAGEEDITORE'] = $pageeditorUser->getTitleKey();
360 $keys['$PAGEEDITORE'] = $pageeditorUser->getTitleKey();
361 $keys['$PAGEEDITOR'] = $name;
362 }
363 $body = strtr( $body, $keys );
364
365 # now save this as the constant user-independent part of the message
366 $this->to = $to;
367 $this->from = $from;
368 $this->replyto = $replyto;
369 $this->subject = wfQuotedprintable($subject);
370 $this->body = $body;
371 return $this;
372 }
373
374
375
376 /**
377 * Does the per-user customizations to a notification e-mail (name,
378 * timestamp in proper timezone, etc) and sends it out.
379 * Returns true if the mail was sent successfully.
380 *
381 * @param User $watchingUser
382 * @param object $mail
383 * @param Article $article
384 * @return bool
385 * @access private
386 */
387 function composeAndSendPersonalisedMail( $watchingUser, $mail, $article ) {
388 global $wgLang;
389
390 $to = $watchingUser->getName() . ' <' . $watchingUser->getEmail() . '>';
391 $body = str_replace( '$WATCHINGUSERNAME', $watchingUser->getName() , $mail->body );
392 $body = str_replace( '$WATCHINGUSEREMAILADDR', $watchingUser->getEmail(), $body );
393
394 $timecorrection = $watchingUser->getOption( 'timecorrection' );
395 if( !$timecorrection ) {
396 # fail safe - I prefer it. TomGries
397 $timecorrection = '00:00';
398 }
399 # $PAGEEDITDATE is the time and date of the page change
400 # expressed in terms of individual local time of the notification
401 # recipient, i.e. watching user
402 $body = str_replace('$PAGEEDITDATE',
403 $wgLang->timeanddate( $article->mTimestamp, true, false, $timecorrection ),
404 $body);
405
406 $error = userMailer( $to, $mail->from, $mail->subject, $body, $mail->replyto );
407 return ($error == '');
408 }
409
410 } # end of class EmailNotification
411 ?>