Index: trunk/phase3/includes/User.php |
— | — | @@ -206,13 +206,7 @@ |
207 | 207 | return false; |
208 | 208 | } |
209 | 209 | |
210 | | - # Save to cache |
211 | | - $data = array(); |
212 | | - foreach ( self::$mCacheVars as $name ) { |
213 | | - $data[$name] = $this->$name; |
214 | | - } |
215 | | - $data['mVersion'] = MW_USER_VERSION; |
216 | | - $wgMemc->set( $key, $data ); |
| 210 | + $this->saveToCache(); |
217 | 211 | } else { |
218 | 212 | wfDebug( "Got user {$this->mId} from cache\n" ); |
219 | 213 | # Restore from cache |
— | — | @@ -224,6 +218,25 @@ |
225 | 219 | } |
226 | 220 | |
227 | 221 | /** |
| 222 | + * Save user data to the shared cache |
| 223 | + */ |
| 224 | + function saveToCache() { |
| 225 | + $this->load(); |
| 226 | + if ( $this->isAnon() ) { |
| 227 | + // Anonymous users are uncached |
| 228 | + return; |
| 229 | + } |
| 230 | + $data = array(); |
| 231 | + foreach ( self::$mCacheVars as $name ) { |
| 232 | + $data[$name] = $this->$name; |
| 233 | + } |
| 234 | + $data['mVersion'] = MW_USER_VERSION; |
| 235 | + $key = wfMemcKey( 'user', 'id', $this->mId ); |
| 236 | + global $wgMemc; |
| 237 | + $wgMemc->set( $key, $data ); |
| 238 | + } |
| 239 | + |
| 240 | + /** |
228 | 241 | * Static factory method for creation from username. |
229 | 242 | * |
230 | 243 | * This is slightly less efficient than newFromId(), so use newFromId() if |
— | — | @@ -1196,11 +1209,13 @@ |
1197 | 1210 | global $wgMemc; |
1198 | 1211 | $key = wfMemcKey( 'newtalk', 'ip', $this->getName() ); |
1199 | 1212 | $newtalk = $wgMemc->get( $key ); |
1200 | | - if( $newtalk != "" ) { |
| 1213 | + if( strval( $newtalk ) !== '' ) { |
1201 | 1214 | $this->mNewtalk = (bool)$newtalk; |
1202 | 1215 | } else { |
1203 | | - $this->mNewtalk = $this->checkNewtalk( 'user_ip', $this->getName() ); |
1204 | | - $wgMemc->set( $key, (int)$this->mNewtalk, time() + 1800 ); |
| 1216 | + // Since we are caching this, make sure it is up to date by getting it |
| 1217 | + // from the master |
| 1218 | + $this->mNewtalk = $this->checkNewtalk( 'user_ip', $this->getName(), true ); |
| 1219 | + $wgMemc->set( $key, (int)$this->mNewtalk, 1800 ); |
1205 | 1220 | } |
1206 | 1221 | } else { |
1207 | 1222 | $this->mNewtalk = $this->checkNewtalk( 'user_id', $this->mId ); |
— | — | @@ -1227,18 +1242,22 @@ |
1228 | 1243 | |
1229 | 1244 | |
1230 | 1245 | /** |
1231 | | - * Perform a user_newtalk check on current slaves; if the memcached data |
1232 | | - * is funky we don't want newtalk state to get stuck on save, as that's |
1233 | | - * damn annoying. |
1234 | | - * |
| 1246 | + * Perform a user_newtalk check, uncached. |
| 1247 | + * Use getNewtalk for a cached check. |
| 1248 | + * |
1235 | 1249 | * @param string $field |
1236 | 1250 | * @param mixed $id |
| 1251 | + * @param bool $fromMaster True to fetch from the master, false for a slave |
1237 | 1252 | * @return bool |
1238 | 1253 | * @private |
1239 | 1254 | */ |
1240 | | - function checkNewtalk( $field, $id ) { |
1241 | | - $dbr = wfGetDB( DB_SLAVE ); |
1242 | | - $ok = $dbr->selectField( 'user_newtalk', $field, |
| 1255 | + function checkNewtalk( $field, $id, $fromMaster = false ) { |
| 1256 | + if ( $fromMaster ) { |
| 1257 | + $db = wfGetDB( DB_MASTER ); |
| 1258 | + } else { |
| 1259 | + $db = wfGetDB( DB_SLAVE ); |
| 1260 | + } |
| 1261 | + $ok = $db->selectField( 'user_newtalk', $field, |
1243 | 1262 | array( $field => $id ), __METHOD__ ); |
1244 | 1263 | return $ok !== false; |
1245 | 1264 | } |
— | — | @@ -1250,17 +1269,18 @@ |
1251 | 1270 | * @private |
1252 | 1271 | */ |
1253 | 1272 | function updateNewtalk( $field, $id ) { |
1254 | | - if( $this->checkNewtalk( $field, $id ) ) { |
1255 | | - wfDebug( __METHOD__." already set ($field, $id), ignoring\n" ); |
1256 | | - return false; |
1257 | | - } |
1258 | 1273 | $dbw = wfGetDB( DB_MASTER ); |
1259 | 1274 | $dbw->insert( 'user_newtalk', |
1260 | 1275 | array( $field => $id ), |
1261 | 1276 | __METHOD__, |
1262 | 1277 | 'IGNORE' ); |
1263 | | - wfDebug( __METHOD__.": set on ($field, $id)\n" ); |
1264 | | - return true; |
| 1278 | + if ( $dbw->affectedRows() ) { |
| 1279 | + wfDebug( __METHOD__.": set on ($field, $id)\n" ); |
| 1280 | + return true; |
| 1281 | + } else { |
| 1282 | + wfDebug( __METHOD__." already set ($field, $id)\n" ); |
| 1283 | + return false; |
| 1284 | + } |
1265 | 1285 | } |
1266 | 1286 | |
1267 | 1287 | /** |
— | — | @@ -1270,16 +1290,17 @@ |
1271 | 1291 | * @private |
1272 | 1292 | */ |
1273 | 1293 | function deleteNewtalk( $field, $id ) { |
1274 | | - if( !$this->checkNewtalk( $field, $id ) ) { |
1275 | | - wfDebug( __METHOD__.": already gone ($field, $id), ignoring\n" ); |
1276 | | - return false; |
1277 | | - } |
1278 | 1294 | $dbw = wfGetDB( DB_MASTER ); |
1279 | 1295 | $dbw->delete( 'user_newtalk', |
1280 | 1296 | array( $field => $id ), |
1281 | 1297 | __METHOD__ ); |
1282 | | - wfDebug( __METHOD__.": killed on ($field, $id)\n" ); |
1283 | | - return true; |
| 1298 | + if ( $dbw->affectedRows() ) { |
| 1299 | + wfDebug( __METHOD__.": killed on ($field, $id)\n" ); |
| 1300 | + return true; |
| 1301 | + } else { |
| 1302 | + wfDebug( __METHOD__.": already gone ($field, $id)\n" ); |
| 1303 | + return false; |
| 1304 | + } |
1284 | 1305 | } |
1285 | 1306 | |
1286 | 1307 | /** |
— | — | @@ -1301,6 +1322,7 @@ |
1302 | 1323 | $field = 'user_id'; |
1303 | 1324 | $id = $this->getId(); |
1304 | 1325 | } |
| 1326 | + global $wgMemc; |
1305 | 1327 | |
1306 | 1328 | if( $val ) { |
1307 | 1329 | $changed = $this->updateNewtalk( $field, $id ); |
— | — | @@ -1308,20 +1330,13 @@ |
1309 | 1331 | $changed = $this->deleteNewtalk( $field, $id ); |
1310 | 1332 | } |
1311 | 1333 | |
1312 | | - if( $changed ) { |
1313 | | - if( $this->isAnon() ) { |
1314 | | - // Anons have a separate memcached space, since |
1315 | | - // user records aren't kept for them. |
1316 | | - global $wgMemc; |
1317 | | - $key = wfMemcKey( 'newtalk', 'ip', $val ); |
1318 | | - $wgMemc->set( $key, $val ? 1 : 0 ); |
1319 | | - } else { |
1320 | | - if( $val ) { |
1321 | | - // Make sure the user page is watched, so a notification |
1322 | | - // will be sent out if enabled. |
1323 | | - $this->addWatch( $this->getTalkPage() ); |
1324 | | - } |
1325 | | - } |
| 1334 | + if( $this->isAnon() ) { |
| 1335 | + // Anons have a separate memcached space, since |
| 1336 | + // user records aren't kept for them. |
| 1337 | + $key = wfMemcKey( 'newtalk', 'ip', $id ); |
| 1338 | + $wgMemc->set( $key, $val ? 1 : 0, 1800 ); |
| 1339 | + } |
| 1340 | + if ( $changed ) { |
1326 | 1341 | $this->invalidateCache(); |
1327 | 1342 | } |
1328 | 1343 | } |
— | — | @@ -1893,7 +1908,7 @@ |
1894 | 1909 | 'wl_notificationtimestamp' => NULL |
1895 | 1910 | ), array( /* WHERE */ |
1896 | 1911 | 'wl_user' => $currentUser |
1897 | | - ), 'UserMailer::clearAll' |
| 1912 | + ), __METHOD__ |
1898 | 1913 | ); |
1899 | 1914 | |
1900 | 1915 | # we also need to clear here the "you have new message" notification for the own user_talk page |
— | — | @@ -2378,10 +2393,9 @@ |
2379 | 2394 | $from = $wgPasswordSender; |
2380 | 2395 | } |
2381 | 2396 | |
2382 | | - require_once( 'UserMailer.php' ); |
2383 | 2397 | $to = new MailAddress( $this ); |
2384 | 2398 | $sender = new MailAddress( $from ); |
2385 | | - $error = userMailer( $to, $sender, $subject, $body ); |
| 2399 | + $error = UserMailer::send( $to, $sender, $subject, $body ); |
2386 | 2400 | |
2387 | 2401 | if( $error == '' ) { |
2388 | 2402 | return true; |
— | — | @@ -2689,3 +2703,4 @@ |
2690 | 2704 | } |
2691 | 2705 | |
2692 | 2706 | |
| 2707 | + |
Index: trunk/phase3/includes/RecentChange.php |
— | — | @@ -221,8 +221,7 @@ |
222 | 222 | if( $wgUseEnotif ) { |
223 | 223 | # this would be better as an extension hook |
224 | 224 | global $wgUser; |
225 | | - include_once( "UserMailer.php" ); |
226 | | - $enotif = new EmailNotification(); |
| 225 | + $enotif = new EmailNotification; |
227 | 226 | $title = Title::makeTitle( $this->mAttribs['rc_namespace'], $this->mAttribs['rc_title'] ); |
228 | 227 | $enotif->notifyOnPageChange( $wgUser, $title, |
229 | 228 | $this->mAttribs['rc_timestamp'], |
— | — | @@ -626,3 +625,4 @@ |
627 | 626 | } |
628 | 627 | } |
629 | 628 | |
| 629 | + |
Index: trunk/phase3/includes/UserMailer.php |
— | — | @@ -1,9 +1,5 @@ |
2 | 2 | <?php |
3 | 3 | /** |
4 | | - * UserMailer.php |
5 | | - * Copyright (C) 2004 Thomas Gries <mail@tgries.de> |
6 | | - * http://www.mediawiki.org/ |
7 | | - * |
8 | 4 | * This program is free software; you can redistribute it and/or modify |
9 | 5 | * it under the terms of the GNU General Public License as published by |
10 | 6 | * the Free Software Foundation; either version 2 of the License, or |
— | — | @@ -21,16 +17,10 @@ |
22 | 18 | * |
23 | 19 | * @author <brion@pobox.com> |
24 | 20 | * @author <mail@tgries.de> |
| 21 | + * @author Tim Starling |
25 | 22 | * |
26 | 23 | */ |
27 | 24 | |
28 | | -/** |
29 | | - * Converts a string into a valid RFC 822 "phrase", such as is used for the sender name |
30 | | - */ |
31 | | -function wfRFC822Phrase( $phrase ) { |
32 | | - $phrase = strtr( $phrase, array( "\r" => '', "\n" => '', '"' => '' ) ); |
33 | | - return '"' . $phrase . '"'; |
34 | | -} |
35 | 25 | |
36 | 26 | /** |
37 | 27 | * Stores a single person's name and email address. |
— | — | @@ -70,155 +60,184 @@ |
71 | 61 | return $this->address; |
72 | 62 | } |
73 | 63 | } |
74 | | -} |
75 | 64 | |
76 | | -function send_mail($mailer, $dest, $headers, $body) |
77 | | -{ |
78 | | - $mailResult =& $mailer->send($dest, $headers, $body); |
79 | | - |
80 | | - # Based on the result return an error string, |
81 | | - if ($mailResult === true) { |
82 | | - return ''; |
83 | | - } elseif (is_object($mailResult)) { |
84 | | - wfDebug( "PEAR::Mail failed: " . $mailResult->getMessage() . "\n" ); |
85 | | - return $mailResult->getMessage(); |
86 | | - } else { |
87 | | - wfDebug( "PEAR::Mail failed, unknown error result\n" ); |
88 | | - return 'Mail object return unknown error.'; |
| 65 | + function __toString() { |
| 66 | + return $this->toString(); |
89 | 67 | } |
90 | 68 | } |
91 | 69 | |
| 70 | + |
92 | 71 | /** |
93 | | - * This function will perform a direct (authenticated) login to |
94 | | - * a SMTP Server to use for mail relaying if 'wgSMTP' specifies an |
95 | | - * array of parameters. It requires PEAR:Mail to do that. |
96 | | - * Otherwise it just uses the standard PHP 'mail' function. |
97 | | - * |
98 | | - * @param $to MailAddress: recipient's email |
99 | | - * @param $from MailAddress: sender's email |
100 | | - * @param $subject String: email's subject. |
101 | | - * @param $body String: email's text. |
102 | | - * @param $replyto String: optional reply-to email (default: null). |
| 72 | + * Collection of static functions for sending mail |
103 | 73 | */ |
104 | | -function userMailer( $to, $from, $subject, $body, $replyto=null ) { |
105 | | - global $wgSMTP, $wgOutputEncoding, $wgErrorString, $wgEnotifImpersonal; |
106 | | - global $wgEnotifMaxRecips; |
| 74 | +class UserMailer { |
| 75 | + /** |
| 76 | + * Send mail using a PEAR mailer |
| 77 | + */ |
| 78 | + protected static function sendWithPear($mailer, $dest, $headers, $body) |
| 79 | + { |
| 80 | + $mailResult =& $mailer->send($dest, $headers, $body); |
107 | 81 | |
108 | | - if (is_array( $wgSMTP )) { |
109 | | - require_once( 'Mail.php' ); |
| 82 | + # Based on the result return an error string, |
| 83 | + if ($mailResult === true) { |
| 84 | + return ''; |
| 85 | + } elseif (is_object($mailResult)) { |
| 86 | + wfDebug( "PEAR::Mail failed: " . $mailResult->getMessage() . "\n" ); |
| 87 | + return $mailResult->getMessage(); |
| 88 | + } else { |
| 89 | + wfDebug( "PEAR::Mail failed, unknown error result\n" ); |
| 90 | + return 'Mail object return unknown error.'; |
| 91 | + } |
| 92 | + } |
110 | 93 | |
111 | | - $msgid = str_replace(" ", "_", microtime()); |
112 | | - if (function_exists('posix_getpid')) |
113 | | - $msgid .= '.' . posix_getpid(); |
| 94 | + /** |
| 95 | + * This function will perform a direct (authenticated) login to |
| 96 | + * a SMTP Server to use for mail relaying if 'wgSMTP' specifies an |
| 97 | + * array of parameters. It requires PEAR:Mail to do that. |
| 98 | + * Otherwise it just uses the standard PHP 'mail' function. |
| 99 | + * |
| 100 | + * @param $to MailAddress: recipient's email |
| 101 | + * @param $from MailAddress: sender's email |
| 102 | + * @param $subject String: email's subject. |
| 103 | + * @param $body String: email's text. |
| 104 | + * @param $replyto String: optional reply-to email (default: null). |
| 105 | + */ |
| 106 | + static function send( $to, $from, $subject, $body, $replyto=null ) { |
| 107 | + global $wgSMTP, $wgOutputEncoding, $wgErrorString, $wgEnotifImpersonal; |
| 108 | + global $wgEnotifMaxRecips; |
114 | 109 | |
115 | | - if (is_array($to)) { |
116 | | - $dest = array(); |
117 | | - foreach ($to as $u) |
118 | | - $dest[] = $u->address; |
119 | | - } else |
120 | | - $dest = $to->address; |
| 110 | + if ( is_array( $to ) ) { |
| 111 | + wfDebug( __METHOD__.': sending mail to ' . implode( ',', $to ) . "\n" ); |
| 112 | + } else { |
| 113 | + wfDebug( __METHOD__.': sending mail to ' . implode( ',', array( $to ) ) . "\n" ); |
| 114 | + } |
121 | 115 | |
122 | | - $headers['From'] = $from->toString(); |
| 116 | + if (is_array( $wgSMTP )) { |
| 117 | + require_once( 'Mail.php' ); |
123 | 118 | |
124 | | - if ($wgEnotifImpersonal) |
125 | | - $headers['To'] = 'undisclosed-recipients:;'; |
126 | | - else |
127 | | - $headers['To'] = $to->toString(); |
| 119 | + $msgid = str_replace(" ", "_", microtime()); |
| 120 | + if (function_exists('posix_getpid')) |
| 121 | + $msgid .= '.' . posix_getpid(); |
128 | 122 | |
129 | | - if ( $replyto ) { |
130 | | - $headers['Reply-To'] = $replyto->toString(); |
131 | | - } |
132 | | - $headers['Subject'] = wfQuotedPrintable( $subject ); |
133 | | - $headers['Date'] = date( 'r' ); |
134 | | - $headers['MIME-Version'] = '1.0'; |
135 | | - $headers['Content-type'] = 'text/plain; charset='.$wgOutputEncoding; |
136 | | - $headers['Content-transfer-encoding'] = '8bit'; |
137 | | - $headers['Message-ID'] = "<$msgid@" . $wgSMTP['IDHost'] . '>'; // FIXME |
138 | | - $headers['X-Mailer'] = 'MediaWiki mailer'; |
| 123 | + if (is_array($to)) { |
| 124 | + $dest = array(); |
| 125 | + foreach ($to as $u) |
| 126 | + $dest[] = $u->address; |
| 127 | + } else |
| 128 | + $dest = $to->address; |
139 | 129 | |
140 | | - // Create the mail object using the Mail::factory method |
141 | | - $mail_object =& Mail::factory('smtp', $wgSMTP); |
142 | | - if( PEAR::isError( $mail_object ) ) { |
143 | | - wfDebug( "PEAR::Mail factory failed: " . $mail_object->getMessage() . "\n" ); |
144 | | - return $mail_object->getMessage(); |
145 | | - } |
| 130 | + $headers['From'] = $from->toString(); |
146 | 131 | |
147 | | - wfDebug( "Sending mail via PEAR::Mail to $dest\n" ); |
148 | | - if (is_array($dest)) { |
149 | | - $chunks = array_chunk($dest, $wgEnotifMaxRecips); |
150 | | - foreach ($chunks as $chunk) { |
151 | | - $e = send_mail($mail_object, $chunk, $headers, $body); |
152 | | - if ($e != '') |
153 | | - return $e; |
| 132 | + if ($wgEnotifImpersonal) |
| 133 | + $headers['To'] = 'undisclosed-recipients:;'; |
| 134 | + else |
| 135 | + $headers['To'] = $to->toString(); |
| 136 | + |
| 137 | + if ( $replyto ) { |
| 138 | + $headers['Reply-To'] = $replyto->toString(); |
154 | 139 | } |
155 | | - } else |
156 | | - return $mail_object->send($dest, $headers, $body); |
| 140 | + $headers['Subject'] = wfQuotedPrintable( $subject ); |
| 141 | + $headers['Date'] = date( 'r' ); |
| 142 | + $headers['MIME-Version'] = '1.0'; |
| 143 | + $headers['Content-type'] = 'text/plain; charset='.$wgOutputEncoding; |
| 144 | + $headers['Content-transfer-encoding'] = '8bit'; |
| 145 | + $headers['Message-ID'] = "<$msgid@" . $wgSMTP['IDHost'] . '>'; // FIXME |
| 146 | + $headers['X-Mailer'] = 'MediaWiki mailer'; |
157 | 147 | |
158 | | - } else { |
159 | | - # In the following $headers = expression we removed "Reply-To: {$from}\r\n" , because it is treated differently |
160 | | - # (fifth parameter of the PHP mail function, see some lines below) |
| 148 | + // Create the mail object using the Mail::factory method |
| 149 | + $mail_object =& Mail::factory('smtp', $wgSMTP); |
| 150 | + if( PEAR::isError( $mail_object ) ) { |
| 151 | + wfDebug( "PEAR::Mail factory failed: " . $mail_object->getMessage() . "\n" ); |
| 152 | + return $mail_object->getMessage(); |
| 153 | + } |
161 | 154 | |
162 | | - # Line endings need to be different on Unix and Windows due to |
163 | | - # the bug described at http://trac.wordpress.org/ticket/2603 |
164 | | - if ( wfIsWindows() ) { |
165 | | - $body = str_replace( "\n", "\r\n", $body ); |
166 | | - $endl = "\r\n"; |
167 | | - } else { |
168 | | - $endl = "\n"; |
169 | | - } |
170 | | - $headers = |
171 | | - "MIME-Version: 1.0$endl" . |
172 | | - "Content-type: text/plain; charset={$wgOutputEncoding}$endl" . |
173 | | - "Content-Transfer-Encoding: 8bit$endl" . |
174 | | - "X-Mailer: MediaWiki mailer$endl". |
175 | | - 'From: ' . $from->toString(); |
176 | | - if ($replyto) { |
177 | | - $headers .= "{$endl}Reply-To: " . $replyto->toString(); |
178 | | - } |
| 155 | + wfDebug( "Sending mail via PEAR::Mail to $dest\n" ); |
| 156 | + if (is_array($dest)) { |
| 157 | + $chunks = array_chunk($dest, $wgEnotifMaxRecips); |
| 158 | + foreach ($chunks as $chunk) { |
| 159 | + $e = self::sendWithPear($mail_object, $chunk, $headers, $body); |
| 160 | + if ($e != '') |
| 161 | + return $e; |
| 162 | + } |
| 163 | + } else |
| 164 | + return $mail_object->send($dest, $headers, $body); |
179 | 165 | |
180 | | - $wgErrorString = ''; |
181 | | - set_error_handler( 'mailErrorHandler' ); |
182 | | - wfDebug( "Sending mail via internal mail() function\n" ); |
| 166 | + } else { |
| 167 | + # In the following $headers = expression we removed "Reply-To: {$from}\r\n" , because it is treated differently |
| 168 | + # (fifth parameter of the PHP mail function, see some lines below) |
183 | 169 | |
184 | | - if (function_exists('mail')) |
185 | | - if (is_array($to)) |
186 | | - foreach ($to as $recip) |
187 | | - $sent = mail( $recip->toString(), wfQuotedPrintable( $subject ), $body, $headers ); |
188 | | - else |
189 | | - $sent = mail( $to->toString(), wfQuotedPrintable( $subject ), $body, $headers ); |
190 | | - else |
191 | | - $wgErrorString = 'PHP is not configured to send mail'; |
| 170 | + # Line endings need to be different on Unix and Windows due to |
| 171 | + # the bug described at http://trac.wordpress.org/ticket/2603 |
| 172 | + if ( wfIsWindows() ) { |
| 173 | + $body = str_replace( "\n", "\r\n", $body ); |
| 174 | + $endl = "\r\n"; |
| 175 | + } else { |
| 176 | + $endl = "\n"; |
| 177 | + } |
| 178 | + $headers = |
| 179 | + "MIME-Version: 1.0$endl" . |
| 180 | + "Content-type: text/plain; charset={$wgOutputEncoding}$endl" . |
| 181 | + "Content-Transfer-Encoding: 8bit$endl" . |
| 182 | + "X-Mailer: MediaWiki mailer$endl". |
| 183 | + 'From: ' . $from->toString(); |
| 184 | + if ($replyto) { |
| 185 | + $headers .= "{$endl}Reply-To: " . $replyto->toString(); |
| 186 | + } |
192 | 187 | |
| 188 | + $wgErrorString = ''; |
| 189 | + $html_errors = ini_get( 'html_errors' ); |
| 190 | + ini_set( 'html_errors', '0' ); |
| 191 | + set_error_handler( array( 'UserMailer', 'errorHandler' ) ); |
| 192 | + wfDebug( "Sending mail via internal mail() function\n" ); |
193 | 193 | |
194 | | - restore_error_handler(); |
| 194 | + if (function_exists('mail')) { |
| 195 | + if (is_array($to)) { |
| 196 | + foreach ($to as $recip) { |
| 197 | + $sent = mail( $recip->toString(), wfQuotedPrintable( $subject ), $body, $headers ); |
| 198 | + } |
| 199 | + } else { |
| 200 | + $sent = mail( $to->toString(), wfQuotedPrintable( $subject ), $body, $headers ); |
| 201 | + } |
| 202 | + } else { |
| 203 | + $wgErrorString = 'PHP is not configured to send mail'; |
| 204 | + } |
195 | 205 | |
196 | | - if ( $wgErrorString ) { |
197 | | - wfDebug( "Error sending mail: $wgErrorString\n" ); |
198 | | - return $wgErrorString; |
199 | | - } elseif (! $sent) { |
200 | | - //mail function only tells if there's an error |
201 | | - wfDebug( "Error sending mail\n" ); |
202 | | - return 'mailer error'; |
203 | | - } else { |
204 | | - return ''; |
| 206 | + restore_error_handler(); |
| 207 | + ini_set( 'html_errors', $html_errors ); |
| 208 | + |
| 209 | + if ( $wgErrorString ) { |
| 210 | + wfDebug( "Error sending mail: $wgErrorString\n" ); |
| 211 | + return $wgErrorString; |
| 212 | + } elseif (! $sent) { |
| 213 | + //mail function only tells if there's an error |
| 214 | + wfDebug( "Error sending mail\n" ); |
| 215 | + return 'mailer error'; |
| 216 | + } else { |
| 217 | + return ''; |
| 218 | + } |
205 | 219 | } |
206 | 220 | } |
207 | | -} |
208 | 221 | |
| 222 | + /** |
| 223 | + * Get the mail error message in global $wgErrorString |
| 224 | + * |
| 225 | + * @param $code Integer: error number |
| 226 | + * @param $string String: error message |
| 227 | + */ |
| 228 | + static function errorHandler( $code, $string ) { |
| 229 | + global $wgErrorString; |
| 230 | + $wgErrorString = preg_replace( '/^mail\(\)(\s*\[.*?\])?: /', '', $string ); |
| 231 | + } |
209 | 232 | |
210 | | - |
211 | | -/** |
212 | | - * Get the mail error message in global $wgErrorString |
213 | | - * |
214 | | - * @param $code Integer: error number |
215 | | - * @param $string String: error message |
216 | | - */ |
217 | | -function mailErrorHandler( $code, $string ) { |
218 | | - global $wgErrorString; |
219 | | - $wgErrorString = preg_replace( '/^mail\(\)(\s*\[.*?\])?: /', '', $string ); |
| 233 | + /** |
| 234 | + * Converts a string into a valid RFC 822 "phrase", such as is used for the sender name |
| 235 | + */ |
| 236 | + static function rfc822Phrase( $phrase ) { |
| 237 | + $phrase = strtr( $phrase, array( "\r" => '', "\n" => '', '"' => '' ) ); |
| 238 | + return '"' . $phrase . '"'; |
| 239 | + } |
220 | 240 | } |
221 | 241 | |
222 | | - |
223 | 242 | /** |
224 | 243 | * This module processes the email notifications when the current page is |
225 | 244 | * changed. It looks up the table watchlist to find out which users are watching |
— | — | @@ -245,10 +264,24 @@ |
246 | 265 | */ |
247 | 266 | var $to, $subject, $body, $replyto, $from; |
248 | 267 | var $user, $title, $timestamp, $summary, $minorEdit, $oldid; |
| 268 | + var $mailTargets = array(); |
249 | 269 | |
250 | 270 | /**@}}*/ |
251 | 271 | |
252 | | - function notifyOnPageChange($editor, &$title, $timestamp, $summary, $minorEdit, $oldid = false) { |
| 272 | + /** |
| 273 | + * Send emails corresponding to the user $editor editing the page $title. |
| 274 | + * Also updates wl_notificationtimestamp. |
| 275 | + * |
| 276 | + * May be deferred via the job queue. |
| 277 | + * |
| 278 | + * @param $editor User object |
| 279 | + * @param $title Title object |
| 280 | + * @param $timestamp |
| 281 | + * @param $summary |
| 282 | + * @param $minorEdit |
| 283 | + * @param $oldid (default: false) |
| 284 | + */ |
| 285 | + function notifyOnPageChange($editor, $title, $timestamp, $summary, $minorEdit, $oldid = false) { |
253 | 286 | global $wgEnotifUseJobQ; |
254 | 287 | |
255 | 288 | if( $title->getNamespace() < 0 ) |
— | — | @@ -269,23 +302,27 @@ |
270 | 303 | |
271 | 304 | } |
272 | 305 | |
273 | | - /** |
274 | | - * @todo document |
| 306 | + /* |
| 307 | + * Immediate version of notifyOnPageChange(). |
| 308 | + * |
| 309 | + * Send emails corresponding to the user $editor editing the page $title. |
| 310 | + * Also updates wl_notificationtimestamp. |
| 311 | + * |
| 312 | + * @param $editor User object |
275 | 313 | * @param $title Title object |
276 | 314 | * @param $timestamp |
277 | 315 | * @param $summary |
278 | 316 | * @param $minorEdit |
279 | 317 | * @param $oldid (default: false) |
280 | 318 | */ |
281 | | - function actuallyNotifyOnPageChange($editor, &$title, $timestamp, $summary, $minorEdit, $oldid=false) { |
| 319 | + function actuallyNotifyOnPageChange($editor, $title, $timestamp, $summary, $minorEdit, $oldid=false) { |
282 | 320 | |
283 | 321 | # we use $wgEmergencyContact as sender's address |
284 | 322 | global $wgEnotifWatchlist; |
285 | 323 | global $wgEnotifMinorEdits, $wgEnotifUserTalk, $wgShowUpdatedMarker; |
286 | 324 | global $wgEnotifImpersonal; |
287 | 325 | |
288 | | - $fname = 'UserMailer::notifyOnPageChange'; |
289 | | - wfProfileIn( $fname ); |
| 326 | + wfProfileIn( __METHOD__ ); |
290 | 327 | |
291 | 328 | # The following code is only run, if several conditions are met: |
292 | 329 | # 1. EmailNotification for pages (other than user_talk pages) must be enabled |
— | — | @@ -295,108 +332,82 @@ |
296 | 333 | $enotifusertalkpage = ($isUserTalkPage && $wgEnotifUserTalk); |
297 | 334 | $enotifwatchlistpage = $wgEnotifWatchlist; |
298 | 335 | |
299 | | - $this->title =& $title; |
| 336 | + $this->title = $title; |
300 | 337 | $this->timestamp = $timestamp; |
301 | 338 | $this->summary = $summary; |
302 | 339 | $this->minorEdit = $minorEdit; |
303 | 340 | $this->oldid = $oldid; |
304 | 341 | $this->composeCommonMailtext($editor); |
305 | 342 | |
306 | | - $impersonals = array(); |
| 343 | + $userTalkId = false; |
307 | 344 | |
308 | 345 | if ( (!$minorEdit || $wgEnotifMinorEdits) ) { |
309 | | - if( $wgEnotifWatchlist ) { |
310 | | - // Send updates to watchers other than the current editor |
311 | | - $userCondition = 'wl_user <> ' . intval( $editor->getId() ); |
312 | | - } elseif( $wgEnotifUserTalk && $title->getNamespace() == NS_USER_TALK ) { |
| 346 | + if ( $wgEnotifUserTalk && $isUserTalkPage ) { |
313 | 347 | $targetUser = User::newFromName( $title->getText() ); |
314 | | - if( is_null( $targetUser ) ) { |
315 | | - wfDebug( "$fname: user-talk-only mode; no such user\n" ); |
316 | | - $userCondition = false; |
317 | | - } elseif( $targetUser->getId() == $editor->getId() ) { |
318 | | - wfDebug( "$fname: user-talk-only mode; editor is target user\n" ); |
319 | | - $userCondition = false; |
| 348 | + if ( !$targetUser || $targetUser->isAnon() ) { |
| 349 | + wfDebug( __METHOD__.": user talk page edited, but user does not exist\n" ); |
| 350 | + } elseif ( $targetUser->getId() == $editor->getId() ) { |
| 351 | + wfDebug( __METHOD__.": user edited their own talk page, no notification sent\n" ); |
320 | 352 | } else { |
321 | | - // Don't notify anyone other than the owner of the talk page |
322 | | - $userCondition = 'wl_user = ' . intval( $targetUser->getId() ); |
| 353 | + $this->compose( $targetUser ); |
| 354 | + $userTalkId = $targetUser->getId(); |
323 | 355 | } |
324 | | - } else { |
325 | | - // Notifications disabled |
326 | | - $userCondition = false; |
327 | 356 | } |
328 | | - if( $userCondition ) { |
329 | | - $dbr = wfGetDB( DB_MASTER ); |
330 | 357 | |
| 358 | + |
| 359 | + if ( $wgEnotifWatchlist ) { |
| 360 | + // Send updates to watchers other than the current editor |
| 361 | + $userCondition = 'wl_user <> ' . intval( $editor->getId() ); |
| 362 | + if ( $userTalkId !== false ) { |
| 363 | + // Already sent an email to this person |
| 364 | + $userCondition .= ' AND wl_user <> ' . intval( $userTalkId ); |
| 365 | + } |
| 366 | + $dbr = wfGetDB( DB_SLAVE ); |
| 367 | + |
331 | 368 | $res = $dbr->select( 'watchlist', array( 'wl_user' ), |
332 | 369 | array( |
333 | 370 | 'wl_title' => $title->getDBkey(), |
334 | 371 | 'wl_namespace' => $title->getNamespace(), |
335 | 372 | $userCondition, |
336 | 373 | 'wl_notificationtimestamp IS NULL', |
337 | | - ), $fname ); |
| 374 | + ), __METHOD__ ); |
338 | 375 | |
339 | | - # if anyone is watching ... set up the email message text which is |
340 | | - # common for all receipients ... |
341 | | - if ( $dbr->numRows( $res ) > 0 ) { |
342 | | - |
343 | | - $watchingUser = new User(); |
344 | | - |
345 | | - # ... now do for all watching users ... if the options fit |
346 | | - for ($i = 1; $i <= $dbr->numRows( $res ); $i++) { |
347 | | - |
348 | | - $wuser = $dbr->fetchObject( $res ); |
349 | | - $watchingUser->setID($wuser->wl_user); |
350 | | - |
351 | | - if ( ( ( $enotifwatchlistpage |
352 | | - && $watchingUser->getOption('enotifwatchlistpages') ) |
353 | | - || ( $enotifusertalkpage |
354 | | - && $watchingUser->getOption('enotifusertalkpages') |
355 | | - && $title->equals( $watchingUser->getTalkPage() ) ) ) |
356 | | - && ( !$minorEdit |
357 | | - || ( $wgEnotifMinorEdits |
358 | | - && $watchingUser->getOption('enotifminoredits') ) ) |
359 | | - && ( $watchingUser->isEmailConfirmed() ) ) { |
360 | | - # ... adjust remaining text and page edit time placeholders |
361 | | - # which needs to be personalized for each user |
362 | | - if ($wgEnotifImpersonal) |
363 | | - $impersonals[] = $watchingUser; |
364 | | - else |
365 | | - $this->composeAndSendPersonalisedMail( $watchingUser ); |
366 | | - |
367 | | - } # if the watching user has an email address in the preferences |
| 376 | + foreach ( $res as $row ) { |
| 377 | + $watchingUser = User::newFromId( $row->wl_user ); |
| 378 | + if ( $watchingUser->getOption( 'enotifwatchlistpages' ) && |
| 379 | + ( !$minorEdit || $watchingUser->getOption('enotifminoredits') ) && |
| 380 | + $watchingUser->isEmailConfirmed() ) |
| 381 | + { |
| 382 | + $this->compose( $watchingUser ); |
368 | 383 | } |
369 | 384 | } |
370 | | - } # if anyone is watching |
371 | | - } # if $wgEnotifWatchlist = true |
| 385 | + } |
| 386 | + } |
372 | 387 | |
373 | 388 | global $wgUsersNotifedOnAllChanges; |
374 | 389 | foreach ( $wgUsersNotifedOnAllChanges as $name ) { |
375 | 390 | $user = User::newFromName( $name ); |
376 | | - if ($wgEnotifImpersonal) |
377 | | - $impersonals[] = $user; |
378 | | - else |
379 | | - $this->composeAndSendPersonalisedMail( $user ); |
| 391 | + $this->compose( $user ); |
380 | 392 | } |
381 | 393 | |
382 | | - $this->composeAndSendImpersonalMail($impersonals); |
| 394 | + $this->sendMails(); |
383 | 395 | |
384 | 396 | if ( $wgShowUpdatedMarker || $wgEnotifWatchlist ) { |
385 | 397 | # mark the changed watch-listed page with a timestamp, so that the page is |
386 | 398 | # listed with an "updated since your last visit" icon in the watch list, ... |
387 | 399 | $dbw = wfGetDB( DB_MASTER ); |
388 | | - $success = $dbw->update( 'watchlist', |
| 400 | + $dbw->update( 'watchlist', |
389 | 401 | array( /* SET */ |
390 | 402 | 'wl_notificationtimestamp' => $dbw->timestamp($timestamp) |
391 | 403 | ), array( /* WHERE */ |
392 | 404 | 'wl_title' => $title->getDBkey(), |
393 | 405 | 'wl_namespace' => $title->getNamespace(), |
394 | 406 | 'wl_notificationtimestamp IS NULL' |
395 | | - ), 'UserMailer::NotifyOnChange' |
| 407 | + ), __METHOD__ |
396 | 408 | ); |
397 | | - # FIXME what do we do on failure ? |
398 | 409 | } |
399 | 410 | |
400 | | - wfProfileOut( $fname ); |
| 411 | + wfProfileOut( __METHOD__ ); |
401 | 412 | } # function NotifyOnChange |
402 | 413 | |
403 | 414 | /** |
— | — | @@ -498,6 +509,31 @@ |
499 | 510 | } |
500 | 511 | |
501 | 512 | /** |
| 513 | + * Compose a mail to a given user and either queue it for sending, or send it now, |
| 514 | + * depending on settings. |
| 515 | + * |
| 516 | + * Call sendMails() to send any mails that were queued. |
| 517 | + */ |
| 518 | + function compose( $user ) { |
| 519 | + global $wgEnotifImpersonal; |
| 520 | + if ( $wgEnotifImpersonal ) { |
| 521 | + $this->mailTargets[] = new MailAddress( $user ); |
| 522 | + } else { |
| 523 | + $this->sendPersonalised( $user ); |
| 524 | + } |
| 525 | + } |
| 526 | + |
| 527 | + /** |
| 528 | + * Send any queued mails |
| 529 | + */ |
| 530 | + function sendMails() { |
| 531 | + global $wgEnotifImpersonal; |
| 532 | + if ( $wgEnotifImpersonal ) { |
| 533 | + $this->sendImpersonal( $this->mailTargets ); |
| 534 | + } |
| 535 | + } |
| 536 | + |
| 537 | + /** |
502 | 538 | * Does the per-user customizations to a notification e-mail (name, |
503 | 539 | * timestamp in proper timezone, etc) and sends it out. |
504 | 540 | * Returns true if the mail was sent successfully. |
— | — | @@ -507,7 +543,7 @@ |
508 | 544 | * @return bool |
509 | 545 | * @private |
510 | 546 | */ |
511 | | - function composeAndSendPersonalisedMail( $watchingUser ) { |
| 547 | + function sendPersonalised( $watchingUser ) { |
512 | 548 | global $wgLang; |
513 | 549 | // From the PHP manual: |
514 | 550 | // Note: The to parameter cannot be an address in the form of "Something <someone@example.com>". |
— | — | @@ -524,23 +560,19 @@ |
525 | 561 | $wgLang->timeanddate( $this->timestamp, true, false, $timecorrection ), |
526 | 562 | $body); |
527 | 563 | |
528 | | - return userMailer($to, $this->from, $this->subject, $body, $this->replyto); |
| 564 | + return UserMailer::send($to, $this->from, $this->subject, $body, $this->replyto); |
529 | 565 | } |
530 | 566 | |
531 | 567 | /** |
532 | | - * Same as composeAndSendPersonalisedMail but does impersonal mail |
533 | | - * suitable for bulk mailing. Takes an array of users. |
| 568 | + * Same as sendPersonalised but does impersonal mail suitable for bulk |
| 569 | + * mailing. Takes an array of MailAddress objects. |
534 | 570 | */ |
535 | | - function composeAndSendImpersonalMail($users) { |
| 571 | + function sendImpersonal( $addresses ) { |
536 | 572 | global $wgLang; |
537 | 573 | |
538 | | - if (empty($users)) |
| 574 | + if (empty($addresses)) |
539 | 575 | return; |
540 | 576 | |
541 | | - $to = array(); |
542 | | - foreach ($users as $user) |
543 | | - $to[] = new MailAddress($user); |
544 | | - |
545 | 577 | $body = str_replace( |
546 | 578 | array( '$WATCHINGUSERNAME', |
547 | 579 | '$PAGEEDITDATE'), |
— | — | @@ -548,8 +580,20 @@ |
549 | 581 | $wgLang->timeanddate($this->timestamp, true, false, false)), |
550 | 582 | $this->body); |
551 | 583 | |
552 | | - return userMailer($to, $this->from, $this->subject, $body, $this->replyto); |
| 584 | + return UserMailer::send($addresses, $this->from, $this->subject, $body, $this->replyto); |
553 | 585 | } |
554 | 586 | |
555 | 587 | } # end of class EmailNotification |
556 | 588 | |
| 589 | +/** |
| 590 | + * Backwards compatibility functions |
| 591 | + */ |
| 592 | +function wfRFC822Phrase( $s ) { |
| 593 | + return UserMailer::rfc822Phrase( $s ); |
| 594 | +} |
| 595 | +function userMailer( $to, $from, $subject, $body, $replyto=null ) { |
| 596 | + return UserMailer::send( $to, $from, $subject, $body, $replyto ); |
| 597 | +} |
| 598 | + |
| 599 | + |
| 600 | + |
Index: trunk/phase3/includes/JobQueue.php |
— | — | @@ -4,8 +4,6 @@ |
5 | 5 | die( "This file is part of MediaWiki, it is not a valid entry point\n" ); |
6 | 6 | } |
7 | 7 | |
8 | | -require_once('UserMailer.php'); |
9 | | - |
10 | 8 | /** |
11 | 9 | * Class to both describe a background job and handle jobs. |
12 | 10 | */ |
— | — | @@ -290,3 +288,4 @@ |
291 | 289 | } |
292 | 290 | } |
293 | 291 | |
| 292 | + |
Index: trunk/phase3/includes/AutoLoader.php |
— | — | @@ -241,6 +241,7 @@ |
242 | 242 | 'User' => 'includes/User.php', |
243 | 243 | 'MailAddress' => 'includes/UserMailer.php', |
244 | 244 | 'EmailNotification' => 'includes/UserMailer.php', |
| 245 | + 'UserMailer' => 'includes/UserMailer.php', |
245 | 246 | 'WatchedItem' => 'includes/WatchedItem.php', |
246 | 247 | 'WebRequest' => 'includes/WebRequest.php', |
247 | 248 | 'WebResponse' => 'includes/WebResponse.php', |
— | — | @@ -382,4 +383,4 @@ |
383 | 384 | require( $file ); |
384 | 385 | } |
385 | 386 | } |
386 | | -} |
\ No newline at end of file |
| 387 | +} |