Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
24.49% covered (danger)
24.49%
12 / 49
21.74% covered (danger)
21.74%
5 / 23
CRAP
0.00% covered (danger)
0.00%
0 / 1
AbstractContent
24.49% covered (danger)
24.49%
12 / 49
21.74% covered (danger)
21.74%
5 / 23
444.75
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
 getModel
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 checkModelID
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 getContentHandler
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getContentHandlerFactory
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getDefaultFormat
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getSupportedFormats
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isSupportedFormat
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 checkFormat
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 serialize
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getNativeData
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 isEmpty
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isValid
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 equals
77.78% covered (warning)
77.78%
7 / 9
0.00% covered (danger)
0.00%
0 / 1
5.27
 equalsInternal
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getRedirectTarget
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isRedirect
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 updateRedirect
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getSection
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 replaceSection
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 addSectionHeader
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 matchMagicWord
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 convert
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2/**
3 * A content object represents page content, e.g. the text to show on a page.
4 * Content objects have no knowledge about how they relate to Wiki pages.
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 * http://www.gnu.org/copyleft/gpl.html
20 *
21 * @since 1.21
22 *
23 * @file
24 * @ingroup Content
25 *
26 * @author Daniel Kinzler
27 */
28
29use MediaWiki\Content\IContentHandlerFactory;
30use MediaWiki\HookContainer\HookRunner;
31use MediaWiki\MediaWikiServices;
32use MediaWiki\Parser\MagicWord;
33use MediaWiki\Title\Title;
34
35/**
36 * Base implementation for content objects.
37 *
38 * @stable to extend
39 *
40 * @ingroup Content
41 */
42abstract class AbstractContent implements Content {
43    /**
44     * Name of the content model this Content object represents.
45     * Use with CONTENT_MODEL_XXX constants
46     *
47     * @since 1.21
48     *
49     * @var string
50     */
51    protected $model_id;
52
53    /**
54     * @stable to call
55     *
56     * @param string|null $modelId
57     *
58     * @since 1.21
59     */
60    public function __construct( $modelId = null ) {
61        $this->model_id = $modelId;
62    }
63
64    /**
65     * @since 1.21
66     *
67     * @see Content::getModel
68     * @return string
69     */
70    public function getModel() {
71        return $this->model_id;
72    }
73
74    /**
75     * @since 1.21
76     *
77     * @param string $modelId The model to check
78     *
79     * @throws MWException If the provided ID is not the ID of the content model supported by this
80     * Content object.
81     */
82    protected function checkModelID( $modelId ) {
83        if ( $modelId !== $this->model_id ) {
84            throw new MWException(
85                "Bad content model: " .
86                "expected {$this->model_id} " .
87                "but got $modelId."
88            );
89        }
90    }
91
92    /**
93     * @since 1.21
94     *
95     * @see Content::getContentHandler
96     * @return ContentHandler
97     */
98    public function getContentHandler() {
99        return $this->getContentHandlerFactory()->getContentHandler( $this->getModel() );
100    }
101
102    /**
103     * @return IContentHandlerFactory
104     */
105    protected function getContentHandlerFactory(): IContentHandlerFactory {
106        return MediaWikiServices::getInstance()->getContentHandlerFactory();
107    }
108
109    /**
110     * @since 1.21
111     *
112     * @see Content::getDefaultFormat
113     * @return string
114     */
115    public function getDefaultFormat() {
116        return $this->getContentHandler()->getDefaultFormat();
117    }
118
119    /**
120     * @since 1.21
121     *
122     * @see Content::getSupportedFormats
123     * @return string[]
124     */
125    public function getSupportedFormats() {
126        return $this->getContentHandler()->getSupportedFormats();
127    }
128
129    /**
130     * @since 1.21
131     *
132     * @param string $format
133     *
134     * @return bool
135     *
136     * @see Content::isSupportedFormat
137     */
138    public function isSupportedFormat( $format ) {
139        if ( !$format ) {
140            return true; // this means "use the default"
141        }
142
143        return $this->getContentHandler()->isSupportedFormat( $format );
144    }
145
146    /**
147     * @since 1.21
148     *
149     * @param string $format The serialization format to check.
150     *
151     * @throws MWException If the format is not supported by this content handler.
152     */
153    protected function checkFormat( $format ) {
154        if ( !$this->isSupportedFormat( $format ) ) {
155            throw new MWException(
156                "Format $format is not supported for content model " .
157                $this->getModel()
158            );
159        }
160    }
161
162    /**
163     * @stable to override
164     * @since 1.21
165     *
166     * @param string|null $format
167     *
168     * @return string
169     *
170     * @see Content::serialize
171     */
172    public function serialize( $format = null ) {
173        return $this->getContentHandler()->serializeContent( $this, $format );
174    }
175
176    /**
177     * Returns native representation of the data. Interpretation depends on
178     * the data model used, as given by getDataModel().
179     *
180     * @stable to override
181     * @since 1.21
182     *
183     * @deprecated since 1.33. Use getText() for TextContent instances.
184     *             For other content models, use specialized getters.
185     *             Emitting deprecation warnings since 1.41.
186     *
187     * @return mixed The native representation of the content. Could be a
188     *    string, a nested array structure, an object, a binary blob...
189     *    anything, really.
190     * @throws LogicException
191     *
192     * @note Caller must be aware of content model!
193     */
194    public function getNativeData() {
195        wfDeprecated( __METHOD__, '1.33' );
196        throw new LogicException( __METHOD__ . ': not implemented' );
197    }
198
199    /**
200     * @stable to override
201     * @since 1.21
202     *
203     * @return bool
204     *
205     * @see Content::isEmpty
206     */
207    public function isEmpty() {
208        return $this->getSize() === 0;
209    }
210
211    /**
212     * Subclasses may override this to implement (light weight) validation.
213     *
214     * @stable to override
215     * @since 1.21
216     *
217     * @return bool Always true.
218     *
219     * @see Content::isValid
220     */
221    public function isValid() {
222        return true;
223    }
224
225    /**
226     * Decides whether two Content objects are equal.
227     * Two Content objects MUST not be considered equal if they do not share the same content model.
228     * Two Content objects that are equal SHOULD have the same serialization.
229     *
230     * This default implementation relies on equalsInternal() to determine whether the
231     * Content objects are logically equivalent. Subclasses that need to implement a custom
232     * equality check should consider overriding equalsInternal(). Subclasses that override
233     * equals() itself MUST make sure that the implementation returns false for $that === null,
234     * and true for $that === this. It MUST also return false if $that does not have the same
235     * content model.
236     *
237     * @stable to override
238     * @since 1.21
239     *
240     * @param Content|null $that
241     *
242     * @return bool
243     *
244     * @see Content::equals
245     */
246    public function equals( Content $that = null ) {
247        if ( $that === null ) {
248            return false;
249        }
250
251        if ( $that === $this ) {
252            return true;
253        }
254
255        if ( $that->getModel() !== $this->getModel() ) {
256            return false;
257        }
258
259        // For type safety. Needed for odd cases like MessageContent using CONTENT_MODEL_WIKITEXT
260        if ( get_class( $that ) !== get_class( $this ) ) {
261            return false;
262        }
263
264        return $this->equalsInternal( $that );
265    }
266
267    /**
268     * Checks whether $that is logically equal to this Content object.
269     *
270     * This method can be overwritten by subclasses that need to implement custom
271     * equality checks.
272     *
273     * This default implementation checks whether the serializations
274     * of $this and $that are the same: $this->serialize() === $that->serialize()
275     *
276     * Implementors can assume that $that is an instance of the same class
277     * as the present Content object, as long as equalsInternal() is only called
278     * by the standard implementation of equals().
279     *
280     * @note Do not call this method directly, call equals() instead.
281     *
282     * @stable to override
283     *
284     * @param Content $that
285     * @return bool
286     */
287    protected function equalsInternal( Content $that ) {
288        return $this->serialize() === $that->serialize();
289    }
290
291    /**
292     * Subclasses that implement redirects should override this.
293     *
294     * @stable to override
295     * @since 1.21
296     *
297     * @return Title|null
298     *
299     * @see Content::getRedirectTarget
300     */
301    public function getRedirectTarget() {
302        return null;
303    }
304
305    /**
306     * @since 1.21
307     *
308     * @return bool
309     *
310     * @see Content::isRedirect
311     */
312    public function isRedirect() {
313        return $this->getRedirectTarget() !== null;
314    }
315
316    /**
317     * This default implementation always returns $this.
318     * Subclasses that implement redirects should override this.
319     *
320     * @stable to override
321     * @since 1.21
322     *
323     * @param Title $target
324     *
325     * @return Content $this
326     *
327     * @see Content::updateRedirect
328     */
329    public function updateRedirect( Title $target ) {
330        return $this;
331    }
332
333    /**
334     * @stable to override
335     * @since 1.21
336     *
337     * @param string|int $sectionId
338     * @return null
339     *
340     * @see Content::getSection
341     */
342    public function getSection( $sectionId ) {
343        return null;
344    }
345
346    /**
347     * @stable to override
348     * @since 1.21
349     *
350     * @param string|int|null|false $sectionId
351     * @param Content $with
352     * @param string $sectionTitle
353     * @return null
354     *
355     * @see Content::replaceSection
356     */
357    public function replaceSection( $sectionId, Content $with, $sectionTitle = '' ) {
358        return null;
359    }
360
361    /**
362     * @stable to override
363     * @since 1.21
364     *
365     * @param string $header
366     * @return Content $this
367     *
368     * @see Content::addSectionHeader
369     */
370    public function addSectionHeader( $header ) {
371        return $this;
372    }
373
374    /**
375     * This default implementation always returns false. Subclasses may override
376     * this to supply matching logic.
377     *
378     * @stable to override
379     * @since 1.21
380     *
381     * @param MagicWord $word
382     *
383     * @return bool Always false.
384     *
385     * @see Content::matchMagicWord
386     */
387    public function matchMagicWord( MagicWord $word ) {
388        return false;
389    }
390
391    /**
392     * This base implementation calls the hook ConvertContent to enable custom conversions.
393     * Subclasses may override this to implement conversion for "their" content model.
394     *
395     * @stable to override
396     *
397     * @param string $toModel
398     * @param string $lossy
399     *
400     * @return Content|false
401     *
402     * @see Content::convert()
403     */
404    public function convert( $toModel, $lossy = '' ) {
405        if ( $this->getModel() === $toModel ) {
406            // nothing to do, shorten out.
407            return $this;
408        }
409
410        $lossy = ( $lossy === 'lossy' ); // string flag, convert to boolean for convenience
411        $result = false;
412
413        ( new HookRunner( MediaWikiServices::getInstance()->getHookContainer() ) )
414            ->onConvertContent( $this, $toModel, $lossy, $result );
415
416        return $result;
417    }
418
419}