wikimedia/mediawiki-extensions-WikiLove

View on GitHub
includes/ApiWikiLove.php

Summary

Maintainability
C
7 hrs
Test Coverage
<?php

namespace MediaWiki\Extension\WikiLove;

use ApiBase;
use ApiMain;
use ApiMessage;
use ChangeTags;
use ExtensionRegistry;
use LqtDispatch;
use MediaWiki\Context\DerivativeContext;
use MediaWiki\Deferred\DeferredUpdates;
use MediaWiki\Parser\Sanitizer;
use MediaWiki\Permissions\PermissionManager;
use MediaWiki\Request\DerivativeRequest;
use MediaWiki\Title\Title;
use MediaWiki\User\User;
use ParserFactory;
use Wikimedia\ParamValidator\ParamValidator;
use Wikimedia\Rdbms\DBQueryError;
use Wikimedia\Rdbms\IConnectionProvider;

class ApiWikiLove extends ApiBase {
    private IConnectionProvider $dbProvider;
    private ParserFactory $parserFactory;
    private PermissionManager $permissionManager;

    public function __construct(
        ApiMain $main,
        string $action,
        IConnectionProvider $dbProvider,
        ParserFactory $parserFactory,
        PermissionManager $permissionManager
    ) {
        parent::__construct( $main, $action );
        $this->dbProvider = $dbProvider;
        $this->parserFactory = $parserFactory;
        $this->permissionManager = $permissionManager;
    }

    /** @inheritDoc */
    public function execute() {
        $params = $this->extractRequestParams();

        // In some cases we need the wiki mark-up stripped from the subject
        $strippedSubject = $this->parserFactory->getInstance()->stripSectionName( $params['subject'] );

        $title = Title::newFromText( $params['title'] );
        if ( $title === null ) {
            $this->dieWithError( [ 'nosuchusershort', $params['title'] ], 'nosuchuser' );
        }

        $talk = Hooks::getUserTalkPage( $this->permissionManager, $title, $this->getUser() );
        // getUserTalkPage() returns an ApiMessage on error
        if ( $talk instanceof ApiMessage ) {
            $this->dieWithError( $talk );
        }

        if ( $this->getConfig()->get( 'WikiLoveLogging' ) ) {
            $this->saveInDb( $talk, $params['subject'], $params['message'], $params['type'],
                isset( $params['email'] ) ? 1 : 0 );
        }

        // Create edit summary
        $summary = $this->msg( 'wikilove-summary', $strippedSubject )->inContentLanguage()
            ->text();

        $extReg = ExtensionRegistry::getInstance();

        // If LQT is installed and enabled, use it.
        if ( $extReg->isLoaded( 'Liquid Threads' ) && LqtDispatch::isLqtPage( $talk ) ) {
            $apiParamArray = [
                'action' => 'threadaction',
                'threadaction' => 'newthread',
                'talkpage' => $talk->getFullText(),
                'subject' => $params['subject'],
                'reason' => $summary,
                'text' => $params['text'],
                'token' => $params['token']
            ];
        // If Flow is installed and enabled, use it.
        } elseif ( $extReg->isLoaded( 'Flow' ) && $talk->hasContentModel( CONTENT_MODEL_FLOW_BOARD ) ) {
            $apiParamArray = [
                'action' => 'flow',
                'submodule' => 'new-topic',
                'page' => $talk->getFullText(),
                'nttopic' => $params['subject'],
                'ntcontent' => $params['text'],
                'token' => $params['token']
            ];
        } else {
            // Requires MediaWiki 1.19 or later
            $apiParamArray = [
                'action' => 'edit',
                'title' => $talk->getFullText(),
                'section' => 'new',
                'sectiontitle' => $params['subject'],
                'text' => $params['text'],
                'token' => $params['token'],
                'summary' => $summary,
                'tags' => implode( '|', $params['tags'] ?? [] ),
                'notminor' => true
            ];
        }

        $api = new ApiMain(
            new DerivativeRequest(
                $this->getRequest(),
                $apiParamArray,
                /* $wasPosted */ true
            ),
            /* $enableWrite */ true
        );

        $api->execute();

        $result = $api->getResult()->getResultData();
        if ( isset( $result['edit'] ) && $result['edit']['result'] === "Success" ) {
            $revId = $result['edit']['newrevid'];
            DeferredUpdates::addCallableUpdate( static function () use ( $revId ) {
                ChangeTags::addTags( "wikilove", null, $revId );
            } );
        }

        if ( isset( $params['email'] ) ) {
            $this->emailUser( $talk, $strippedSubject, $params['email'], $params['token'] );
        }

        $this->getResult()->addValue( 'redirect', 'pageName', $talk->getPrefixedDBkey() );
        $this->getResult()->addValue( 'redirect', 'fragment',
            Sanitizer::escapeIdForLink( $strippedSubject )
        );
        // note that we cannot use Title::makeTitle here as it doesn't sanitize the fragment
    }

