X-Git-Url: https://scripts.mit.edu/gitweb/autoinstallsdev/mediawiki.git/blobdiff_plain/19e297c21b10b1b8a3acad5e73fc71dcb35db44a..6932310fd58ebef145fa01eb76edf7150284d8ea:/includes/specials/SpecialEmailuser.php diff --git a/includes/specials/SpecialEmailuser.php b/includes/specials/SpecialEmailuser.php index 61271227..249be7f1 100644 --- a/includes/specials/SpecialEmailuser.php +++ b/includes/specials/SpecialEmailuser.php @@ -20,6 +20,7 @@ * @file * @ingroup SpecialPage */ +use MediaWiki\MediaWikiServices; /** * A special page that allows users to send e-mails to other users @@ -28,187 +29,268 @@ */ class SpecialEmailUser extends UnlistedSpecialPage { protected $mTarget; - - public function __construct(){ + + /** + * @var User|string $mTargetObj + */ + protected $mTargetObj; + + public function __construct() { parent::__construct( 'Emailuser' ); } - - protected function getFormFields(){ - global $wgUser; - return array( - 'From' => array( + + public function doesWrites() { + return true; + } + + public function getDescription() { + $target = self::getTarget( $this->mTarget, $this->getUser() ); + if ( !$target instanceof User ) { + return $this->msg( 'emailuser-title-notarget' )->text(); + } + + return $this->msg( 'emailuser-title-target', $target->getName() )->text(); + } + + protected function getFormFields() { + $linkRenderer = $this->getLinkRenderer(); + return [ + 'From' => [ 'type' => 'info', 'raw' => 1, - 'default' => $wgUser->getSkin()->link( - $wgUser->getUserPage(), - htmlspecialchars( $wgUser->getName() ) + 'default' => $linkRenderer->makeLink( + $this->getUser()->getUserPage(), + $this->getUser()->getName() ), 'label-message' => 'emailfrom', 'id' => 'mw-emailuser-sender', - ), - 'To' => array( + ], + 'To' => [ 'type' => 'info', 'raw' => 1, - 'default' => $wgUser->getSkin()->link( - $this->mTargetObj->getUserPage(), - htmlspecialchars( $this->mTargetObj->getName() ) + 'default' => $linkRenderer->makeLink( + $this->mTargetObj->getUserPage(), + $this->mTargetObj->getName() ), 'label-message' => 'emailto', 'id' => 'mw-emailuser-recipient', - ), - 'Target' => array( + ], + 'Target' => [ 'type' => 'hidden', 'default' => $this->mTargetObj->getName(), - ), - 'Subject' => array( + ], + 'Subject' => [ 'type' => 'text', - 'default' => wfMsgExt( 'defemailsubject', array( 'content', 'parsemag' ) ), + 'default' => $this->msg( 'defemailsubject', + $this->getUser()->getName() )->inContentLanguage()->text(), 'label-message' => 'emailsubject', 'maxlength' => 200, 'size' => 60, - 'required' => 1, - ), - 'Text' => array( + 'required' => true, + ], + 'Text' => [ 'type' => 'textarea', 'rows' => 20, 'cols' => 80, 'label-message' => 'emailmessage', - 'required' => 1, - ), - 'CCMe' => array( + 'required' => true, + ], + 'CCMe' => [ 'type' => 'check', 'label-message' => 'emailccme', - 'default' => $wgUser->getBoolOption( 'ccmeonemails' ), - ), - ); + 'default' => $this->getUser()->getBoolOption( 'ccmeonemails' ), + ], + ]; } - + public function execute( $par ) { - global $wgRequest, $wgOut, $wgUser; + $out = $this->getOutput(); + $out->addModuleStyles( 'mediawiki.special' ); + + $this->mTarget = is_null( $par ) + ? $this->getRequest()->getVal( 'wpTarget', $this->getRequest()->getVal( 'target', '' ) ) + : $par; + // This needs to be below assignment of $this->mTarget because + // getDescription() needs it to determine the correct page title. $this->setHeaders(); $this->outputHeader(); - $this->mTarget = is_null( $par ) - ? $wgRequest->getVal( 'wpTarget', $wgRequest->getVal( 'target', '' ) ) - : $par; - - $ret = self::getTarget( $this->mTarget ); - if( $ret instanceof User ){ - $this->mTargetObj = $ret; - } else { - $wgOut->showErrorPage( "{$ret}title", "{$ret}text" ); - return false; - } - - $error = self::getPermissionsError( $wgUser, $wgRequest->getVal( 'wpEditToken' ) ); + // error out if sending user cannot do this + $error = self::getPermissionsError( + $this->getUser(), + $this->getRequest()->getVal( 'wpEditToken' ), + $this->getConfig() + ); + switch ( $error ) { case null: # Wahey! break; case 'badaccess': - $wgOut->permissionRequired( 'sendemail' ); - return; + throw new PermissionsError( 'sendemail' ); case 'blockedemailuser': - $wgOut->blockedPage(); - return; + throw new UserBlockedError( $this->getUser()->mBlock ); case 'actionthrottledtext': - $wgOut->rateLimited(); - return; + throw new ThrottledError; case 'mailnologin': case 'usermaildisabled': - $wgOut->showErrorPage( $error, "{$error}text" ); - return; + throw new ErrorPageError( $error, "{$error}text" ); default: # It's a hook error list( $title, $msg, $params ) = $error; - $wgOut->showErrorPage( $title, $msg, $params ); - return; + throw new ErrorPageError( $title, $msg, $params ); } - - $form = new HTMLForm( $this->getFormFields() ); - $form->addPreText( wfMsgExt( 'emailpagetext', 'parseinline' ) ); - $form->setSubmitText( wfMsg( 'emailsend' ) ); - $form->setTitle( $this->getTitle() ); - $form->setSubmitCallback( array( __CLASS__, 'submit' ) ); - $form->setWrapperLegend( wfMsgExt( 'email-legend', 'parsemag' ) ); + // Got a valid target user name? Else ask for one. + $ret = self::getTarget( $this->mTarget, $this->getUser() ); + if ( !$ret instanceof User ) { + if ( $this->mTarget != '' ) { + // Messages used here: notargettext, noemailtext, nowikiemailtext + $ret = ( $ret == 'notarget' ) ? 'emailnotarget' : ( $ret . 'text' ); + $out->wrapWikiMsg( "

