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