Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 145
0.00% covered (danger)
0.00%
0 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
ApiWikiLove
0.00% covered (danger)
0.00%
0 / 145
0.00% covered (danger)
0.00%
0 / 8
552
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 65
0.00% covered (danger)
0.00%
0 / 1
156
 saveInDb
0.00% covered (danger)
0.00%
0 / 26
0.00% covered (danger)
0.00%
0 / 1
30
 emailUser
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
2
 getAllowedParams
0.00% covered (danger)
0.00%
0 / 31
0.00% covered (danger)
0.00%
0 / 1
2
 needsToken
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isWriteMode
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getExamplesMessages
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace MediaWiki\Extension\WikiLove;
4
5use ApiBase;
6use ApiMain;
7use ApiMessage;
8use ChangeTags;
9use DerivativeContext;
10use ExtensionRegistry;
11use LqtDispatch;
12use MediaWiki\Deferred\DeferredUpdates;
13use MediaWiki\Parser\Sanitizer;
14use MediaWiki\Request\DerivativeRequest;
15use MediaWiki\Title\Title;
16use MediaWiki\User\User;
17use ParserFactory;
18use Wikimedia\ParamValidator\ParamValidator;
19use Wikimedia\Rdbms\DBQueryError;
20use Wikimedia\Rdbms\IConnectionProvider;
21
22class ApiWikiLove extends ApiBase {
23    /** @var IConnectionProvider */
24    private $dbProvider;
25
26    /** @var ParserFactory */
27    private $parserFactory;
28
29    /**
30     * @param ApiMain $main
31     * @param string $action
32     * @param IConnectionProvider $dbProvider
33     * @param ParserFactory $parserFactory
34     */
35    public function __construct(
36        ApiMain $main,
37        $action,
38        IConnectionProvider $dbProvider,
39        ParserFactory $parserFactory
40    ) {
41        parent::__construct( $main, $action );
42        $this->dbProvider = $dbProvider;
43        $this->parserFactory = $parserFactory;
44    }
45
46    /** @inheritDoc */
47    public function execute() {
48        $params = $this->extractRequestParams();
49
50        // In some cases we need the wiki mark-up stripped from the subject
51        $strippedSubject = $this->parserFactory->getInstance()->stripSectionName( $params['subject'] );
52
53        $title = Title::newFromText( $params['title'] );
54        if ( $title === null ) {
55            $this->dieWithError( [ 'nosuchusershort', $params['title'] ], 'nosuchuser' );
56        }
57
58        $talk = Hooks::getUserTalkPage( $title, $this->getUser() );
59        // getUserTalkPage() returns an ApiMessage on error
60        if ( $talk instanceof ApiMessage ) {
61            $this->dieWithError( $talk );
62        }
63
64        if ( $this->getConfig()->get( 'WikiLoveLogging' ) ) {
65            $this->saveInDb( $talk, $params['subject'], $params['message'], $params['type'],
66                isset( $params['email'] ) ? 1 : 0 );
67        }
68
69        // Create edit summary
70        $summary = $this->msg( 'wikilove-summary', $strippedSubject )->inContentLanguage()
71            ->text();
72
73        $extReg = ExtensionRegistry::getInstance();
74
75        // If LQT is installed and enabled, use it.
76        if ( $extReg->isLoaded( 'Liquid Threads' ) && LqtDispatch::isLqtPage( $talk ) ) {
77            $apiParamArray = [
78                'action' => 'threadaction',
79                'threadaction' => 'newthread',
80                'talkpage' => $talk->getFullText(),
81                'subject' => $params['subject'],
82                'reason' => $summary,
83                'text' => $params['text'],
84                'token' => $params['token']
85            ];
86        // If Flow is installed and enabled, use it.
87        } elseif ( $extReg->isLoaded( 'Flow' ) && $talk->hasContentModel( CONTENT_MODEL_FLOW_BOARD ) ) {
88            $apiParamArray = [
89                'action' => 'flow',
90                'submodule' => 'new-topic',
91                'page' => $talk->getFullText(),
92                'nttopic' => $params['subject'],
93                'ntcontent' => $params['text'],
94                'token' => $params['token']
95            ];
96        } else {
97            // Requires MediaWiki 1.19 or later
98            $apiParamArray = [
99                'action' => 'edit',
100                'title' => $talk->getFullText(),
101                'section' => 'new',
102                'sectiontitle' => $params['subject'],
103                'text' => $params['text'],
104                'token' => $params['token'],
105                'summary' => $summary,
106                'tags' => implode( '|', $params['tags'] ?? [] ),
107                'notminor' => true
108            ];
109        }
110
111        $api = new ApiMain(
112            new DerivativeRequest(
113                $this->getRequest(),
114                $apiParamArray,
115                /* $wasPosted */ true
116            ),
117            /* $enableWrite */ true
118        );
119
120        $api->execute();
121
122        $result = $api->getResult()->getResultData();
123        if ( isset( $result['edit'] ) && $result['edit']['result'] === "Success" ) {
124            $revId = $result['edit']['newrevid'];
125            DeferredUpdates::addCallableUpdate( static function () use ( $revId ) {
126                ChangeTags::addTags( "wikilove", null, $revId );
127            } );
128        }
129
130        if ( isset( $params['email'] ) ) {
131            $this->emailUser( $talk, $strippedSubject, $params['email'], $params['token'] );
132        }
133
134        $this->getResult()->addValue( 'redirect', 'pageName', $talk->getPrefixedDBkey() );
135        $this->getResult()->addValue( 'redirect', 'fragment',
136            Sanitizer::escapeIdForLink( $strippedSubject )
137        );
138        // note that we cannot use Title::makeTitle here as it doesn't sanitize the fragment
139    }
140
141    /**
142     * @param Title $talk
143     * @param string $subject
144     * @param string $message
145     * @param string $type
146     * @param int $email
147     * @return void
148     */
149    private function saveInDb( $talk, $subject, $message, $type, $email ) {
150        $dbw = $this->dbProvider->getPrimaryDatabase();
151        $receiver = User::newFromName( $talk->getSubjectPage()->getBaseText() );
152        if ( $receiver === false || $receiver->isAnon() || $receiver->isTemp() ) {
153            $this->addWarning( 'apiwarn-wikilove-ignoringunregistered' );
154            return;
155        }
156
157        $user = $this->getUser();
158        $values = [
159            'wll_timestamp' => $dbw->timestamp(),
160            'wll_sender' => $user->getId(),
161            'wll_sender_editcount' => $user->getEditCount(),
162            'wll_sender_registration' => $user->getRegistration(),
163            'wll_receiver' => $receiver->getId(),
164            'wll_receiver_editcount' => $receiver->getEditCount(),
165            'wll_receiver_registration' => $receiver->getRegistration(),
166            'wll_type' => $type,
167            'wll_subject' => $subject,
168            'wll_message' => $message,
169            'wll_email' => $email,
170        ];
171
172        try {
173            $dbw->newInsertQueryBuilder()
174                ->insertInto( 'wikilove_log' )
175                ->row( $values )
176                ->caller( __METHOD__ )
177                ->execute();
178        } catch ( DBQueryError $dbqe ) {
179            $this->addWarning( 'Action was not logged' );
180        }
181    }
182
183    /**
184     * @param Title $talk
185     * @param string $subject
186     * @param string $text
187     * @param string $token
188     */
189    private function emailUser( $talk, $subject, $text, $token ) {
190        $context = new DerivativeContext( $this->getContext() );
191        $context->setRequest( new DerivativeRequest(
192            $this->getRequest(),
193            [
194                'action' => 'emailuser',
195                'target' => User::newFromName( $talk->getSubjectPage()->getBaseText() )->getName(),
196                'subject' => $subject,
197                'text' => $text,
198                'token' => $token
199            ],
200            true
201        ) );
202        $api = new ApiMain( $context, true );
203        $api->execute();
204    }
205
206    /** @inheritDoc */
207    public function getAllowedParams() {
208        return [
209            'title' => [
210                ParamValidator::PARAM_TYPE => 'string',
211                ParamValidator::PARAM_REQUIRED => true,
212            ],
213            'text' => [
214                ParamValidator::PARAM_TYPE => 'string',
215                ParamValidator::PARAM_REQUIRED => true,
216            ],
217            'message' => [
218                ParamValidator::PARAM_TYPE => 'string',
219            ],
220            'token' => [
221                ParamValidator::PARAM_TYPE => 'string',
222                ParamValidator::PARAM_REQUIRED => true,
223            ],
224            'subject' => [
225                ParamValidator::PARAM_TYPE => 'string',
226                ParamValidator::PARAM_REQUIRED => true,
227            ],
228            'type' => [
229                ParamValidator::PARAM_TYPE => 'string',
230            ],
231            'email' => [
232                ParamValidator::PARAM_TYPE => 'string',
233            ],
234            'tags' => [
235                ParamValidator::PARAM_TYPE => 'tags',
236                ParamValidator::PARAM_ISMULTI => true,
237            ],
238        ];
239    }
240
241    /** @inheritDoc */
242    public function needsToken() {
243        return 'csrf';
244    }
245
246    /** @inheritDoc */
247    public function isWriteMode() {
248        return true;
249    }
250
251    /** @inheritDoc */
252    protected function getExamplesMessages() {
253        return [
254            'action=wikilove&title=User:Dummy&text=Love&subject=Hi&token=123ABC'
255                => 'apihelp-wikilove-example-1',
256        ];
257    }
258}