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