Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 99
0.00% covered (danger)
0.00%
0 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
SpecialThanks
0.00% covered (danger)
0.00%
0 / 99
0.00% covered (danger)
0.00%
0 / 9
1122
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setParameter
0.00% covered (danger)
0.00%
0 / 23
0.00% covered (danger)
0.00%
0 / 1
132
 getFormFields
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
2
 preHtml
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
56
 alterForm
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
20
 getDisplayFormat
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 onSubmit
0.00% covered (danger)
0.00%
0 / 43
0.00% covered (danger)
0.00%
0 / 1
42
 doesWrites
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isListed
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace MediaWiki\Extension\Thanks;
4
5use MediaWiki\Api\ApiMain;
6use MediaWiki\Api\ApiUsageException;
7use MediaWiki\Html\Html;
8use MediaWiki\HTMLForm\HTMLForm;
9use MediaWiki\Linker\LinkRenderer;
10use MediaWiki\Request\DerivativeRequest;
11use MediaWiki\SpecialPage\FormSpecialPage;
12use MediaWiki\SpecialPage\SpecialPage;
13use MediaWiki\Status\Status;
14use MediaWiki\User\UserFactory;
15use MediaWiki\User\UserRigorOptions;
16
17class SpecialThanks extends FormSpecialPage {
18
19    /**
20     * 'rev' for revision, 'log' for log entry, or 'flow' for Flow comment,
21     * null if no ID is specified
22     */
23    protected ?string $type;
24
25    /**
26     * Revision or Log ID ('0' = invalid) or Flow UUID
27     */
28    protected ?string $id;
29
30    public function __construct(
31        private readonly LinkRenderer $linkRenderer,
32        private readonly UserFactory $userFactory,
33    ) {
34        parent::__construct( 'Thanks' );
35        $this->id = null;
36    }
37
38    /**
39     * Set the type and ID or UUID of the request.
40     * @param string $par The subpage name.
41     */
42    protected function setParameter( $par ) {
43        if ( $par === null || $par === '' ) {
44            $this->type = null;
45            return;
46        }
47
48        $tokens = explode( '/', $par );
49        if ( $tokens[0] === 'Flow' ) {
50            if ( count( $tokens ) === 1 || $tokens[1] === '' ) {
51                $this->type = null;
52                return;
53            }
54            $this->type = 'flow';
55            $this->id = $tokens[1];
56            return;
57        }
58
59        if ( strtolower( $tokens[0] ) === 'log' ) {
60            $this->type = 'log';
61            // Make sure there's a numeric ID specified as the subpage.
62            if ( count( $tokens ) === 1 || $tokens[1] === '' || !( ctype_digit( $tokens[1] ) ) ) {
63                $this->id = '0';
64                return;
65            }
66            $this->id = $tokens[1];
67            return;
68        }
69
70        $this->type = 'rev';
71        if ( !( ctype_digit( $par ) ) ) {
72            // Revision ID is not an integer.
73            $this->id = '0';
74            return;
75        }
76
77        $this->id = $par;
78    }
79
80    /**
81     * HTMLForm fields
82     * @return string[][]
83     */
84    protected function getFormFields(): array {
85        return [
86            'id' => [
87                'id' => 'mw-thanks-form-id',
88                'name' => 'id',
89                'type' => 'hidden',
90                'default' => $this->id ?? '',
91            ],
92            'type' => [
93                'id' => 'mw-thanks-form-type',
94                'name' => 'type',
95                'type' => 'hidden',
96                'default' => $this->type ?? '',
97            ],
98        ];
99    }
100
101    /**
102     * Return the confirmation or error message.
103     */
104    protected function preHtml(): string {
105        if ( $this->type === null ) {
106            $msgKey = 'thanks-error-no-id-specified';
107        } elseif ( $this->type === 'rev' && $this->id === '0' ) {
108            $msgKey = 'thanks-error-invalidrevision';
109        } elseif ( $this->type === 'log' && $this->id === '0' ) {
110            $msgKey = 'thanks-error-invalid-log-id';
111        } elseif ( $this->type === 'flow' ) {
112            $msgKey = 'flow-thanks-confirmation-special';
113        } else {
114            // The following messages are used here
115            // * thanks-confirmation-special-rev
116            // * thanks-confirmation-special-log
117            $msgKey = 'thanks-confirmation-special-' . $this->type;
118        }
119        return Html::element( 'p', [], $this->msg( $msgKey )->text() );
120    }
121
122    /**
123     * Format the submission form.
124     * @param HTMLForm $form The form object to modify.
125     */
126    protected function alterForm( HTMLForm $form ) {
127        if ( $this->type === null
128            || ( in_array( $this->type, [ 'rev', 'log' ] ) && $this->id === '0' )
129        ) {
130            $form->suppressDefaultSubmit( true );
131        } else {
132            $form->setSubmitTextMsg( 'thanks-submit' );
133        }
134    }
135
136    /** @inheritDoc */
137    protected function getDisplayFormat(): string {
138        return 'ooui';
139    }
140
141    /**
142     * Call the API internally.
143     * @param string[] $data The form data.
144     */
145    public function onSubmit( array $data ): Status {
146        if ( !isset( $data['id'] ) ) {
147            return Status::newFatal( 'thanks-error-invalidrevision' );
148        }
149
150        if ( in_array( $this->type, [ 'rev', 'log' ] ) ) {
151            $requestData = [
152                'action' => 'thank',
153                $this->type => (int)$data['id'],
154                'source' => 'specialpage',
155                'token' => $this->getOutput()->getCsrfTokenSet()->getToken(),
156            ];
157            $msgKey = 'thanks-thanked-notice';
158        } else {
159            $requestData = [
160                'action' => 'flowthank',
161                'postid' => $data['id'],
162                'token' => $this->getOutput()->getCsrfTokenSet()->getToken(),
163            ];
164            $msgKey = 'flow-thanks-thanked-notice';
165        }
166
167        $request = new DerivativeRequest(
168            $this->getRequest(),
169            $requestData,
170            true
171        );
172
173        $api = new ApiMain(
174            $request,
175            true
176        );
177
178        try {
179            $api->execute();
180        } catch ( ApiUsageException $e ) {
181            return Status::wrap( $e->getStatusValue() );
182        }
183
184        $result = $api->getResult()->getResultData( [ 'result' ] );
185        $sender = $this->getUser();
186        $recipient = $this->userFactory->newFromName( $result['recipient'], UserRigorOptions::RIGOR_NONE );
187        '@phan-var \MediaWiki\User\User $recipient';
188        $link = $this->linkRenderer->makeUserLink( $recipient, $this->getContext() );
189
190        // Display a message to the user.
191        $msg = $this->msg( $msgKey )
192            ->rawParams( $link )
193            ->params( $recipient->getName(), $sender->getName() );
194        $out = $this->getOutput();
195        $out->addHTML( $msg->parse() );
196        if ( $this->type === 'rev' ) {
197            $out->addReturnTo( SpecialPage::getTitleFor( 'Diff', $data['id'] ) );
198        } elseif ( $this->type === 'log' ) {
199            $out->addReturnTo( SpecialPage::getTitleFor( 'Redirect', 'logid/' . $data['id'] ) );
200        }
201        return Status::newGood();
202    }
203
204    /** @inheritDoc */
205    public function doesWrites(): bool {
206        return true;
207    }
208
209    /** @inheritDoc */
210    public function isListed(): bool {
211        return false;
212    }
213}