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