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 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
SpecialTemplateSandbox
0.00% covered (danger)
0.00%
0 / 147
0.00% covered (danger)
0.00%
0 / 7
1722
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 getGroupName
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 / 82
0.00% covered (danger)
0.00%
0 / 1
240
 validatePageParam
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
30
 validateRevidParam
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
30
 validatePrefixParam
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
56
 onSubmit
0.00% covered (danger)
0.00%
0 / 26
0.00% covered (danger)
0.00%
0 / 1
56
1<?php
2
3namespace MediaWiki\Extension\TemplateSandbox;
4
5use MediaWiki\Content\Content;
6use MediaWiki\Content\IContentHandlerFactory;
7use MediaWiki\Content\Renderer\ContentRenderer;
8use MediaWiki\EditPage\EditPage;
9use MediaWiki\Html\Html;
10use MediaWiki\HTMLForm\HTMLForm;
11use MediaWiki\Page\WikiPageFactory;
12use MediaWiki\Parser\ParserOutput;
13use MediaWiki\Revision\RevisionLookup;
14use MediaWiki\Revision\RevisionRecord;
15use MediaWiki\Revision\SlotRecord;
16use MediaWiki\SpecialPage\SpecialPage;
17use MediaWiki\Status\Status;
18use MediaWiki\Title\Title;
19use MediaWiki\User\TempUser\TempUserConfig;
20
21class SpecialTemplateSandbox extends SpecialPage {
22    /** @var string[] */
23    private $prefixes = [];
24
25    /**
26     * @var null|ParserOutput
27     */
28    private $output = null;
29
30    /** @var RevisionLookup */
31    private $revisionLookup;
32
33    /** @var IContentHandlerFactory */
34    private $contentHandlerFactory;
35
36    /** @var WikiPageFactory */
37    private $wikiPageFactory;
38
39    /** @var ContentRenderer */
40    private $contentRenderer;
41
42    /** @var TempUserConfig */
43    private $tempUserConfig;
44
45    /**
46     * @param RevisionLookup $revisionLookup
47     * @param IContentHandlerFactory $contentHandlerFactory
48     * @param WikiPageFactory $wikiPageFactory
49     * @param ContentRenderer $contentRenderer
50     */
51    public function __construct(
52        RevisionLookup $revisionLookup,
53        IContentHandlerFactory $contentHandlerFactory,
54        WikiPageFactory $wikiPageFactory,
55        ContentRenderer $contentRenderer,
56        TempUserConfig $tempUserConfig
57    ) {
58        parent::__construct( 'TemplateSandbox' );
59        $this->revisionLookup = $revisionLookup;
60        $this->contentHandlerFactory = $contentHandlerFactory;
61        $this->wikiPageFactory = $wikiPageFactory;
62        $this->contentRenderer = $contentRenderer;
63        $this->tempUserConfig = $tempUserConfig;
64    }
65
66    /** @inheritDoc */
67    protected function getGroupName() {
68        return 'wiki';
69    }
70
71    /**
72     * @param string|null $par
73     */
74    public function execute( $par ) {
75        $this->setHeaders();
76        $this->addHelpLink( 'Help:Extension:TemplateSandbox' );
77        $this->checkPermissions();
78
79        $request = $this->getRequest();
80
81        if ( $par !== null && !$request->getCheck( 'page' ) ) {
82            $request->setVal( 'page', $par );
83        }
84
85        if ( $this->tempUserConfig->isEnabled() && $this->getUser()->isAnon() ) {
86            // Don't expose IP address for anonymous users if using temp accounts.
87            $default_prefix = null;
88        } else {
89            $default_prefix = Title::makeTitle( NS_USER,
90                $this->getUser()->getName() . '/' . $this->msg( 'templatesandbox-suffix' )->plain()
91            )->getPrefixedText();
92        }
93
94        $form = HTMLForm::factory( 'ooui', [
95            'prefix' => [
96                'type' => 'text',
97                'name' => 'prefix',
98                'default' => $default_prefix,
99                'label-message' => 'templatesandbox-prefix-label',
100                'validation-callback' => [ $this, 'validatePrefixParam' ],
101            ],
102
103            'page' => [
104                'type' => 'text',
105                'name' => 'page',
106                'label-message' => 'templatesandbox-page-label',
107                'validation-callback' => [ $this, 'validatePageParam' ],
108            ],
109
110            'revid' => [
111                'type' => 'int',
112                'name' => 'revid',
113                'label-message' => 'templatesandbox-revid-label',
114                'validation-callback' => [ $this, 'validateRevidParam' ],
115            ],
116
117            'text' => [
118                'type' => 'textarea',
119                'name' => 'text',
120                'label-message' => 'templatesandbox-text-label',
121                'useeditfont' => true,
122                'rows' => 5,
123            ],
124        ], $this->getContext() );
125        $form->setSubmitCallback( [ $this, 'onSubmit' ] );
126        $form->setWrapperLegendMsg( 'templatesandbox-legend' );
127        $form->addHeaderHtml( $this->msg( 'templatesandbox-text' )->parseAsBlock() );
128        $form->setSubmitTextMsg( 'templatesandbox-submit' );
129
130        $form->prepareForm();
131        if ( $request->getCheck( 'page' ) || $request->getCheck( 'revid' ) ) {
132            $form->displayForm( $form->tryAuthorizedSubmit() );
133        } else {
134            $form->displayForm( false );
135        }
136
137        $user = $this->getUser();
138        $output = $this->getOutput();
139        $error = false;
140        if ( $this->getRequest()->wasPosted() ) {
141            if ( $user->isAnon() && !$user->isAllowed( 'edit' ) ) {
142                $error = 'templatesandbox-fail-post-anon';
143            } elseif ( !$user->matchEditToken( $request->getVal( 'wpEditToken' ), '', $request ) ) {
144                $error = 'templatesandbox-fail-post';
145            }
146        }
147        if ( $error !== false ) {
148            $output->addHTML(
149                Html::errorBox(
150                    $output->msg( $error )->parse(),
151                    '',
152                    'previewnote'
153                )
154            );
155        } elseif ( $this->output !== null ) {
156            // Anons have predictable edit tokens, only do the JS/CSS preview for logged-in users.
157            if ( $user->isAnon() ) {
158                $output->addHTML(
159                    Html::warningBox(
160                        $output->msg( 'templatesandbox-anon-limited-preview' )->parse(),
161                        'previewnote'
162                    )
163                );
164            } else {
165                Logic::addSubpageHandlerToOutput( $this->prefixes, $output );
166            }
167            $output->addParserOutput( $this->output );
168
169            $output->addHTML( Html::rawElement(
170                'div',
171                [ 'class' => 'limitreport', 'style' => 'clear:both' ],
172                EditPage::getPreviewLimitReport( $this->output )
173            ) );
174            $output->addModules( 'mediawiki.collapseFooterLists' );
175
176            $titleText = $this->output->getTitleText();
177            if ( strval( $titleText ) !== '' ) {
178                $output->setPageTitleMsg( $this->msg( 'templatesandbox-title-output', $titleText ) );
179            }
180        }
181    }
182
183    /**
184     * @param string|null $value
185     * @param array $allData
186     * @return bool|string
187     */
188    public function validatePageParam( $value, $allData ) {
189        if ( $value === '' || $value === null ) {
190            return true;
191        }
192        $title = Title::newFromText( $value );
193        if ( !$title instanceof Title ) {
194            return $this->msg( 'templatesandbox-invalid-title' )->parseAsBlock();
195        }
196        if ( !$title->exists() ) {
197            return $this->msg( 'templatesandbox-title-not-exists' )->parseAsBlock();
198        }
199        return true;
200    }
201
202    /**
203     * @param string|null $value
204     * @param array $allData
205     * @return bool|string
206     */
207    public function validateRevidParam( $value, $allData ) {
208        if ( $value === '' || $value === null ) {
209            return true;
210        }
211
212        $revisionRecord = $this->revisionLookup->getRevisionById( (int)$value );
213        if ( $revisionRecord === null ) {
214            return $this->msg( 'templatesandbox-revision-not-exists' )->parseAsBlock();
215        }
216
217        $content = $revisionRecord->getContent(
218            SlotRecord::MAIN,
219            RevisionRecord::FOR_THIS_USER,
220            $this->getUser()
221        );
222
223        if ( $content === null ) {
224            return $this->msg( 'templatesandbox-revision-no-content' )->parseAsBlock();
225        }
226        return true;
227    }
228
229    /**
230     * @param string|null $value
231     * @param array $allData
232     * @return bool|string
233     */
234    public function validatePrefixParam( $value, $allData ) {
235        if ( $value === '' || $value === null ) {
236            return true;
237        }
238        $prefixes = array_map( 'trim', explode( '|', $value ) );
239        foreach ( $prefixes as $prefix ) {
240            $title = Title::newFromText( rtrim( $prefix, '/' ) );
241            if ( !$title instanceof Title || $title->getFragment() !== '' ) {
242                return $this->msg( 'templatesandbox-invalid-prefix' )->parseAsBlock();
243            }
244            if ( $title->isExternal() ) {
245                return $this->msg( 'templatesandbox-prefix-not-local' )->parseAsBlock();
246            }
247            $this->prefixes[] = $title->getPrefixedText();
248        }
249        return true;
250    }
251
252    /**
253     * @param array $data
254     * @param HTMLForm $form
255     * @return Status
256     */
257    public function onSubmit( $data, $form ) {
258        if ( $data['revid'] !== '' && $data['revid'] !== null ) {
259            $rev = $this->revisionLookup->getRevisionById( $data['revid'] );
260            $title = Title::newFromLinkTarget( $rev->getPageAsLinkTarget() );
261        } elseif ( $data['page'] !== '' && $data['page'] !== null ) {
262            $title = Title::newFromText( $data['page'] );
263            $rev = $this->revisionLookup->getRevisionByTitle( $title );
264        } else {
265            return Status::newFatal( 'templatesandbox-page-or-revid' );
266        }
267
268        if ( $data['text'] !== '' && $data['text'] !== null ) {
269            $content = $this->contentHandlerFactory
270                ->getContentHandler( $rev->getSlot( SlotRecord::MAIN )->getModel() )
271                ->unserializeContent( $data['text'] );
272        } else {
273            $content = $rev->getContent(
274                SlotRecord::MAIN,
275                RevisionRecord::FOR_THIS_USER,
276                $this->getUser()
277            );
278        }
279
280        // Title and Content are validated by validatePrefixParam and validatePageParam
281        '@phan-var Title $title';
282        '@phan-var Content $content';
283
284        $page = $this->wikiPageFactory->newFromTitle( $title );
285        $popts = $page->makeParserOptions( $this->getContext() );
286        $popts->setIsPreview( true );
287        $popts->setIsSectionPreview( false );
288        $logic = new Logic( $this->prefixes, null, null );
289        $reset = $logic->setupForParse( $popts );
290        $this->output = $this->contentRenderer->getParserOutput( $content, $title, $rev, $popts );
291
292        return Status::newGood();
293    }
294}