[ Index ] |
PHP Cross Reference of MediaWiki-1.24.0 |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * Base class for content handling. 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 28 /** 29 * Exception representing a failure to serialize or unserialize a content object. 30 * 31 * @ingroup Content 32 */ 33 class MWContentSerializationException extends MWException { 34 } 35 36 /** 37 * A content handler knows how do deal with a specific type of content on a wiki 38 * page. Content is stored in the database in a serialized form (using a 39 * serialization format a.k.a. MIME type) and is unserialized into its native 40 * PHP representation (the content model), which is wrapped in an instance of 41 * the appropriate subclass of Content. 42 * 43 * ContentHandler instances are stateless singletons that serve, among other 44 * things, as a factory for Content objects. Generally, there is one subclass 45 * of ContentHandler and one subclass of Content for every type of content model. 46 * 47 * Some content types have a flat model, that is, their native representation 48 * is the same as their serialized form. Examples would be JavaScript and CSS 49 * code. As of now, this also applies to wikitext (MediaWiki's default content 50 * type), but wikitext content may be represented by a DOM or AST structure in 51 * the future. 52 * 53 * @ingroup Content 54 */ 55 abstract class ContentHandler { 56 /** 57 * Switch for enabling deprecation warnings. Used by ContentHandler::deprecated() 58 * and ContentHandler::runLegacyHooks(). 59 * 60 * Once the ContentHandler code has settled in a bit, this should be set to true to 61 * make extensions etc. show warnings when using deprecated functions and hooks. 62 */ 63 protected static $enableDeprecationWarnings = false; 64 65 /** 66 * Convenience function for getting flat text from a Content object. This 67 * should only be used in the context of backwards compatibility with code 68 * that is not yet able to handle Content objects! 69 * 70 * If $content is null, this method returns the empty string. 71 * 72 * If $content is an instance of TextContent, this method returns the flat 73 * text as returned by $content->getNativeData(). 74 * 75 * If $content is not a TextContent object, the behavior of this method 76 * depends on the global $wgContentHandlerTextFallback: 77 * - If $wgContentHandlerTextFallback is 'fail' and $content is not a 78 * TextContent object, an MWException is thrown. 79 * - If $wgContentHandlerTextFallback is 'serialize' and $content is not a 80 * TextContent object, $content->serialize() is called to get a string 81 * form of the content. 82 * - If $wgContentHandlerTextFallback is 'ignore' and $content is not a 83 * TextContent object, this method returns null. 84 * - otherwise, the behavior is undefined. 85 * 86 * @since 1.21 87 * 88 * @param Content $content 89 * 90 * @throws MWException If the content is not an instance of TextContent and 91 * wgContentHandlerTextFallback was set to 'fail'. 92 * @return string|null Textual form of the content, if available. 93 */ 94 public static function getContentText( Content $content = null ) { 95 global $wgContentHandlerTextFallback; 96 97 if ( is_null( $content ) ) { 98 return ''; 99 } 100 101 if ( $content instanceof TextContent ) { 102 return $content->getNativeData(); 103 } 104 105 wfDebugLog( 'ContentHandler', 'Accessing ' . $content->getModel() . ' content as text!' ); 106 107 if ( $wgContentHandlerTextFallback == 'fail' ) { 108 throw new MWException( 109 "Attempt to get text from Content with model " . 110 $content->getModel() 111 ); 112 } 113 114 if ( $wgContentHandlerTextFallback == 'serialize' ) { 115 return $content->serialize(); 116 } 117 118 return null; 119 } 120 121 /** 122 * Convenience function for creating a Content object from a given textual 123 * representation. 124 * 125 * $text will be deserialized into a Content object of the model specified 126 * by $modelId (or, if that is not given, $title->getContentModel()) using 127 * the given format. 128 * 129 * @since 1.21 130 * 131 * @param string $text The textual representation, will be 132 * unserialized to create the Content object 133 * @param Title $title The title of the page this text belongs to. 134 * Required if $modelId is not provided. 135 * @param string $modelId The model to deserialize to. If not provided, 136 * $title->getContentModel() is used. 137 * @param string $format The format to use for deserialization. If not 138 * given, the model's default format is used. 139 * 140 * @throws MWException If model ID or format is not supported or if the text can not be 141 * unserialized using the format. 142 * @return Content A Content object representing the text. 143 */ 144 public static function makeContent( $text, Title $title = null, 145 $modelId = null, $format = null ) { 146 if ( is_null( $modelId ) ) { 147 if ( is_null( $title ) ) { 148 throw new MWException( "Must provide a Title object or a content model ID." ); 149 } 150 151 $modelId = $title->getContentModel(); 152 } 153 154 $handler = ContentHandler::getForModelID( $modelId ); 155 156 return $handler->unserializeContent( $text, $format ); 157 } 158 159 /** 160 * Returns the name of the default content model to be used for the page 161 * with the given title. 162 * 163 * Note: There should rarely be need to call this method directly. 164 * To determine the actual content model for a given page, use 165 * Title::getContentModel(). 166 * 167 * Which model is to be used by default for the page is determined based 168 * on several factors: 169 * - The global setting $wgNamespaceContentModels specifies a content model 170 * per namespace. 171 * - The hook ContentHandlerDefaultModelFor may be used to override the page's default 172 * model. 173 * - Pages in NS_MEDIAWIKI and NS_USER default to the CSS or JavaScript 174 * model if they end in .js or .css, respectively. 175 * - Pages in NS_MEDIAWIKI default to the wikitext model otherwise. 176 * - The hook TitleIsCssOrJsPage may be used to force a page to use the CSS 177 * or JavaScript model. This is a compatibility feature. The ContentHandlerDefaultModelFor 178 * hook should be used instead if possible. 179 * - The hook TitleIsWikitextPage may be used to force a page to use the 180 * wikitext model. This is a compatibility feature. The ContentHandlerDefaultModelFor 181 * hook should be used instead if possible. 182 * 183 * If none of the above applies, the wikitext model is used. 184 * 185 * Note: this is used by, and may thus not use, Title::getContentModel() 186 * 187 * @since 1.21 188 * 189 * @param Title $title 190 * 191 * @return string Default model name for the page given by $title 192 */ 193 public static function getDefaultModelFor( Title $title ) { 194 // NOTE: this method must not rely on $title->getContentModel() directly or indirectly, 195 // because it is used to initialize the mContentModel member. 196 197 $ns = $title->getNamespace(); 198 199 $ext = false; 200 $m = null; 201 $model = MWNamespace::getNamespaceContentModel( $ns ); 202 203 // Hook can determine default model 204 if ( !wfRunHooks( 'ContentHandlerDefaultModelFor', array( $title, &$model ) ) ) { 205 if ( !is_null( $model ) ) { 206 return $model; 207 } 208 } 209 210 // Could this page contain custom CSS or JavaScript, based on the title? 211 $isCssOrJsPage = NS_MEDIAWIKI == $ns && preg_match( '!\.(css|js)$!u', $title->getText(), $m ); 212 if ( $isCssOrJsPage ) { 213 $ext = $m[1]; 214 } 215 216 // Hook can force JS/CSS 217 wfRunHooks( 'TitleIsCssOrJsPage', array( $title, &$isCssOrJsPage ) ); 218 219 // Is this a .css subpage of a user page? 220 $isJsCssSubpage = NS_USER == $ns 221 && !$isCssOrJsPage 222 && preg_match( "/\\/.*\\.(js|css)$/", $title->getText(), $m ); 223 if ( $isJsCssSubpage ) { 224 $ext = $m[1]; 225 } 226 227 // Is this wikitext, according to $wgNamespaceContentModels or the DefaultModelFor hook? 228 $isWikitext = is_null( $model ) || $model == CONTENT_MODEL_WIKITEXT; 229 $isWikitext = $isWikitext && !$isCssOrJsPage && !$isJsCssSubpage; 230 231 // Hook can override $isWikitext 232 wfRunHooks( 'TitleIsWikitextPage', array( $title, &$isWikitext ) ); 233 234 if ( !$isWikitext ) { 235 switch ( $ext ) { 236 case 'js': 237 return CONTENT_MODEL_JAVASCRIPT; 238 case 'css': 239 return CONTENT_MODEL_CSS; 240 default: 241 return is_null( $model ) ? CONTENT_MODEL_TEXT : $model; 242 } 243 } 244 245 // We established that it must be wikitext 246 247 return CONTENT_MODEL_WIKITEXT; 248 } 249 250 /** 251 * Returns the appropriate ContentHandler singleton for the given title. 252 * 253 * @since 1.21 254 * 255 * @param Title $title 256 * 257 * @return ContentHandler 258 */ 259 public static function getForTitle( Title $title ) { 260 $modelId = $title->getContentModel(); 261 262 return ContentHandler::getForModelID( $modelId ); 263 } 264 265 /** 266 * Returns the appropriate ContentHandler singleton for the given Content 267 * object. 268 * 269 * @since 1.21 270 * 271 * @param Content $content 272 * 273 * @return ContentHandler 274 */ 275 public static function getForContent( Content $content ) { 276 $modelId = $content->getModel(); 277 278 return ContentHandler::getForModelID( $modelId ); 279 } 280 281 /** 282 * @var array A Cache of ContentHandler instances by model id 283 */ 284 protected static $handlers; 285 286 /** 287 * Returns the ContentHandler singleton for the given model ID. Use the 288 * CONTENT_MODEL_XXX constants to identify the desired content model. 289 * 290 * ContentHandler singletons are taken from the global $wgContentHandlers 291 * array. Keys in that array are model names, the values are either 292 * ContentHandler singleton objects, or strings specifying the appropriate 293 * subclass of ContentHandler. 294 * 295 * If a class name is encountered when looking up the singleton for a given 296 * model name, the class is instantiated and the class name is replaced by 297 * the resulting singleton in $wgContentHandlers. 298 * 299 * If no ContentHandler is defined for the desired $modelId, the 300 * ContentHandler may be provided by the ContentHandlerForModelID hook. 301 * If no ContentHandler can be determined, an MWException is raised. 302 * 303 * @since 1.21 304 * 305 * @param string $modelId The ID of the content model for which to get a 306 * handler. Use CONTENT_MODEL_XXX constants. 307 * 308 * @throws MWException If no handler is known for the model ID. 309 * @return ContentHandler The ContentHandler singleton for handling the model given by the ID. 310 */ 311 public static function getForModelID( $modelId ) { 312 global $wgContentHandlers; 313 314 if ( isset( ContentHandler::$handlers[$modelId] ) ) { 315 return ContentHandler::$handlers[$modelId]; 316 } 317 318 if ( empty( $wgContentHandlers[$modelId] ) ) { 319 $handler = null; 320 321 wfRunHooks( 'ContentHandlerForModelID', array( $modelId, &$handler ) ); 322 323 if ( $handler === null ) { 324 throw new MWException( "No handler for model '$modelId' registered in \$wgContentHandlers" ); 325 } 326 327 if ( !( $handler instanceof ContentHandler ) ) { 328 throw new MWException( "ContentHandlerForModelID must supply a ContentHandler instance" ); 329 } 330 } else { 331 $class = $wgContentHandlers[$modelId]; 332 $handler = new $class( $modelId ); 333 334 if ( !( $handler instanceof ContentHandler ) ) { 335 throw new MWException( "$class from \$wgContentHandlers is not " . 336 "compatible with ContentHandler" ); 337 } 338 } 339 340 wfDebugLog( 'ContentHandler', 'Created handler for ' . $modelId 341 . ': ' . get_class( $handler ) ); 342 343 ContentHandler::$handlers[$modelId] = $handler; 344 345 return ContentHandler::$handlers[$modelId]; 346 } 347 348 /** 349 * Returns the localized name for a given content model. 350 * 351 * Model names are localized using system messages. Message keys 352 * have the form content-model-$name, where $name is getContentModelName( $id ). 353 * 354 * @param string $name The content model ID, as given by a CONTENT_MODEL_XXX 355 * constant or returned by Revision::getContentModel(). 356 * 357 * @throws MWException If the model ID isn't known. 358 * @return string The content model's localized name. 359 */ 360 public static function getLocalizedName( $name ) { 361 // Messages: content-model-wikitext, content-model-text, 362 // content-model-javascript, content-model-css 363 $key = "content-model-$name"; 364 365 $msg = wfMessage( $key ); 366 367 return $msg->exists() ? $msg->plain() : $name; 368 } 369 370 public static function getContentModels() { 371 global $wgContentHandlers; 372 373 return array_keys( $wgContentHandlers ); 374 } 375 376 public static function getAllContentFormats() { 377 global $wgContentHandlers; 378 379 $formats = array(); 380 381 foreach ( $wgContentHandlers as $model => $class ) { 382 $handler = ContentHandler::getForModelID( $model ); 383 $formats = array_merge( $formats, $handler->getSupportedFormats() ); 384 } 385 386 $formats = array_unique( $formats ); 387 388 return $formats; 389 } 390 391 // ------------------------------------------------------------------------ 392 393 /** 394 * @var string 395 */ 396 protected $mModelID; 397 398 /** 399 * @var string[] 400 */ 401 protected $mSupportedFormats; 402 403 /** 404 * Constructor, initializing the ContentHandler instance with its model ID 405 * and a list of supported formats. Values for the parameters are typically 406 * provided as literals by subclass's constructors. 407 * 408 * @param string $modelId (use CONTENT_MODEL_XXX constants). 409 * @param string[] $formats List for supported serialization formats 410 * (typically as MIME types) 411 */ 412 public function __construct( $modelId, $formats ) { 413 $this->mModelID = $modelId; 414 $this->mSupportedFormats = $formats; 415 416 $this->mModelName = preg_replace( '/(Content)?Handler$/', '', get_class( $this ) ); 417 $this->mModelName = preg_replace( '/[_\\\\]/', '', $this->mModelName ); 418 $this->mModelName = strtolower( $this->mModelName ); 419 } 420 421 /** 422 * Serializes a Content object of the type supported by this ContentHandler. 423 * 424 * @since 1.21 425 * 426 * @param Content $content The Content object to serialize 427 * @param string $format The desired serialization format 428 * 429 * @return string Serialized form of the content 430 */ 431 abstract public function serializeContent( Content $content, $format = null ); 432 433 /** 434 * Applies transformations on export (returns the blob unchanged per default). 435 * Subclasses may override this to perform transformations such as conversion 436 * of legacy formats or filtering of internal meta-data. 437 * 438 * @param string $blob The blob to be exported 439 * @param string|null $format The blob's serialization format 440 * 441 * @return string 442 */ 443 public function exportTransform( $blob, $format = null ) { 444 return $blob; 445 } 446 447 /** 448 * Unserializes a Content object of the type supported by this ContentHandler. 449 * 450 * @since 1.21 451 * 452 * @param string $blob Serialized form of the content 453 * @param string $format The format used for serialization 454 * 455 * @return Content The Content object created by deserializing $blob 456 */ 457 abstract public function unserializeContent( $blob, $format = null ); 458 459 /** 460 * Apply import transformation (per default, returns $blob unchanged). 461 * This gives subclasses an opportunity to transform data blobs on import. 462 * 463 * @since 1.24 464 * 465 * @param string $blob 466 * @param string|null $format 467 * 468 * @return string 469 */ 470 public function importTransform( $blob, $format = null ) { 471 return $blob; 472 } 473 474 /** 475 * Creates an empty Content object of the type supported by this 476 * ContentHandler. 477 * 478 * @since 1.21 479 * 480 * @return Content 481 */ 482 abstract public function makeEmptyContent(); 483 484 /** 485 * Creates a new Content object that acts as a redirect to the given page, 486 * or null if redirects are not supported by this content model. 487 * 488 * This default implementation always returns null. Subclasses supporting redirects 489 * must override this method. 490 * 491 * Note that subclasses that override this method to return a Content object 492 * should also override supportsRedirects() to return true. 493 * 494 * @since 1.21 495 * 496 * @param Title $destination The page to redirect to. 497 * @param string $text Text to include in the redirect, if possible. 498 * 499 * @return Content Always null. 500 */ 501 public function makeRedirectContent( Title $destination, $text = '' ) { 502 return null; 503 } 504 505 /** 506 * Returns the model id that identifies the content model this 507 * ContentHandler can handle. Use with the CONTENT_MODEL_XXX constants. 508 * 509 * @since 1.21 510 * 511 * @return string The model ID 512 */ 513 public function getModelID() { 514 return $this->mModelID; 515 } 516 517 /** 518 * @since 1.21 519 * 520 * @param string $model_id The model to check 521 * 522 * @throws MWException If the model ID is not the ID of the content model supported by this 523 * ContentHandler. 524 */ 525 protected function checkModelID( $model_id ) { 526 if ( $model_id !== $this->mModelID ) { 527 throw new MWException( "Bad content model: " . 528 "expected {$this->mModelID} " . 529 "but got $model_id." ); 530 } 531 } 532 533 /** 534 * Returns a list of serialization formats supported by the 535 * serializeContent() and unserializeContent() methods of this 536 * ContentHandler. 537 * 538 * @since 1.21 539 * 540 * @return string[] List of serialization formats as MIME type like strings 541 */ 542 public function getSupportedFormats() { 543 return $this->mSupportedFormats; 544 } 545 546 /** 547 * The format used for serialization/deserialization by default by this 548 * ContentHandler. 549 * 550 * This default implementation will return the first element of the array 551 * of formats that was passed to the constructor. 552 * 553 * @since 1.21 554 * 555 * @return string The name of the default serialization format as a MIME type 556 */ 557 public function getDefaultFormat() { 558 return $this->mSupportedFormats[0]; 559 } 560 561 /** 562 * Returns true if $format is a serialization format supported by this 563 * ContentHandler, and false otherwise. 564 * 565 * Note that if $format is null, this method always returns true, because 566 * null means "use the default format". 567 * 568 * @since 1.21 569 * 570 * @param string $format The serialization format to check 571 * 572 * @return bool 573 */ 574 public function isSupportedFormat( $format ) { 575 if ( !$format ) { 576 return true; // this means "use the default" 577 } 578 579 return in_array( $format, $this->mSupportedFormats ); 580 } 581 582 /** 583 * Convenient for checking whether a format provided as a parameter is actually supported. 584 * 585 * @param string $format The serialization format to check 586 * 587 * @throws MWException If the format is not supported by this content handler. 588 */ 589 protected function checkFormat( $format ) { 590 if ( !$this->isSupportedFormat( $format ) ) { 591 throw new MWException( 592 "Format $format is not supported for content model " 593 . $this->getModelID() 594 ); 595 } 596 } 597 598 /** 599 * Returns overrides for action handlers. 600 * Classes listed here will be used instead of the default one when 601 * (and only when) $wgActions[$action] === true. This allows subclasses 602 * to override the default action handlers. 603 * 604 * @since 1.21 605 * 606 * @return array Always an empty array. 607 */ 608 public function getActionOverrides() { 609 return array(); 610 } 611 612 /** 613 * Factory for creating an appropriate DifferenceEngine for this content model. 614 * 615 * @since 1.21 616 * 617 * @param IContextSource $context Context to use, anything else will be ignored. 618 * @param int $old Revision ID we want to show and diff with. 619 * @param int|string $new Either a revision ID or one of the strings 'cur', 'prev' or 'next'. 620 * @param int $rcid FIXME: Deprecated, no longer used. Defaults to 0. 621 * @param bool $refreshCache If set, refreshes the diff cache. Defaults to false. 622 * @param bool $unhide If set, allow viewing deleted revs. Defaults to false. 623 * 624 * @return DifferenceEngine 625 */ 626 public function createDifferenceEngine( IContextSource $context, $old = 0, $new = 0, 627 $rcid = 0, //FIXME: Deprecated, no longer used 628 $refreshCache = false, $unhide = false ) { 629 $diffEngineClass = $this->getDiffEngineClass(); 630 631 return new $diffEngineClass( $context, $old, $new, $rcid, $refreshCache, $unhide ); 632 } 633 634 /** 635 * Get the language in which the content of the given page is written. 636 * 637 * This default implementation just returns $wgContLang (except for pages 638 * in the MediaWiki namespace) 639 * 640 * Note that the pages language is not cacheable, since it may in some 641 * cases depend on user settings. 642 * 643 * Also note that the page language may or may not depend on the actual content of the page, 644 * that is, this method may load the content in order to determine the language. 645 * 646 * @since 1.21 647 * 648 * @param Title $title The page to determine the language for. 649 * @param Content $content The page's content, if you have it handy, to avoid reloading it. 650 * 651 * @return Language The page's language 652 */ 653 public function getPageLanguage( Title $title, Content $content = null ) { 654 global $wgContLang, $wgLang; 655 $pageLang = $wgContLang; 656 657 if ( $title->getNamespace() == NS_MEDIAWIKI ) { 658 // Parse mediawiki messages with correct target language 659 list( /* $unused */, $lang ) = MessageCache::singleton()->figureMessage( $title->getText() ); 660 $pageLang = wfGetLangObj( $lang ); 661 } 662 663 wfRunHooks( 'PageContentLanguage', array( $title, &$pageLang, $wgLang ) ); 664 665 return wfGetLangObj( $pageLang ); 666 } 667 668 /** 669 * Get the language in which the content of this page is written when 670 * viewed by user. Defaults to $this->getPageLanguage(), but if the user 671 * specified a preferred variant, the variant will be used. 672 * 673 * This default implementation just returns $this->getPageLanguage( $title, $content ) unless 674 * the user specified a preferred variant. 675 * 676 * Note that the pages view language is not cacheable, since it depends on user settings. 677 * 678 * Also note that the page language may or may not depend on the actual content of the page, 679 * that is, this method may load the content in order to determine the language. 680 * 681 * @since 1.21 682 * 683 * @param Title $title The page to determine the language for. 684 * @param Content $content The page's content, if you have it handy, to avoid reloading it. 685 * 686 * @return Language The page's language for viewing 687 */ 688 public function getPageViewLanguage( Title $title, Content $content = null ) { 689 $pageLang = $this->getPageLanguage( $title, $content ); 690 691 if ( $title->getNamespace() !== NS_MEDIAWIKI ) { 692 // If the user chooses a variant, the content is actually 693 // in a language whose code is the variant code. 694 $variant = $pageLang->getPreferredVariant(); 695 if ( $pageLang->getCode() !== $variant ) { 696 $pageLang = Language::factory( $variant ); 697 } 698 } 699 700 return $pageLang; 701 } 702 703 /** 704 * Determines whether the content type handled by this ContentHandler 705 * can be used on the given page. 706 * 707 * This default implementation always returns true. 708 * Subclasses may override this to restrict the use of this content model to specific locations, 709 * typically based on the namespace or some other aspect of the title, such as a special suffix 710 * (e.g. ".svg" for SVG content). 711 * 712 * @note this calls the ContentHandlerCanBeUsedOn hook which may be used to override which 713 * content model can be used where. 714 * 715 * @param Title $title The page's title. 716 * 717 * @return bool True if content of this kind can be used on the given page, false otherwise. 718 */ 719 public function canBeUsedOn( Title $title ) { 720 $ok = true; 721 722 wfRunHooks( 'ContentModelCanBeUsedOn', array( $this->getModelID(), $title, &$ok ) ); 723 724 return $ok; 725 } 726 727 /** 728 * Returns the name of the diff engine to use. 729 * 730 * @since 1.21 731 * 732 * @return string 733 */ 734 protected function getDiffEngineClass() { 735 return 'DifferenceEngine'; 736 } 737 738 /** 739 * Attempts to merge differences between three versions. Returns a new 740 * Content object for a clean merge and false for failure or a conflict. 741 * 742 * This default implementation always returns false. 743 * 744 * @since 1.21 745 * 746 * @param Content $oldContent The page's previous content. 747 * @param Content $myContent One of the page's conflicting contents. 748 * @param Content $yourContent One of the page's conflicting contents. 749 * 750 * @return Content|bool Always false. 751 */ 752 public function merge3( Content $oldContent, Content $myContent, Content $yourContent ) { 753 return false; 754 } 755 756 /** 757 * Return an applicable auto-summary if one exists for the given edit. 758 * 759 * @since 1.21 760 * 761 * @param Content $oldContent The previous text of the page. 762 * @param Content $newContent The submitted text of the page. 763 * @param int $flags Bit mask: a bit mask of flags submitted for the edit. 764 * 765 * @return string An appropriate auto-summary, or an empty string. 766 */ 767 public function getAutosummary( Content $oldContent = null, Content $newContent = null, 768 $flags ) { 769 // Decide what kind of auto-summary is needed. 770 771 // Redirect auto-summaries 772 773 /** 774 * @var $ot Title 775 * @var $rt Title 776 */ 777 778 $ot = !is_null( $oldContent ) ? $oldContent->getRedirectTarget() : null; 779 $rt = !is_null( $newContent ) ? $newContent->getRedirectTarget() : null; 780 781 if ( is_object( $rt ) ) { 782 if ( !is_object( $ot ) 783 || !$rt->equals( $ot ) 784 || $ot->getFragment() != $rt->getFragment() 785 ) { 786 $truncatedtext = $newContent->getTextForSummary( 787 250 788 - strlen( wfMessage( 'autoredircomment' )->inContentLanguage()->text() ) 789 - strlen( $rt->getFullText() ) ); 790 791 return wfMessage( 'autoredircomment', $rt->getFullText() ) 792 ->rawParams( $truncatedtext )->inContentLanguage()->text(); 793 } 794 } 795 796 // New page auto-summaries 797 if ( $flags & EDIT_NEW && $newContent->getSize() > 0 ) { 798 // If they're making a new article, give its text, truncated, in 799 // the summary. 800 801 $truncatedtext = $newContent->getTextForSummary( 802 200 - strlen( wfMessage( 'autosumm-new' )->inContentLanguage()->text() ) ); 803 804 return wfMessage( 'autosumm-new' )->rawParams( $truncatedtext ) 805 ->inContentLanguage()->text(); 806 } 807 808 // Blanking auto-summaries 809 if ( !empty( $oldContent ) && $oldContent->getSize() > 0 && $newContent->getSize() == 0 ) { 810 return wfMessage( 'autosumm-blank' )->inContentLanguage()->text(); 811 } elseif ( !empty( $oldContent ) 812 && $oldContent->getSize() > 10 * $newContent->getSize() 813 && $newContent->getSize() < 500 814 ) { 815 // Removing more than 90% of the article 816 817 $truncatedtext = $newContent->getTextForSummary( 818 200 - strlen( wfMessage( 'autosumm-replace' )->inContentLanguage()->text() ) ); 819 820 return wfMessage( 'autosumm-replace' )->rawParams( $truncatedtext ) 821 ->inContentLanguage()->text(); 822 } 823 824 // New blank article auto-summary 825 if ( $flags & EDIT_NEW && $newContent->isEmpty() ) { 826 return wfMessage( 'autosumm-newblank' )->inContentLanguage()->text(); 827 } 828 829 // If we reach this point, there's no applicable auto-summary for our 830 // case, so our auto-summary is empty. 831 return ''; 832 } 833 834 /** 835 * Auto-generates a deletion reason 836 * 837 * @since 1.21 838 * 839 * @param Title $title The page's title 840 * @param bool &$hasHistory Whether the page has a history 841 * 842 * @return mixed String containing deletion reason or empty string, or 843 * boolean false if no revision occurred 844 * 845 * @todo &$hasHistory is extremely ugly, it's here because 846 * WikiPage::getAutoDeleteReason() and Article::generateReason() 847 * have it / want it. 848 */ 849 public function getAutoDeleteReason( Title $title, &$hasHistory ) { 850 $dbw = wfGetDB( DB_MASTER ); 851 852 // Get the last revision 853 $rev = Revision::newFromTitle( $title ); 854 855 if ( is_null( $rev ) ) { 856 return false; 857 } 858 859 // Get the article's contents 860 $content = $rev->getContent(); 861 $blank = false; 862 863 // If the page is blank, use the text from the previous revision, 864 // which can only be blank if there's a move/import/protect dummy 865 // revision involved 866 if ( !$content || $content->isEmpty() ) { 867 $prev = $rev->getPrevious(); 868 869 if ( $prev ) { 870 $rev = $prev; 871 $content = $rev->getContent(); 872 $blank = true; 873 } 874 } 875 876 $this->checkModelID( $rev->getContentModel() ); 877 878 // Find out if there was only one contributor 879 // Only scan the last 20 revisions 880 $res = $dbw->select( 'revision', 'rev_user_text', 881 array( 882 'rev_page' => $title->getArticleID(), 883 $dbw->bitAnd( 'rev_deleted', Revision::DELETED_USER ) . ' = 0' 884 ), 885 __METHOD__, 886 array( 'LIMIT' => 20 ) 887 ); 888 889 if ( $res === false ) { 890 // This page has no revisions, which is very weird 891 return false; 892 } 893 894 $hasHistory = ( $res->numRows() > 1 ); 895 $row = $dbw->fetchObject( $res ); 896 897 if ( $row ) { // $row is false if the only contributor is hidden 898 $onlyAuthor = $row->rev_user_text; 899 // Try to find a second contributor 900 foreach ( $res as $row ) { 901 if ( $row->rev_user_text != $onlyAuthor ) { // Bug 22999 902 $onlyAuthor = false; 903 break; 904 } 905 } 906 } else { 907 $onlyAuthor = false; 908 } 909 910 // Generate the summary with a '$1' placeholder 911 if ( $blank ) { 912 // The current revision is blank and the one before is also 913 // blank. It's just not our lucky day 914 $reason = wfMessage( 'exbeforeblank', '$1' )->inContentLanguage()->text(); 915 } else { 916 if ( $onlyAuthor ) { 917 $reason = wfMessage( 918 'excontentauthor', 919 '$1', 920 $onlyAuthor 921 )->inContentLanguage()->text(); 922 } else { 923 $reason = wfMessage( 'excontent', '$1' )->inContentLanguage()->text(); 924 } 925 } 926 927 if ( $reason == '-' ) { 928 // Allow these UI messages to be blanked out cleanly 929 return ''; 930 } 931 932 // Max content length = max comment length - length of the comment (excl. $1) 933 $text = $content ? $content->getTextForSummary( 255 - ( strlen( $reason ) - 2 ) ) : ''; 934 935 // Now replace the '$1' placeholder 936 $reason = str_replace( '$1', $text, $reason ); 937 938 return $reason; 939 } 940 941 /** 942 * Get the Content object that needs to be saved in order to undo all revisions 943 * between $undo and $undoafter. Revisions must belong to the same page, 944 * must exist and must not be deleted. 945 * 946 * @since 1.21 947 * 948 * @param Revision $current The current text 949 * @param Revision $undo The revision to undo 950 * @param Revision $undoafter Must be an earlier revision than $undo 951 * 952 * @return mixed String on success, false on failure 953 */ 954 public function getUndoContent( Revision $current, Revision $undo, Revision $undoafter ) { 955 $cur_content = $current->getContent(); 956 957 if ( empty( $cur_content ) ) { 958 return false; // no page 959 } 960 961 $undo_content = $undo->getContent(); 962 $undoafter_content = $undoafter->getContent(); 963 964 if ( !$undo_content || !$undoafter_content ) { 965 return false; // no content to undo 966 } 967 968 $this->checkModelID( $cur_content->getModel() ); 969 $this->checkModelID( $undo_content->getModel() ); 970 $this->checkModelID( $undoafter_content->getModel() ); 971 972 if ( $cur_content->equals( $undo_content ) ) { 973 // No use doing a merge if it's just a straight revert. 974 return $undoafter_content; 975 } 976 977 $undone_content = $this->merge3( $undo_content, $undoafter_content, $cur_content ); 978 979 return $undone_content; 980 } 981 982 /** 983 * Get parser options suitable for rendering and caching the article 984 * 985 * @param IContextSource|User|string $context One of the following: 986 * - IContextSource: Use the User and the Language of the provided 987 * context 988 * - User: Use the provided User object and $wgLang for the language, 989 * so use an IContextSource object if possible. 990 * - 'canonical': Canonical options (anonymous user with default 991 * preferences and content language). 992 * 993 * @throws MWException 994 * @return ParserOptions 995 */ 996 public function makeParserOptions( $context ) { 997 global $wgContLang, $wgEnableParserLimitReporting; 998 999 if ( $context instanceof IContextSource ) { 1000 $options = ParserOptions::newFromContext( $context ); 1001 } elseif ( $context instanceof User ) { // settings per user (even anons) 1002 $options = ParserOptions::newFromUser( $context ); 1003 } elseif ( $context === 'canonical' ) { // canonical settings 1004 $options = ParserOptions::newFromUserAndLang( new User, $wgContLang ); 1005 } else { 1006 throw new MWException( "Bad context for parser options: $context" ); 1007 } 1008 1009 $options->enableLimitReport( $wgEnableParserLimitReporting ); // show inclusion/loop reports 1010 $options->setTidy( true ); // fix bad HTML 1011 1012 return $options; 1013 } 1014 1015 /** 1016 * Returns true for content models that support caching using the 1017 * ParserCache mechanism. See WikiPage::isParserCacheUsed(). 1018 * 1019 * @since 1.21 1020 * 1021 * @return bool Always false. 1022 */ 1023 public function isParserCacheSupported() { 1024 return false; 1025 } 1026 1027 /** 1028 * Returns true if this content model supports sections. 1029 * This default implementation returns false. 1030 * 1031 * Content models that return true here should also implement 1032 * Content::getSection, Content::replaceSection, etc. to handle sections.. 1033 * 1034 * @return bool Always false. 1035 */ 1036 public function supportsSections() { 1037 return false; 1038 } 1039 1040 /** 1041 * Returns true if this content model supports redirects. 1042 * This default implementation returns false. 1043 * 1044 * Content models that return true here should also implement 1045 * ContentHandler::makeRedirectContent to return a Content object. 1046 * 1047 * @return bool Always false. 1048 */ 1049 public function supportsRedirects() { 1050 return false; 1051 } 1052 1053 /** 1054 * Logs a deprecation warning, visible if $wgDevelopmentWarnings, but only if 1055 * self::$enableDeprecationWarnings is set to true. 1056 * 1057 * @param string $func The name of the deprecated function 1058 * @param string $version The version since the method is deprecated. Usually 1.21 1059 * for ContentHandler related stuff. 1060 * @param string|bool $component : Component to which the function belongs. 1061 * If false, it is assumed the function is in MediaWiki core. 1062 * 1063 * @see ContentHandler::$enableDeprecationWarnings 1064 * @see wfDeprecated 1065 */ 1066 public static function deprecated( $func, $version, $component = false ) { 1067 if ( self::$enableDeprecationWarnings ) { 1068 wfDeprecated( $func, $version, $component, 3 ); 1069 } 1070 } 1071 1072 /** 1073 * Call a legacy hook that uses text instead of Content objects. 1074 * Will log a warning when a matching hook function is registered. 1075 * If the textual representation of the content is changed by the 1076 * hook function, a new Content object is constructed from the new 1077 * text. 1078 * 1079 * @param string $event Event name 1080 * @param array $args Parameters passed to hook functions 1081 * @param bool $warn Whether to log a warning. 1082 * Default to self::$enableDeprecationWarnings. 1083 * May be set to false for testing. 1084 * 1085 * @return bool True if no handler aborted the hook 1086 * 1087 * @see ContentHandler::$enableDeprecationWarnings 1088 */ 1089 public static function runLegacyHooks( $event, $args = array(), 1090 $warn = null 1091 ) { 1092 1093 if ( $warn === null ) { 1094 $warn = self::$enableDeprecationWarnings; 1095 } 1096 1097 if ( !Hooks::isRegistered( $event ) ) { 1098 return true; // nothing to do here 1099 } 1100 1101 if ( $warn ) { 1102 // Log information about which handlers are registered for the legacy hook, 1103 // so we can find and fix them. 1104 1105 $handlers = Hooks::getHandlers( $event ); 1106 $handlerInfo = array(); 1107 1108 wfSuppressWarnings(); 1109 1110 foreach ( $handlers as $handler ) { 1111 if ( is_array( $handler ) ) { 1112 if ( is_object( $handler[0] ) ) { 1113 $info = get_class( $handler[0] ); 1114 } else { 1115 $info = $handler[0]; 1116 } 1117 1118 if ( isset( $handler[1] ) ) { 1119 $info .= '::' . $handler[1]; 1120 } 1121 } elseif ( is_object( $handler ) ) { 1122 $info = get_class( $handler[0] ); 1123 $info .= '::on' . $event; 1124 } else { 1125 $info = $handler; 1126 } 1127 1128 $handlerInfo[] = $info; 1129 } 1130 1131 wfRestoreWarnings(); 1132 1133 wfWarn( "Using obsolete hook $event via ContentHandler::runLegacyHooks()! Handlers: " . 1134 implode( ', ', $handlerInfo ), 2 ); 1135 } 1136 1137 // convert Content objects to text 1138 $contentObjects = array(); 1139 $contentTexts = array(); 1140 1141 foreach ( $args as $k => $v ) { 1142 if ( $v instanceof Content ) { 1143 /* @var Content $v */ 1144 1145 $contentObjects[$k] = $v; 1146 1147 $v = $v->serialize(); 1148 $contentTexts[$k] = $v; 1149 $args[$k] = $v; 1150 } 1151 } 1152 1153 // call the hook functions 1154 $ok = wfRunHooks( $event, $args ); 1155 1156 // see if the hook changed the text 1157 foreach ( $contentTexts as $k => $orig ) { 1158 /* @var Content $content */ 1159 1160 $modified = $args[$k]; 1161 $content = $contentObjects[$k]; 1162 1163 if ( $modified !== $orig ) { 1164 // text was changed, create updated Content object 1165 $content = $content->getContentHandler()->unserializeContent( $modified ); 1166 } 1167 1168 $args[$k] = $content; 1169 } 1170 1171 return $ok; 1172 } 1173 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 14:03:12 2014 | Cross-referenced by PHPXref 0.7.1 |