[ Index ]

PHP Cross Reference of MediaWiki-1.24.0

title

Body

[close]

/includes/content/ -> ContentHandler.php (source)

   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  }


Generated: Fri Nov 28 14:03:12 2014 Cross-referenced by PHPXref 0.7.1