Merge "fix fatal error in HttpTest"
[lhc/web/wiklou.git] / includes / content / AbstractContent.php
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 abstract class AbstractContent implements Content {
29
30 /**
31 * Name of the content model this Content object represents.
32 * Use with CONTENT_MODEL_XXX constants
33 *
34 * @since 1.21
35 *
36 * @var string $model_id
37 */
38 protected $model_id;
39
40 /**
41 * @param string|null $modelId
42 *
43 * @since 1.21
44 */
45 public function __construct( $modelId = null ) {
46 $this->model_id = $modelId;
47 }
48
49 /**
50 * @see Content::getModel
51 *
52 * @since 1.21
53 */
54 public function getModel() {
55 return $this->model_id;
56 }
57
58 /**
59 * Throws an MWException if $model_id is not the id of the content model
60 * supported by this Content object.
61 *
62 * @since 1.21
63 *
64 * @param string $modelId The model to check
65 *
66 * @throws MWException
67 */
68 protected function checkModelID( $modelId ) {
69 if ( $modelId !== $this->model_id ) {
70 throw new MWException(
71 "Bad content model: " .
72 "expected {$this->model_id} " .
73 "but got $modelId."
74 );
75 }
76 }
77
78 /**
79 * @see Content::getContentHandler
80 *
81 * @since 1.21
82 */
83 public function getContentHandler() {
84 return ContentHandler::getForContent( $this );
85 }
86
87 /**
88 * @see Content::getDefaultFormat
89 *
90 * @since 1.21
91 */
92 public function getDefaultFormat() {
93 return $this->getContentHandler()->getDefaultFormat();
94 }
95
96 /**
97 * @see Content::getSupportedFormats
98 *
99 * @since 1.21
100 */
101 public function getSupportedFormats() {
102 return $this->getContentHandler()->getSupportedFormats();
103 }
104
105 /**
106 * @see Content::isSupportedFormat
107 *
108 * @param string $format
109 *
110 * @since 1.21
111 *
112 * @return boolean
113 */
114 public function isSupportedFormat( $format ) {
115 if ( !$format ) {
116 return true; // this means "use the default"
117 }
118
119 return $this->getContentHandler()->isSupportedFormat( $format );
120 }
121
122 /**
123 * Throws an MWException if $this->isSupportedFormat( $format ) does not
124 * return true.
125 *
126 * @since 1.21
127 *
128 * @param string $format
129 * @throws MWException
130 */
131 protected function checkFormat( $format ) {
132 if ( !$this->isSupportedFormat( $format ) ) {
133 throw new MWException(
134 "Format $format is not supported for content model " .
135 $this->getModel()
136 );
137 }
138 }
139
140 /**
141 * @see Content::serialize
142 *
143 * @param string|null $format
144 *
145 * @since 1.21
146 *
147 * @return string
148 */
149 public function serialize( $format = null ) {
150 return $this->getContentHandler()->serializeContent( $this, $format );
151 }
152
153 /**
154 * @see Content::isEmpty
155 *
156 * @since 1.21
157 *
158 * @return boolean
159 */
160 public function isEmpty() {
161 return $this->getSize() === 0;
162 }
163
164 /**
165 * @see Content::isValid
166 *
167 * @since 1.21
168 *
169 * @return boolean
170 */
171 public function isValid() {
172 return true;
173 }
174
175 /**
176 * @see Content::equals
177 *
178 * @since 1.21
179 *
180 * @param Content|null $that
181 *
182 * @return boolean
183 */
184 public function equals( Content $that = null ) {
185 if ( is_null( $that ) ) {
186 return false;
187 }
188
189 if ( $that === $this ) {
190 return true;
191 }
192
193 if ( $that->getModel() !== $this->getModel() ) {
194 return false;
195 }
196
197 return $this->getNativeData() === $that->getNativeData();
198 }
199
200
201 /**
202 * Returns a list of DataUpdate objects for recording information about this
203 * Content in some secondary data store.
204 *
205 * This default implementation calls
206 * $this->getParserOutput( $content, $title, null, null, false ),
207 * and then calls getSecondaryDataUpdates( $title, $recursive ) on the
208 * resulting ParserOutput object.
209 *
210 * Subclasses may override this to determine the secondary data updates more
211 * efficiently, preferrably without the need to generate a parser output object.
212 *
213 * @see Content::getSecondaryDataUpdates()
214 *
215 * @param $title Title The context for determining the necessary updates
216 * @param $old Content|null An optional Content object representing the
217 * previous content, i.e. the content being replaced by this Content
218 * object.
219 * @param $recursive boolean Whether to include recursive updates (default:
220 * false).
221 * @param $parserOutput ParserOutput|null Optional ParserOutput object.
222 * Provide if you have one handy, to avoid re-parsing of the content.
223 *
224 * @return Array. A list of DataUpdate objects for putting information
225 * about this content object somewhere.
226 *
227 * @since 1.21
228 */
229 public function getSecondaryDataUpdates( Title $title,
230 Content $old = null,
231 $recursive = true, ParserOutput $parserOutput = null
232 ) {
233 if ( !$parserOutput ) {
234 $parserOutput = $this->getParserOutput( $title, null, null, false );
235 }
236
237 return $parserOutput->getSecondaryDataUpdates( $title, $recursive );
238 }
239
240
241 /**
242 * @see Content::getRedirectChain
243 *
244 * @since 1.21
245 */
246 public function getRedirectChain() {
247 global $wgMaxRedirects;
248 $title = $this->getRedirectTarget();
249 if ( is_null( $title ) ) {
250 return null;
251 }
252 // recursive check to follow double redirects
253 $recurse = $wgMaxRedirects;
254 $titles = array( $title );
255 while ( --$recurse > 0 ) {
256 if ( $title->isRedirect() ) {
257 $page = WikiPage::factory( $title );
258 $newtitle = $page->getRedirectTarget();
259 } else {
260 break;
261 }
262 // Redirects to some special pages are not permitted
263 if ( $newtitle instanceOf Title && $newtitle->isValidRedirectTarget() ) {
264 // The new title passes the checks, so make that our current
265 // title so that further recursion can be checked
266 $title = $newtitle;
267 $titles[] = $newtitle;
268 } else {
269 break;
270 }
271 }
272 return $titles;
273 }
274
275 /**
276 * @see Content::getRedirectTarget
277 *
278 * @since 1.21
279 */
280 public function getRedirectTarget() {
281 return null;
282 }
283
284 /**
285 * @see Content::getUltimateRedirectTarget
286 * @note: migrated here from Title::newFromRedirectRecurse
287 *
288 * @since 1.21
289 */
290 public function getUltimateRedirectTarget() {
291 $titles = $this->getRedirectChain();
292 return $titles ? array_pop( $titles ) : null;
293 }
294
295 /**
296 * @see Content::isRedirect
297 *
298 * @since 1.21
299 *
300 * @return bool
301 */
302 public function isRedirect() {
303 return $this->getRedirectTarget() !== null;
304 }
305
306 /**
307 * @see Content::updateRedirect
308 *
309 * This default implementation always returns $this.
310 *
311 * @param Title $target
312 *
313 * @since 1.21
314 *
315 * @return Content $this
316 */
317 public function updateRedirect( Title $target ) {
318 return $this;
319 }
320
321 /**
322 * @see Content::getSection
323 *
324 * @since 1.21
325 */
326 public function getSection( $sectionId ) {
327 return null;
328 }
329
330 /**
331 * @see Content::replaceSection
332 *
333 * @since 1.21
334 */
335 public function replaceSection( $section, Content $with, $sectionTitle = '' ) {
336 return null;
337 }
338
339 /**
340 * @see Content::preSaveTransform
341 *
342 * @since 1.21
343 */
344 public function preSaveTransform( Title $title, User $user, ParserOptions $popts ) {
345 return $this;
346 }
347
348 /**
349 * @see Content::addSectionHeader
350 *
351 * @since 1.21
352 */
353 public function addSectionHeader( $header ) {
354 return $this;
355 }
356
357 /**
358 * @see Content::preloadTransform
359 *
360 * @since 1.21
361 */
362 public function preloadTransform( Title $title, ParserOptions $popts ) {
363 return $this;
364 }
365
366 /**
367 * @see Content::prepareSave
368 *
369 * @since 1.21
370 */
371 public function prepareSave( WikiPage $page, $flags, $baseRevId, User $user ) {
372 if ( $this->isValid() ) {
373 return Status::newGood();
374 } else {
375 return Status::newFatal( "invalid-content-data" );
376 }
377 }
378
379 /**
380 * @see Content::getDeletionUpdates
381 *
382 * @since 1.21
383 *
384 * @param $page WikiPage the deleted page
385 * @param $parserOutput null|ParserOutput optional parser output object
386 * for efficient access to meta-information about the content object.
387 * Provide if you have one handy.
388 *
389 * @return array A list of DataUpdate instances that will clean up the
390 * database after deletion.
391 */
392 public function getDeletionUpdates( WikiPage $page,
393 ParserOutput $parserOutput = null )
394 {
395 return array(
396 new LinksDeletionUpdate( $page ),
397 );
398 }
399
400 /**
401 * This default implementation always returns false. Subclasses may override this to supply matching logic.
402 *
403 * @see Content::matchMagicWord
404 *
405 * @since 1.21
406 *
407 * @param MagicWord $word
408 *
409 * @return bool
410 */
411 public function matchMagicWord( MagicWord $word ) {
412 return false;
413 }
414 }