    private function saveInDb( Title $talk, string $subject, string $message, string $type, int $email ): void {
        $dbw = $this->dbProvider->getPrimaryDatabase();
        $receiver = User::newFromName( $talk->getSubjectPage()->getBaseText() );
        if ( $receiver === false || $receiver->isAnon() || $receiver->isTemp() ) {
            $this->addWarning( 'apiwarn-wikilove-ignoringunregistered' );
            return;
        }

        $user = $this->getUser();
        $values = [
            'wll_timestamp' => $dbw->timestamp(),
            'wll_sender' => $user->getId(),
            'wll_sender_editcount' => $user->getEditCount(),
            'wll_sender_registration' => $user->getRegistration(),
            'wll_receiver' => $receiver->getId(),
            'wll_receiver_editcount' => $receiver->getEditCount(),
            'wll_receiver_registration' => $receiver->getRegistration(),
            'wll_type' => $type,
            'wll_subject' => $subject,
            'wll_message' => $message,
            'wll_email' => $email,
        ];

        try {
            $dbw->newInsertQueryBuilder()
                ->insertInto( 'wikilove_log' )
                ->row( $values )
                ->caller( __METHOD__ )
                ->execute();
        } catch ( DBQueryError $dbqe ) {
            $this->addWarning( 'Action was not logged' );
        }
    }

    private function emailUser( Title $talk, string $subject, string $text, string $token ): void {
        $context = new DerivativeContext( $this->getContext() );
        $context->setRequest( new DerivativeRequest(
            $this->getRequest(),
            [
                'action' => 'emailuser',
                'target' => User::newFromName( $talk->getSubjectPage()->getBaseText() )->getName(),
                'subject' => $subject,
                'text' => $text,
                'token' => $token
            ],
            true
        ) );
        $api = new ApiMain( $context, true );
        $api->execute();
    }

    /** @inheritDoc */
    public function getAllowedParams() {
        return [
            'title' => [
                ParamValidator::PARAM_TYPE => 'string',
                ParamValidator::PARAM_REQUIRED => true,
            ],
            'text' => [
                ParamValidator::PARAM_TYPE => 'string',
                ParamValidator::PARAM_REQUIRED => true,
            ],
            'message' => [
                ParamValidator::PARAM_TYPE => 'string',
            ],
            'token' => [
                ParamValidator::PARAM_TYPE => 'string',
                ParamValidator::PARAM_REQUIRED => true,
            ],
            'subject' => [
                ParamValidator::PARAM_TYPE => 'string',
                ParamValidator::PARAM_REQUIRED => true,
            ],
            'type' => [
                ParamValidator::PARAM_TYPE => 'string',
            ],
            'email' => [
                ParamValidator::PARAM_TYPE => 'string',
            ],
            'tags' => [
                ParamValidator::PARAM_TYPE => 'tags',
                ParamValidator::PARAM_ISMULTI => true,
            ],
        ];
    }

    /** @inheritDoc */
    public function needsToken() {
        return 'csrf';
    }

    /** @inheritDoc */
    public function isWriteMode() {
        return true;
    }

    /** @inheritDoc */
    protected function getExamplesMessages() {
        return [
            'action=wikilove&title=User:Dummy&text=Love&subject=Hi&token=123ABC'
                => 'apihelp-wikilove-example-1',
        ];
    }
}