Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 146
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 / 146
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 / 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 ExtensionRegistry;
10use LqtDispatch;
11use MediaWiki\Context\DerivativeContext;
12use MediaWiki\Deferred\DeferredUpdates;
13use MediaWiki\Parser\Sanitizer;
14use MediaWiki\Permissions\PermissionManager;
15use MediaWiki\Request\DerivativeRequest;
16use MediaWiki\Title\Title;
17use MediaWiki\User\User;
18use ParserFactory;
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                ChangeTags::addTags( "wikilove", null, $revId );
122            } );
123        }
124
125        if ( isset( $params['email'] ) ) {
126            $this->emailUser( $talk, $strippedSubject, $params['email'], $params['token'] );
127        }
128
129        $this->getResult()->addValue( 'redirect', 'pageName', $talk->getPrefixedDBkey() );
130        $this->getResult()->addValue( 'redirect', 'fragment',
131            Sanitizer::escapeIdForLink( $strippedSubject )
132        );
133        // note that we cannot use Title::makeTitle here as it doesn't sanitize the fragment
134    }
135
136    private function saveInDb( Title $talk, string $subject, string $message, string $type, int $email ): void {
137        $dbw = $this->dbProvider->getPrimaryDatabase();
138        $receiver = User::newFromName( $talk->getSubjectPage()->getBaseText() );
139        if ( $receiver === false || $receiver->isAnon() || $receiver->isTemp() ) {
140            $this->addWarning( 'apiwarn-wikilove-ignoringunregistered' );
141            return;
142        }
143
144        $user = $this->getUser();
145        $values = [
146            'wll_timestamp' => $dbw->timestamp(),
147            'wll_sender' => $user->getId(),
148            'wll_sender_editcount' => $user->getEditCount(),
149            'wll_sender_registration' => $user->getRegistration(),
150            'wll_receiver' => $receiver->getId(),
151            'wll_receiver_editcount' => $receiver->getEditCount(),
152            'wll_receiver_registration' => $receiver->getRegistration(),
153            'wll_type' => $type,
154            'wll_subject' => $subject,
155            'wll_message' => $message,
156            'wll_email' => $email,
157        ];
158
159        try {
160            $dbw->newInsertQueryBuilder()
161                ->insertInto( 'wikilove_log' )
162                ->row( $values )
163                ->caller( __METHOD__ )
164                ->execute();
165        } catch ( DBQueryError $dbqe ) {
166            $this->addWarning( 'Action was not logged' );
167        }
168    }
169
170    private function emailUser( Title $talk, string $subject, string $text, string $token ): void {
171        $context = new DerivativeContext( $this->getContext() );
172        $context->setRequest( new DerivativeRequest(
173            $this->getRequest(),
174            [
175                'action' => 'emailuser',
176                'target' => User::newFromName( $talk->getSubjectPage()->getBaseText() )->getName(),
177                'subject' => $subject,
178                'text' => $text,
179                'token' => $token
180            ],
181            true
182        ) );
183        $api = new ApiMain( $context, true );
184        $api->execute();
185    }
186
187    /** @inheritDoc */
188    public function getAllowedParams() {
189        return [
190            'title' => [
191                ParamValidator::PARAM_TYPE => 'string',
192                ParamValidator::PARAM_REQUIRED => true,
193            ],
194            'text' => [
195                ParamValidator::PARAM_TYPE => 'string',
196                ParamValidator::PARAM_REQUIRED => true,
197            ],
198            'message' => [
199                ParamValidator::PARAM_TYPE => 'string',
200            ],
201            'token' => [
202                ParamValidator::PARAM_TYPE => 'string',
203                ParamValidator::PARAM_REQUIRED => true,
204            ],
205            'subject' => [
206                ParamValidator::PARAM_TYPE => 'string',
207                ParamValidator::PARAM_REQUIRED => true,
208            ],
209            'type' => [
210                ParamValidator::PARAM_TYPE => 'string',
211            ],
212            'email' => [
213                ParamValidator::PARAM_TYPE => 'string',
214            ],
215            'tags' => [
216                ParamValidator::PARAM_TYPE => 'tags',
217                ParamValidator::PARAM_ISMULTI => true,
218            ],
219        ];
220    }
221
222    /** @inheritDoc */
223    public function needsToken() {
224        return 'csrf';
225    }
226
227    /** @inheritDoc */
228    public function isWriteMode() {
229        return true;
230    }
231
232    /** @inheritDoc */
233    protected function getExamplesMessages() {
234        return [
235            'action=wikilove&title=User:Dummy&text=Love&subject=Hi&token=123ABC'
236                => 'apihelp-wikilove-example-1',
237        ];
238    }
239}