58 protected static function sendWithPear( $mailer, $dest, $headers, $body ) {
59 $mailResult = $mailer->send( $dest, $headers, $body );
62 if ( PEAR::isError( $mailResult ) ) {
63 wfDebug(
"PEAR::Mail failed: " . $mailResult->getMessage() );
64 return Status::newFatal(
'pear-mail-error', $mailResult->getMessage() );
66 return Status::newGood();
112 public static function send( $to, $from, $subject, $body, $options = [] ) {
113 $services = MediaWikiServices::getInstance();
114 $allowHTMLEmail = $services->getMainConfig()->get(
115 MainConfigNames::AllowHTMLEmail );
117 if ( !isset( $options[
'contentType'] ) ) {
118 $options[
'contentType'] =
'text/plain; charset=UTF-8';
121 if ( !is_array( $to ) ) {
132 !is_array( $body ) &&
133 strlen( $body ) >= $minBodyLen
138 isset( $body[
'text'] ) &&
139 isset( $body[
'html'] ) &&
140 strlen( $body[
'text'] ) >= $minBodyLen &&
141 strlen( $body[
'html'] ) >= $minBodyLen
145 return Status::newFatal(
'user-mail-no-body' );
148 if ( !$allowHTMLEmail && is_array( $body ) ) {
150 $body = $body[
'text'];
153 wfDebug( __METHOD__ .
': sending mail to ' . implode(
', ', $to ) );
156 $has_address =
false;
157 foreach ( $to as $u ) {
163 if ( !$has_address ) {
164 return Status::newFatal(
'user-mail-no-addy' );
169 if ( count( $to ) > 1 ) {
171 (
new HookRunner( $services->getHookContainer() ) )->onUserMailerSplitTo( $to );
172 if ( $oldTo != $to ) {
173 $splitTo = array_diff( $oldTo, $to );
174 $to = array_diff( $oldTo, $splitTo );
176 $status = Status::newGood();
178 $status->merge( self::sendInternal(
179 $to, $from, $subject, $body, $options ) );
181 foreach ( $splitTo as $newTo ) {
182 $status->merge( self::sendInternal(
183 [ $newTo ], $from, $subject, $body, $options ) );
189 return self::sendInternal( $to, $from, $subject, $body, $options );
212 $services = MediaWikiServices::getInstance();
213 $mainConfig = $services->getMainConfig();
214 $smtp = $mainConfig->get( MainConfigNames::SMTP );
215 $enotifMaxRecips = $mainConfig->get( MainConfigNames::EnotifMaxRecips );
216 $additionalMailParams = $mainConfig->get( MainConfigNames::AdditionalMailParams );
218 $replyto = $options[
'replyTo'] ??
null;
219 $contentType = $options[
'contentType'] ??
'text/plain; charset=UTF-8';
220 $headers = $options[
'headers'] ?? [];
222 $hookRunner =
new HookRunner( $services->getHookContainer() );
226 if ( !$hookRunner->onUserMailerTransformContent( $to, $from, $body, $error ) ) {
228 return Status::newFatal(
'php-mail-error', $error );
230 return Status::newFatal(
'php-mail-error-unknown' );
263 $headers[
'From'] = $from->
toString();
264 $returnPath = $from->address;
265 $extraParams = $additionalMailParams;
268 $hookRunner->onUserMailerChangeReturnPath( $to, $returnPath );
278 $returnPathCLI =
'"' . str_replace(
'"',
'', $returnPath ) .
'"';
279 $extraParams .=
' -f ' . $returnPathCLI;
281 $headers[
'Return-Path'] = $returnPath;
284 $headers[
'Reply-To'] = $replyto->
toString();
287 $headers[
'Date'] = MWTimestamp::getLocalInstance()->format(
'r' );
288 $headers[
'Message-ID'] = self::makeMsgId();
289 $headers[
'X-Mailer'] =
'MediaWiki mailer';
290 $headers[
'List-Unsubscribe'] =
'<' . SpecialPage::getTitleFor(
'Preferences' )
297 if ( is_array( $body ) ) {
299 wfDebug(
"Assembling multipart mime email" );
301 $body[
'text'] = str_replace(
"\n",
"\r\n", $body[
'text'] );
302 $body[
'html'] = str_replace(
"\n",
"\r\n", $body[
'html'] );
304 $mime =
new Mail_mime( [
306 'text_charset' =>
'UTF-8',
307 'html_charset' =>
'UTF-8'
309 $mime->setTXTBody( $body[
'text'] );
310 $mime->setHTMLBody( $body[
'html'] );
311 $body = $mime->get();
312 $headers = $mime->headers( $headers );
316 $body = str_replace(
"\n",
"\r\n", $body );
318 $headers[
'MIME-Version'] =
'1.0';
319 $headers[
'Content-type'] = $contentType;
320 $headers[
'Content-transfer-encoding'] =
'8bit';
324 if ( !$hookRunner->onUserMailerTransformMessage(
325 $to, $from, $subject, $headers, $body, $error )
328 return Status::newFatal(
'php-mail-error', $error );
330 return Status::newFatal(
'php-mail-error-unknown' );
334 $ret = $hookRunner->onAlternateUserMailer( $headers, $to, $from, $subject, $body );
335 if ( $ret ===
false ) {
337 LoggerFactory::getInstance(
'usermailer' )->info(
338 "Email to {to} from {from} with subject {subject} handled by AlternateUserMailer",
340 'to' => $to[0]->toString(),
341 'allto' => implode(
', ', array_map(
'strval', $to ) ),
343 'subject' => $subject,
346 return Status::newGood();
347 } elseif ( $ret !==
true ) {
349 return Status::newFatal(
'php-mail-error', $ret );
352 if ( is_array( $smtp ) ) {
353 $recips = array_map(
'strval', $to );
356 $mail_object = Mail::factory(
'smtp', $smtp );
357 if ( PEAR::isError( $mail_object ) ) {
358 wfDebug(
"PEAR::Mail factory failed: " . $mail_object->getMessage() );
359 return Status::newFatal(
'pear-mail-error', $mail_object->getMessage() );
361 '@phan-var Mail_smtp $mail_object';
363 wfDebug(
"Sending mail via PEAR::Mail" );
365 $headers[
'Subject'] = self::quotedPrintable( $subject );
368 if ( count( $recips ) == 1 ) {
369 $headers[
'To'] = $recips[0];
374 $chunks = array_chunk( $recips, $enotifMaxRecips );
375 foreach ( $chunks as $chunk ) {
376 $status = self::sendWithPear( $mail_object, $chunk, $headers, $body );
378 if ( !$status->isOK() ) {
382 return Status::newGood();
385 if ( count( $to ) > 1 ) {
386 $headers[
'To'] =
'undisclosed-recipients:;';
389 wfDebug(
"Sending mail via internal mail() function" );
391 self::$mErrorString =
'';
392 $html_errors = ini_get(
'html_errors' );
393 ini_set(
'html_errors',
'0' );
394 set_error_handler( [ self::class,
'errorHandler' ] );
397 foreach ( $to as $recip ) {
400 self::quotedPrintable( $subject ),
406 }
catch ( Exception $e ) {
407 restore_error_handler();
411 restore_error_handler();
412 ini_set(
'html_errors', $html_errors );
414 if ( self::$mErrorString ) {
415 wfDebug(
"Error sending mail: " . self::$mErrorString );
416 return Status::newFatal(
'php-mail-error', self::$mErrorString );
417 } elseif ( !$sent ) {
420 wfDebug(
"Unknown error sending mail" );
421 return Status::newFatal(
'php-mail-error-unknown' );
423 LoggerFactory::getInstance(
'usermailer' )->info(
424 "Email sent to {to} from {from} with subject {subject}",
426 'to' => $to[0]->toString(),
427 'allto' => implode(
', ', array_map(
'strval', $to ) ),
429 'subject' => $subject,
432 return Status::newGood();