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