$1

", $ret ); + } + $out->addHTML( $this->userForm( $this->mTarget ) ); + + return; + } + + $this->mTargetObj = $ret; + + // Set the 'relevant user' in the skin, so it displays links like Contributions, + // User logs, UserRights, etc. + $this->getSkin()->setRelevantUser( $this->mTargetObj ); + + $context = new DerivativeContext( $this->getContext() ); + $context->setTitle( $this->getPageTitle() ); // Remove subpage + $form = new HTMLForm( $this->getFormFields(), $context ); + // By now we are supposed to be sure that $this->mTarget is a user name + $form->addPreText( $this->msg( 'emailpagetext', $this->mTarget )->parse() ); + $form->setSubmitTextMsg( 'emailsend' ); + $form->setSubmitCallback( [ __CLASS__, 'uiSubmit' ] ); + $form->setWrapperLegendMsg( 'email-legend' ); $form->loadData(); - - if( !wfRunHooks( 'EmailUserForm', array( &$form ) ) ){ - return false; + + if ( !Hooks::run( 'EmailUserForm', [ &$form ] ) ) { + return; } - - $wgOut->setPagetitle( wfMsg( 'emailpage' ) ); + $result = $form->show(); - - if( $result === true || ( $result instanceof Status && $result->isGood() ) ){ - $wgOut->setPagetitle( wfMsg( 'emailsent' ) ); - $wgOut->addWikiMsg( 'emailsenttext' ); - $wgOut->returnToMain( false, $this->mTargetObj->getUserPage() ); + + if ( $result === true || ( $result instanceof Status && $result->isGood() ) ) { + $out->setPageTitle( $this->msg( 'emailsent' ) ); + $out->addWikiMsg( 'emailsenttext', $this->mTarget ); + $out->returnToMain( false, $this->mTargetObj->getUserPage() ); } } /** * Validate target User * - * @param $target String: target user name - * @return User object on success or a string on error + * @param string $target Target user name + * @param User|null $sender User sending the email + * @return User|string User object on success or a string on error */ - public static function getTarget( $target ) { + public static function getTarget( $target, User $sender = null ) { + if ( $sender === null ) { + wfDeprecated( __METHOD__ . ' without specifying the sending user', '1.30' ); + } + if ( $target == '' ) { wfDebug( "Target is empty.\n" ); + return 'notarget'; } - + $nu = User::newFromName( $target ); - if( !$nu instanceof User || !$nu->getId() ) { + $error = self::validateTarget( $nu, $sender ); + + return $error ? $error : $nu; + } + + /** + * Validate target User + * + * @param User $target Target user + * @param User|null $sender User sending the email + * @return string Error message or empty string if valid. + * @since 1.30 + */ + public static function validateTarget( $target, User $sender = null ) { + if ( $sender === null ) { + wfDeprecated( __METHOD__ . ' without specifying the sending user', '1.30' ); + } + + if ( !$target instanceof User || !$target->getId() ) { wfDebug( "Target is invalid user.\n" ); + return 'notarget'; - } else if ( !$nu->isEmailConfirmed() ) { + } elseif ( !$target->isEmailConfirmed() ) { wfDebug( "User has no valid email.\n" ); + return 'noemail'; - } else if ( !$nu->canReceiveEmail() ) { + } elseif ( !$target->canReceiveEmail() ) { wfDebug( "User does not allow user emails.\n" ); + return 'nowikiemail'; + } elseif ( $sender !== null ) { + $blacklist = $target->getOption( 'email-blacklist', [] ); + if ( $blacklist ) { + $lookup = CentralIdLookup::factory(); + $senderId = $lookup->centralIdFromLocalUser( $sender ); + if ( $senderId !== 0 && in_array( $senderId, $blacklist ) ) { + wfDebug( "User does not allow user emails from this user.\n" ); + + return 'nowikiemail'; + } + } } - return $nu; + return ''; } /** * Check whether a user is allowed to send email * - * @param $user User object - * @param $editToken String: edit token - * @return null on success or string on error + * @param User $user + * @param string $editToken Edit token + * @param Config $config optional for backwards compatibility + * @return string|null Null on success or string on error */ - public static function getPermissionsError( $user, $editToken ) { - global $wgEnableEmail, $wgEnableUserEmail; - if( !$wgEnableEmail || !$wgEnableUserEmail ){ - return 'usermaildisabled'; + public static function getPermissionsError( $user, $editToken, Config $config = null ) { + if ( $config === null ) { + wfDebug( __METHOD__ . ' called without a Config instance passed to it' ); + $config = MediaWikiServices::getInstance()->getMainConfig(); } - - if( !$user->isAllowed( 'sendemail' ) ) { - return 'badaccess'; + if ( !$config->get( 'EnableEmail' ) || !$config->get( 'EnableUserEmail' ) ) { + return 'usermaildisabled'; } - - if( !$user->isEmailConfirmed() ){ + + // Run this before $user->isAllowed, to show appropriate message to anons (T160309) + if ( !$user->isEmailConfirmed() ) { return 'mailnologin'; } - if( $user->isBlockedFromEmailuser() ) { + if ( !$user->isAllowed( 'sendemail' ) ) { + return 'badaccess'; + } + + if ( $user->isBlockedFromEmailuser() ) { wfDebug( "User is blocked from sending e-mail.\n" ); + return "blockedemailuser"; } - if( $user->pingLimiter( 'emailuser' ) ) { + if ( $user->pingLimiter( 'emailuser' ) ) { wfDebug( "Ping limiter triggered.\n" ); + return 'actionthrottledtext'; } $hookErr = false; - wfRunHooks( 'UserCanSendEmail', array( &$user, &$hookErr ) ); - wfRunHooks( 'EmailUserPermissionsErrors', array( $user, $editToken, &$hookErr ) ); + + Hooks::run( 'UserCanSendEmail', [ &$user, &$hookErr ] ); + Hooks::run( 'EmailUserPermissionsErrors', [ $user, $editToken, &$hookErr ] ); + if ( $hookErr ) { return $hookErr; } @@ -216,88 +298,207 @@ class SpecialEmailUser extends UnlistedSpecialPage { return null; } + /** + * Form to ask for target user name. + * + * @param string $name User name submitted. + * @return string Form asking for user name. + */ + protected function userForm( $name ) { + $this->getOutput()->addModules( 'mediawiki.userSuggest' ); + $string = Html::openElement( + 'form', + [ 'method' => 'get', 'action' => wfScript(), 'id' => 'askusername' ] + ) . + Html::hidden( 'title', $this->getPageTitle()->getPrefixedText() ) . + Html::openElement( 'fieldset' ) . + Html::rawElement( 'legend', null, $this->msg( 'emailtarget' )->parse() ) . + Html::label( + $this->msg( 'emailusername' )->text(), + 'emailusertarget' + ) . ' ' . + Html::input( + 'target', + $name, + 'text', + [ + 'id' => 'emailusertarget', + 'class' => 'mw-autocomplete-user', // used by mediawiki.userSuggest + 'autofocus' => true, + 'size' => 30, + ] + ) . + ' ' . + Html::submitButton( $this->msg( 'emailusernamesubmit' )->text(), [] ) . + Html::closeElement( 'fieldset' ) . + Html::closeElement( 'form' ) . "\n"; + + return $string; + } + + /** + * Submit callback for an HTMLForm object, will simply call submit(). + * + * @since 1.20 + * @param array $data + * @param HTMLForm $form + * @return Status|bool + */ + public static function uiSubmit( array $data, HTMLForm $form ) { + return self::submit( $data, $form->getContext() ); + } + /** * Really send a mail. Permissions should have been checked using - * getPermissionsError(). It is probably also a good + * getPermissionsError(). It is probably also a good * idea to check the edit token and ping limiter in advance. * - * @return Mixed: Status object, or potentially a String on error - * or maybe even true on success if anything uses the EmailUser hook. + * @param array $data + * @param IContextSource $context + * @return Status|bool */ - public static function submit( $data ) { - global $wgUser, $wgUserEmailUseReplyTo; + public static function submit( array $data, IContextSource $context ) { + $config = $context->getConfig(); - $target = self::getTarget( $data['Target'] ); - if( !$target instanceof User ){ - return wfMsgExt( $target . 'text', 'parse' ); + $target = self::getTarget( $data['Target'], $context->getUser() ); + if ( !$target instanceof User ) { + // Messages used here: notargettext, noemailtext, nowikiemailtext + return Status::newFatal( $target . 'text' ); } - $to = new MailAddress( $target ); - $from = new MailAddress( $wgUser ); + + $to = MailAddress::newFromUser( $target ); + $from = MailAddress::newFromUser( $context->getUser() ); $subject = $data['Subject']; $text = $data['Text']; // Add a standard footer and trim up trailing newlines $text = rtrim( $text ) . "\n\n-- \n"; - $text .= wfMsgExt( - 'emailuserfooter', - array( 'content', 'parsemag' ), - array( $from->name, $to->name ) - ); + $text .= $context->msg( 'emailuserfooter', + $from->name, $to->name )->inContentLanguage()->text(); - $error = ''; - if( !wfRunHooks( 'EmailUser', array( &$to, &$from, &$subject, &$text, &$error ) ) ) { - return $error; + $error = false; + if ( !Hooks::run( 'EmailUser', [ &$to, &$from, &$subject, &$text, &$error ] ) ) { + if ( $error instanceof Status ) { + return $error; + } elseif ( $error === false || $error === '' || $error === [] ) { + // Possibly to tell HTMLForm to pretend there was no submission? + return false; + } elseif ( $error === true ) { + // Hook sent the mail itself and indicates success? + return Status::newGood(); + } elseif ( is_array( $error ) ) { + $status = Status::newGood(); + foreach ( $error as $e ) { + $status->fatal( $e ); + } + return $status; + } elseif ( $error instanceof MessageSpecifier ) { + return Status::newFatal( $error ); + } else { + // Ugh. Either a raw HTML string, or something that's supposed + // to be treated like one. + $type = is_object( $error ) ? get_class( $error ) : gettype( $error ); + wfDeprecated( "EmailUser hook returning a $type as \$error", '1.29' ); + return Status::newFatal( new ApiRawMessage( + [ '$1', Message::rawParam( (string)$error ) ], 'hookaborted' + ) ); + } } - - if( $wgUserEmailUseReplyTo ) { - // Put the generic wiki autogenerated address in the From: - // header and reserve the user for Reply-To. - // - // This is a bit ugly, but will serve to differentiate - // wiki-borne mails from direct mails and protects against - // SPF and bounce problems with some mailers (see below). - global $wgPasswordSender, $wgPasswordSenderName; - $mailFrom = new MailAddress( $wgPasswordSender, $wgPasswordSenderName ); + + if ( $config->get( 'UserEmailUseReplyTo' ) ) { + /** + * Put the generic wiki autogenerated address in the From: + * header and reserve the user for Reply-To. + * + * This is a bit ugly, but will serve to differentiate + * wiki-borne mails from direct mails and protects against + * SPF and bounce problems with some mailers (see below). + */ + $mailFrom = new MailAddress( $config->get( 'PasswordSender' ), + wfMessage( 'emailsender' )->inContentLanguage()->text() ); $replyTo = $from; } else { - // Put the sending user's e-mail address in the From: header. - // - // This is clean-looking and convenient, but has issues. - // One is that it doesn't as clearly differentiate the wiki mail - // from "directly" sent mails. - // - // Another is that some mailers (like sSMTP) will use the From - // address as the envelope sender as well. For open sites this - // can cause mails to be flunked for SPF violations (since the - // wiki server isn't an authorized sender for various users' - // domains) as well as creating a privacy issue as bounces - // containing the recipient's e-mail address may get sent to - // the sending user. + /** + * Put the sending user's e-mail address in the From: header. + * + * This is clean-looking and convenient, but has issues. + * One is that it doesn't as clearly differentiate the wiki mail + * from "directly" sent mails. + * + * Another is that some mailers (like sSMTP) will use the From + * address as the envelope sender as well. For open sites this + * can cause mails to be flunked for SPF violations (since the + * wiki server isn't an authorized sender for various users' + * domains) as well as creating a privacy issue as bounces + * containing the recipient's e-mail address may get sent to + * the sending user. + */ $mailFrom = $from; $replyTo = null; } - $status = UserMailer::send( $to, $mailFrom, $subject, $text, $replyTo ); + $status = UserMailer::send( $to, $mailFrom, $subject, $text, [ + 'replyTo' => $replyTo, + ] ); - if( !$status->isGood() ) { + if ( !$status->isGood() ) { return $status; } else { // if the user requested a copy of this mail, do this now, - // unless they are emailing themselves, in which case one + // unless they are emailing themselves, in which case one // copy of the message is sufficient. if ( $data['CCMe'] && $to != $from ) { - $cc_subject = wfMsg( - 'emailccsubject', - $target->getName(), - $subject - ); - wfRunHooks( 'EmailUserCC', array( &$from, &$from, &$cc_subject, &$text ) ); - $ccStatus = UserMailer::send( $from, $from, $cc_subject, $text ); + $ccTo = $from; + $ccFrom = $from; + $ccSubject = $context->msg( 'emailccsubject' )->rawParams( + $target->getName(), $subject )->text(); + $ccText = $text; + + Hooks::run( 'EmailUserCC', [ &$ccTo, &$ccFrom, &$ccSubject, &$ccText ] ); + + if ( $config->get( 'UserEmailUseReplyTo' ) ) { + $mailFrom = new MailAddress( + $config->get( 'PasswordSender' ), + wfMessage( 'emailsender' )->inContentLanguage()->text() + ); + $replyTo = $ccFrom; + } else { + $mailFrom = $ccFrom; + $replyTo = null; + } + + $ccStatus = UserMailer::send( + $ccTo, $mailFrom, $ccSubject, $ccText, [ + 'replyTo' => $replyTo, + ] ); $status->merge( $ccStatus ); } - wfRunHooks( 'EmailUserComplete', array( $to, $from, $subject, $text ) ); + Hooks::run( 'EmailUserComplete', [ $to, $from, $subject, $text ] ); + return $status; } } + + /** + * Return an array of subpages beginning with $search that this special page will accept. + * + * @param string $search Prefix to search for + * @param int $limit Maximum number of results to return (usually 10) + * @param int $offset Number of results to skip (usually 0) + * @return string[] Matching subpages + */ + public function prefixSearchSubpages( $search, $limit, $offset ) { + $user = User::newFromName( $search ); + if ( !$user ) { + // No prefix suggestion for invalid user + return []; + } + // Autocomplete subpage as user list - public to allow caching + return UserNamePrefixSearch::search( 'public', $search, $limit, $offset ); + } + + protected function getGroupName() { + return 'users'; + } }