Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 83
0.00% covered (danger)
0.00%
0 / 14
CRAP
0.00% covered (danger)
0.00%
0 / 1
SpecialCiteThisPage
0.00% covered (danger)
0.00%
0 / 83
0.00% covered (danger)
0.00%
0 / 14
462
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 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 alterForm
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getFormFields
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
 onSubmit
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 prefixSearchSubpages
0.00% covered (danger)
0.00%
0 / 1
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
 showCitations
0.00% covered (danger)
0.00%
0 / 28
0.00% covered (danger)
0.00%
0 / 1
12
 getContentText
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
20
 getParserOptions
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 citationTag
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
2
 getDisplayFormat
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 requiresUnblock
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 requiresWrite
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace MediaWiki\Extension\CiteThisPage;
4
5use HTMLForm;
6use MediaWiki\Html\Html;
7use MediaWiki\Revision\RevisionLookup;
8use MediaWiki\SpecialPage\FormSpecialPage;
9use MediaWiki\Title\Title;
10use Parser;
11use ParserFactory;
12use ParserOptions;
13use SearchEngineFactory;
14
15class SpecialCiteThisPage extends FormSpecialPage {
16
17    /**
18     * @var Parser
19     */
20    private $citationParser;
21
22    /**
23     * @var Title|bool
24     */
25    protected $title = false;
26
27    /** @var SearchEngineFactory */
28    private $searchEngineFactory;
29
30    /** @var RevisionLookup */
31    private $revisionLookup;
32
33    /** @var ParserFactory */
34    private $parserFactory;
35
36    /**
37     * @param SearchEngineFactory $searchEngineFactory
38     * @param RevisionLookup $revisionLookup
39     * @param ParserFactory $parserFactory
40     */
41    public function __construct(
42        SearchEngineFactory $searchEngineFactory,
43        RevisionLookup $revisionLookup,
44        ParserFactory $parserFactory
45    ) {
46        parent::__construct( 'CiteThisPage' );
47        $this->searchEngineFactory = $searchEngineFactory;
48        $this->revisionLookup = $revisionLookup;
49        $this->parserFactory = $parserFactory;
50    }
51
52    /**
53     * @param string $par
54     */
55    public function execute( $par ) {
56        $this->setHeaders();
57        $this->addHelpLink( 'Extension:CiteThisPage' );
58        parent::execute( $par );
59        if ( $this->title instanceof Title ) {
60            $id = $this->getRequest()->getInt( 'id' );
61            $this->showCitations( $this->title, $id );
62        }
63    }
64
65    /**
66     * @param HTMLForm $form
67     */
68    protected function alterForm( HTMLForm $form ) {
69        $form->setMethod( 'get' );
70        $form->setFormIdentifier( 'titleform' );
71    }
72
73    /**
74     * @return array
75     */
76    protected function getFormFields() {
77        return [
78            'page' => [
79                'name' => 'page',
80                'type' => 'title',
81                'exists' => true,
82                'default' => $this->par ?? '',
83                'label-message' => 'citethispage-change-target'
84            ]
85        ];
86    }
87
88    /**
89     * @param array $data
90     * @return bool
91     */
92    public function onSubmit( array $data ) {
93        // GET forms are "submitted" on every view, so check
94        // that some data was put in for page
95        if ( strlen( $data['page'] ) ) {
96            $this->title = Title::newFromText( $data['page'] );
97        }
98        return true;
99    }
100
101    /**
102     * Return an array of subpages beginning with $search that this special page will accept.
103     *
104     * @param string $search Prefix to search for
105     * @param int $limit Maximum number of results to return (usually 10)
106     * @param int $offset Number of results to skip (usually 0)
107     * @return string[] Matching subpages
108     */
109    public function prefixSearchSubpages( $search, $limit, $offset ) {
110        return $this->prefixSearchString( $search, $limit, $offset, $this->searchEngineFactory );
111    }
112
113    /** @inheritDoc */
114    protected function getGroupName() {
115        return 'pagetools';
116    }
117
118    /**
119     * @param Title $title
120     * @param int $revId
121     */
122    private function showCitations( Title $title, $revId ) {
123        if ( !$revId ) {
124            $revId = $title->getLatestRevID();
125        }
126
127        $out = $this->getOutput();
128
129        $revTimestamp = $this->revisionLookup->getTimestampFromId( $revId );
130
131        if ( !$revTimestamp ) {
132            $out->addHTML(
133                Html::errorBox(
134                    $out->msg( 'citethispage-badrevision', $title->getPrefixedText(), $revId )->parse()
135                )
136            );
137            return;
138        }
139
140        $parserOptions = $this->getParserOptions();
141        // Set the overall timestamp to the revision's timestamp
142        $parserOptions->setTimestamp( $revTimestamp );
143
144        $parser = $this->parserFactory->create();
145        // Register our <citation> tag which just parses using a different
146        // context
147        $parser->setHook( 'citation', [ $this, 'citationTag' ] );
148
149        // Also hold on to a separate Parser instance for <citation> tag parsing
150        // since we can't parse in a parse using the same Parser
151        $this->citationParser = $this->parserFactory->create();
152
153        $ret = $parser->parse(
154            $this->getContentText(),
155            $title,
156            $parserOptions,
157            /* $linestart = */ false,
158            /* $clearstate = */ true,
159            $revId
160        );
161
162        $this->getOutput()->addModuleStyles( 'ext.citeThisPage' );
163        $this->getOutput()->addParserOutputContent( $ret, [
164            'enableSectionEditLinks' => false,
165        ] );
166    }
167
168    /**
169     * Get the content to parse
170     *
171     * @return string
172     */
173    private function getContentText() {
174        $msg = $this->msg( 'citethispage-content' )->inContentLanguage()->plain();
175        if ( $msg == '' ) {
176            # With MediaWiki 1.20 the plain text files were deleted
177            # and the text moved into SpecialCite.i18n.php
178            # This code is kept for b/c in case an installation has its own file "citethispage-content-xx"
179            # for a previously not supported language.
180            $dir = __DIR__ . '/../';
181            $contentLang = $this->getContentLanguage();
182            $code = $contentLang->lc( $contentLang->getCode() );
183            if ( file_exists( "{$dir}citethispage-content-$code" ) ) {
184                $msg = file_get_contents( "{$dir}citethispage-content-$code" );
185            } elseif ( file_exists( "{$dir}citethispage-content" ) ) {
186                $msg = file_get_contents( "{$dir}citethispage-content" );
187            }
188        }
189
190        return $msg;
191    }
192
193    /**
194     * Get the common ParserOptions for both parses
195     *
196     * @return ParserOptions
197     */
198    private function getParserOptions() {
199        $parserOptions = ParserOptions::newFromContext( $this->getContext() );
200        $parserOptions->setDateFormat( 'default' );
201        $parserOptions->setInterfaceMessage( true );
202        return $parserOptions;
203    }
204
205    /**
206     * Implements the <citation> tag.
207     *
208     * This is a hack to allow content that is typically parsed
209     * using the page's timestamp/pagetitle to use the current
210     * request's time and title
211     *
212     * @param string $text
213     * @param array $params
214     * @param Parser $parser
215     * @return string
216     */
217    public function citationTag( $text, $params, Parser $parser ) {
218        $parserOptions = $this->getParserOptions();
219
220        $ret = $this->citationParser->parse(
221            $text,
222            $this->getPageTitle(),
223            $parserOptions,
224            /* $linestart = */ false
225        );
226
227        return Parser::stripOuterParagraph( $ret->getText( [
228            'enableSectionEditLinks' => false,
229            // This will be inserted into the output of another parser, so there will actually be a wrapper
230            'unwrap' => true,
231            'wrapperDivClass' => '',
232        ] ) );
233    }
234
235    /** @inheritDoc */
236    protected function getDisplayFormat() {
237        return 'ooui';
238    }
239
240    /** @inheritDoc */
241    public function requiresUnblock() {
242        return false;
243    }
244
245    /** @inheritDoc */
246    public function requiresWrite() {
247        return false;
248    }
249}