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