Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
55.07% covered (warning)
55.07%
38 / 69
38.46% covered (danger)
38.46%
5 / 13
CRAP
0.00% covered (danger)
0.00%
0 / 1
WikitextContent
55.07% covered (warning)
55.07%
38 / 69
38.46% covered (danger)
38.46%
5 / 13
87.30
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getSection
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 replaceSection
86.36% covered (warning)
86.36%
19 / 22
0.00% covered (danger)
0.00%
0 / 1
7.12
 addSectionHeader
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getRedirectTargetAndText
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 getRedirectTarget
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 updateRedirect
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 isCountable
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
30
 getTextForSummary
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 matchMagicWord
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setPreSaveTransformFlags
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getPreSaveTransformFlags
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getContentHandler
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/**
3 * Content object for wiki text pages.
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
19 *
20 * @since 1.21
21 *
22 * @file
23 * @ingroup Content
24 *
25 * @author Daniel Kinzler
26 */
27
28use MediaWiki\Context\RequestContext;
29use MediaWiki\HookContainer\HookRunner;
30use MediaWiki\MainConfigNames;
31use MediaWiki\MediaWikiServices;
32use MediaWiki\Parser\MagicWord;
33use MediaWiki\Title\Title;
34
35/**
36 * Content object for wiki text pages.
37 *
38 * @newable
39 * @ingroup Content
40 */
41class WikitextContent extends TextContent {
42
43    /**
44     * @var string[] flags set by PST
45     */
46    private $preSaveTransformFlags = [];
47
48    /**
49     * @stable to call
50     *
51     * @param string $text
52     */
53    public function __construct( $text ) {
54        parent::__construct( $text, CONTENT_MODEL_WIKITEXT );
55    }
56
57    /**
58     * @param string|int $sectionId
59     *
60     * @return Content|false|null
61     *
62     * @see Content::getSection()
63     */
64    public function getSection( $sectionId ) {
65        $text = $this->getText();
66        $sect = MediaWikiServices::getInstance()->getParserFactory()->getInstance()
67            ->getSection( $text, $sectionId, false );
68
69        if ( $sect === false ) {
70            return false;
71        } else {
72            return new static( $sect );
73        }
74    }
75
76    /**
77     * @param string|int|null|false $sectionId
78     * @param Content $with New section content, must have the same content model as $this.
79     * @param string $sectionTitle
80     * @return Content
81     *
82     * @see Content::replaceSection()
83     */
84    public function replaceSection( $sectionId, Content $with, $sectionTitle = '' ) {
85        // @phan-suppress-previous-line PhanParamSignatureMismatch False positive
86        $myModelId = $this->getModel();
87        $sectionModelId = $with->getModel();
88
89        if ( $sectionModelId != $myModelId ) {
90            throw new InvalidArgumentException( "Incompatible content model for section: " .
91                "document uses $myModelId but " .
92                "section uses $sectionModelId." );
93        }
94        /** @var self $with $oldtext */
95        '@phan-var self $with';
96
97        $oldtext = $this->getText();
98        $text = $with->getText();
99
100        if ( strval( $sectionId ) === '' ) {
101            return $with; # XXX: copy first?
102        }
103
104        if ( $sectionId === 'new' ) {
105            # Inserting a new section
106            $subject = strval( $sectionTitle ) !== '' ? wfMessage( 'newsectionheaderdefaultlevel' )
107                    ->plaintextParams( $sectionTitle )->inContentLanguage()->text() . "\n\n" : '';
108            $hookRunner = ( new HookRunner( MediaWikiServices::getInstance()->getHookContainer() ) );
109            if ( $hookRunner->onPlaceNewSection( $this, $oldtext, $subject, $text ) ) {
110                $text = strlen( trim( $oldtext ) ) > 0
111                    ? "{$oldtext}\n\n{$subject}{$text}"
112                    : "{$subject}{$text}";
113            }
114        } else {
115            # Replacing an existing section; roll out the big guns
116            $text = MediaWikiServices::getInstance()->getParserFactory()->getInstance()
117                ->replaceSection( $oldtext, $sectionId, $text );
118        }
119
120        $newContent = new static( $text );
121
122        return $newContent;
123    }
124
125    /**
126     * Returns a new WikitextContent object with the given section heading
127     * prepended.
128     *
129     * @param string $header
130     *
131     * @return Content
132     */
133    public function addSectionHeader( $header ) {
134        $text = strval( $header ) !== '' ? wfMessage( 'newsectionheaderdefaultlevel' )
135            ->plaintextParams( $header )->inContentLanguage()->text() . "\n\n" : '';
136        $text .= $this->getText();
137
138        return new static( $text );
139    }
140
141    /**
142     * Extract the redirect target and the remaining text on the page.
143     *
144     * @since 1.23
145     * @deprecated since 1.41, use WikitextContentHandler::getRedirectTargetAndText
146     *
147     * @return array List of two elements: Title|null and string.
148     */
149    public function getRedirectTargetAndText() {
150        wfDeprecated( __METHOD__, '1.41' );
151
152        $handler = $this->getContentHandler();
153        [ $target, $content ] = $handler->extractRedirectTargetAndText( $this );
154
155        return [ Title::castFromLinkTarget( $target ), $content->getText() ];
156    }
157
158    /**
159     * Implement redirect extraction for wikitext.
160     *
161     * @return Title|null
162     *
163     * @see Content::getRedirectTarget
164     */
165    public function getRedirectTarget() {
166        // TODO: The redirect target should be injected on construction.
167        //       But that only works if the object is created by WikitextContentHandler.
168
169        $handler = $this->getContentHandler();
170        [ $target, ] = $handler->extractRedirectTargetAndText( $this );
171
172        return Title::castFromLinkTarget( $target );
173    }
174
175    /**
176     * This implementation replaces the first link on the page with the given new target
177     * if this Content object is a redirect. Otherwise, this method returns $this.
178     *
179     * @since 1.21
180     *
181     * @param Title $target
182     *
183     * @return Content
184     *
185     * @see Content::updateRedirect()
186     */
187    public function updateRedirect( Title $target ) {
188        if ( !$this->isRedirect() ) {
189            return $this;
190        }
191
192        # Fix the text
193        # Remember that redirect pages can have categories, templates, etc.,
194        # so the regex has to be fairly general
195        $newText = preg_replace( '/ \[ \[  [^\]]*  \] \] /x',
196            '[[' . $target->getFullText() . ']]',
197            $this->getText(), 1 );
198
199        return new static( $newText );
200    }
201
202    /**
203     * Returns true if this content is not a redirect, and this content's text
204     * is countable according to the criteria defined by $wgArticleCountMethod.
205     *
206     * @param bool|null $hasLinks If it is known whether this content contains
207     *    links, provide this information here, to avoid redundant parsing to
208     *    find out (default: null).
209     * @param Title|null $title Optional title, defaults to the title from the current main request.
210     *
211     * @return bool
212     */
213    public function isCountable( $hasLinks = null, Title $title = null ) {
214        $articleCountMethod = MediaWikiServices::getInstance()->getMainConfig()
215            ->get( MainConfigNames::ArticleCountMethod );
216
217        if ( $this->isRedirect() ) {
218            return false;
219        }
220
221        if ( $articleCountMethod === 'link' ) {
222            if ( $hasLinks === null ) { # not known, find out
223                // @TODO: require an injected title
224                if ( !$title ) {
225                    $context = RequestContext::getMain();
226                    $title = $context->getTitle();
227                }
228                $contentRenderer = MediaWikiServices::getInstance()->getContentRenderer();
229                // @phan-suppress-next-line PhanTypeMismatchArgumentNullable getTitle does not return null here
230                $po = $contentRenderer->getParserOutput( $this, $title, null, null, false );
231                $links = $po->getLinks();
232                $hasLinks = $links !== [];
233            }
234
235            return $hasLinks;
236        }
237
238        return true;
239    }
240
241    /**
242     * @param int $maxlength
243     * @return string
244     */
245    public function getTextForSummary( $maxlength = 250 ) {
246        $truncatedtext = parent::getTextForSummary( $maxlength );
247
248        # clean up unfinished links
249        # XXX: make this optional? wasn't there in autosummary, but required for
250        # deletion summary.
251        $truncatedtext = preg_replace( '/\[\[([^\]]*)\]?$/', '$1', $truncatedtext );
252
253        return $truncatedtext;
254    }
255
256    /**
257     * This implementation calls $word->match() on the this TextContent object's text.
258     *
259     * @param MagicWord $word
260     *
261     * @return bool
262     *
263     * @see Content::matchMagicWord()
264     */
265    public function matchMagicWord( MagicWord $word ) {
266        return $word->match( $this->getText() );
267    }
268
269    /**
270     * Records flags set by preSaveTransform
271     * @internal for use by WikitextContentHandler
272     * @param string[] $flags
273     */
274    public function setPreSaveTransformFlags( array $flags ) {
275        $this->preSaveTransformFlags = $flags;
276    }
277
278    /**
279     * Records flags set by preSaveTransform
280     * @internal for use by WikitextContentHandler
281     * @return string[]
282     */
283    public function getPreSaveTransformFlags() {
284        return $this->preSaveTransformFlags;
285    }
286
287    public function getContentHandler(): WikitextContentHandler {
288        $handler = parent::getContentHandler();
289        '@phan-var WikitextContentHandler $handler';
290        return $handler;
291    }
292}