Index: branches/Wikidata/phase3/includes/Defines.php |
— | — | @@ -249,3 +249,12 @@ |
250 | 250 | define( 'PROTO_CURRENT', null ); |
251 | 251 | define( 'PROTO_CANONICAL', 1 ); |
252 | 252 | define( 'PROTO_INTERNAL', 2 ); |
| 253 | + |
| 254 | +/** |
| 255 | + * Content model names, used by Content and ContentHandler |
| 256 | + */ |
| 257 | +define('CONTENT_MODEL_WIKITEXT', 'wikitext'); |
| 258 | +define('CONTENT_MODEL_JAVASCRIPT', 'javascript'); |
| 259 | +define('CONTENT_MODEL_CSS', 'css'); |
| 260 | +define('CONTENT_MODEL_TEXT', 'text'); |
| 261 | + |
Index: branches/Wikidata/phase3/includes/Article.php |
— | — | @@ -37,7 +37,7 @@ |
38 | 38 | */ |
39 | 39 | public $mParserOptions; |
40 | 40 | |
41 | | - var $mContent; // !< |
| 41 | + var $mContent; // !< #FIXME: use Content object! |
42 | 42 | var $mContentLoaded = false; // !< |
43 | 43 | var $mOldId; // !< |
44 | 44 | |
— | — | @@ -557,6 +557,7 @@ |
558 | 558 | |
559 | 559 | # Pages containing custom CSS or JavaScript get special treatment |
560 | 560 | if ( $this->getTitle()->isCssOrJsPage() || $this->getTitle()->isCssJsSubpage() ) { |
| 561 | + #FIXME: use Content object instead! |
561 | 562 | wfDebug( __METHOD__ . ": showing CSS/JS source\n" ); |
562 | 563 | $this->showCssOrJsPage(); |
563 | 564 | $outputDone = true; |
— | — | @@ -694,17 +695,18 @@ |
695 | 696 | * This is hooked by SyntaxHighlight_GeSHi to do syntax highlighting of these |
696 | 697 | * page views. |
697 | 698 | */ |
698 | | - protected function showCssOrJsPage() { |
| 699 | + protected function showCssOrJsPage() { #FIXME: deprecate, keep for BC |
699 | 700 | global $wgOut; |
700 | 701 | |
701 | 702 | $dir = $this->getContext()->getLanguage()->getDir(); |
702 | 703 | $lang = $this->getContext()->getLanguage()->getCode(); |
703 | 704 | |
704 | 705 | $wgOut->wrapWikiMsg( "<div id='mw-clearyourcache' lang='$lang' dir='$dir' class='mw-content-$dir'>\n$1\n</div>", |
705 | | - 'clearyourcache' ); |
| 706 | + 'clearyourcache' ); #FIXME: get this from handler |
706 | 707 | |
707 | 708 | // Give hooks a chance to customise the output |
708 | 709 | if ( wfRunHooks( 'ShowRawCssJs', array( $this->mContent, $this->getTitle(), $wgOut ) ) ) { |
| 710 | + #FIXME: use content object instead |
709 | 711 | // Wrap the whole lot in a <pre> and don't parse |
710 | 712 | $m = array(); |
711 | 713 | preg_match( '!\.(css|js)$!u', $this->getTitle()->getText(), $m ); |
Index: branches/Wikidata/phase3/includes/parser/ParserOutput.php |
— | — | @@ -30,7 +30,7 @@ |
31 | 31 | */ |
32 | 32 | function setCacheTime( $t ) { return wfSetVar( $this->mCacheTime, $t ); } |
33 | 33 | |
34 | | - /** |
| 34 | + /**abstract |
35 | 35 | * Sets the number of seconds after which this object should expire. |
36 | 36 | * This value is used with the ParserCache. |
37 | 37 | * If called with a value greater than the value provided at any previous call, |
Index: branches/Wikidata/phase3/includes/Content.php |
— | — | @@ -0,0 +1,166 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +/** |
| 5 | + * A content object represents page content, e.g. the text to show on a page. |
| 6 | + * |
| 7 | + */ |
| 8 | +abstract class Content { |
| 9 | + |
| 10 | + public function __construct( Title $title, $revId, $modelName ) { |
| 11 | + $this->mModelName = $modelName; |
| 12 | + $this->mTitle = $title; |
| 13 | + $this->mRevId = $revId; |
| 14 | + } |
| 15 | + |
| 16 | + public function getModelName() { |
| 17 | + return $this->mModelName; |
| 18 | + } |
| 19 | + |
| 20 | + public function getTitle() { |
| 21 | + return $this->mTitle; |
| 22 | + } |
| 23 | + |
| 24 | + public function getRevId() { |
| 25 | + return $this->mRevId; |
| 26 | + } |
| 27 | + |
| 28 | + public abstract function getSearchText( $obj ); |
| 29 | + |
| 30 | + public abstract function getWikitextForTransclusion( $obj ); |
| 31 | + |
| 32 | + public abstract function getParserOutput( ParserOptions $options = NULL ); |
| 33 | + |
| 34 | + public abstract function getRawData( ); |
| 35 | + |
| 36 | + public function getHtml( ParserOptions $options ) { |
| 37 | + $po = $this->getParserOutput( $options ); |
| 38 | + return $po->getText(); |
| 39 | + } |
| 40 | + |
| 41 | + public function getIndexUpdateJobs( ParserOptions $options , $recursive = true ) { |
| 42 | + $po = $this->getParserOutput( $options ); |
| 43 | + $update = new LinksUpdate( $this->mTitle, $po, $recursive ); |
| 44 | + return $update; |
| 45 | + } |
| 46 | + |
| 47 | + #XXX: is the native model for wikitext a string or the parser output? parse early or parse late? |
| 48 | +} |
| 49 | + |
| 50 | +class TextContent extends Content { |
| 51 | + public function __construct( $text, Title $title, $revId, $modelName ) { |
| 52 | + parent::__construct($title, $revId, $modelName); |
| 53 | + |
| 54 | + $this->mText = $text; |
| 55 | + } |
| 56 | + |
| 57 | + public function getSearchText( $obj ) { |
| 58 | + return $this->getRawData(); |
| 59 | + } |
| 60 | + |
| 61 | + public function getWikitextForTransclusion( $obj ) { |
| 62 | + return $this->getRawData(); |
| 63 | + } |
| 64 | + |
| 65 | + |
| 66 | + public function getParserOutput( ParserOptions $options = null ) { |
| 67 | + # generic implementation, relying on $this->getHtml() |
| 68 | + |
| 69 | + $html = $this->getHtml( $options ); |
| 70 | + $po = new ParserOutput( $html ); |
| 71 | + |
| 72 | + #TODO: cache settings, etc? |
| 73 | + |
| 74 | + return $po; |
| 75 | + } |
| 76 | + |
| 77 | + public function getHtml( ParserOptions $options ) { |
| 78 | + $html = ""; |
| 79 | + $html .= "<pre class=\"mw-code\" dir=\"ltr\">\n"; |
| 80 | + $html .= htmlspecialchars( $this->getRawData() ); |
| 81 | + $html .= "\n</pre>\n"; |
| 82 | + |
| 83 | + return $html; |
| 84 | + } |
| 85 | + |
| 86 | + |
| 87 | + public function getRawData( ) { |
| 88 | + global $wgParser, $wgUser; |
| 89 | + |
| 90 | + $text = $this->mText; |
| 91 | + return $text; |
| 92 | + } |
| 93 | + |
| 94 | +} |
| 95 | + |
| 96 | +class WikitextContent extends TextContent { |
| 97 | + public function __construct( $text, Title $title, $revId = null) { |
| 98 | + parent::__construct($text, $title, $revId, CONTENT_MODEL_WIKITEXT); |
| 99 | + |
| 100 | + $this->mDefaultParserOptions = null; |
| 101 | + } |
| 102 | + |
| 103 | + public function getDefaultParserOptions() { |
| 104 | + global $wgUser, $wgContLang; |
| 105 | + |
| 106 | + if ( !$this->mDefaultParserOptions ) { |
| 107 | + #TODO: use static member?! |
| 108 | + $this->mDefaultParserOptions = ParserOptions::newFromUserAndLang( $wgUser, $wgContLang ); |
| 109 | + } |
| 110 | + |
| 111 | + return $this->mDefaultParserOptions; |
| 112 | + } |
| 113 | + |
| 114 | + public function getParserOutput( ParserOptions $options = null ) { |
| 115 | + global $wgParser; |
| 116 | + |
| 117 | + #TODO: quick local cache: if $options is NULL, use ->mParserOutput! |
| 118 | + #FIXME: need setParserOutput, so we can use stuff from the parser cache?? |
| 119 | + #FIXME: ...or we somehow need to know the parser cache key?? |
| 120 | + |
| 121 | + if ( !$options ) { |
| 122 | + $options = $this->getDefaultParserOptions(); |
| 123 | + } |
| 124 | + |
| 125 | + $po = $wgParser->parse( $this->mText, $this->getTitle(), $options ); |
| 126 | + |
| 127 | + return $po; |
| 128 | + } |
| 129 | + |
| 130 | +} |
| 131 | + |
| 132 | + |
| 133 | +class JavaScriptContent extends TextContent { |
| 134 | + public function __construct( $text, Title $title, $revId = null ) { |
| 135 | + parent::__construct($text, $title, $revId, CONTENT_MODEL_JAVASCRIPT); |
| 136 | + } |
| 137 | + |
| 138 | + public function getHtml( ParserOptions $options ) { |
| 139 | + $html = ""; |
| 140 | + $html .= "<pre class=\"mw-code mw-js\" dir=\"ltr\">\n"; |
| 141 | + $html .= htmlspecialchars( $this->getRawData() ); |
| 142 | + $html .= "\n</pre>\n"; |
| 143 | + |
| 144 | + return $html; |
| 145 | + } |
| 146 | + |
| 147 | +} |
| 148 | + |
| 149 | +class CssContent extends TextContent { |
| 150 | + public function __construct( $text, Title $title, $revId = null ) { |
| 151 | + parent::__construct($text, $title, $revId, CONTENT_MODEL_CSS); |
| 152 | + } |
| 153 | + |
| 154 | + public function getHtml( ParserOptions $options ) { |
| 155 | + $html = ""; |
| 156 | + $html .= "<pre class=\"mw-code mw-css\" dir=\"ltr\">\n"; |
| 157 | + $html .= htmlspecialchars( $this->getRawData() ); |
| 158 | + $html .= "\n</pre>\n"; |
| 159 | + |
| 160 | + return $html; |
| 161 | + } |
| 162 | +} |
| 163 | + |
| 164 | +#TODO: MultipartMultipart < WikipageContent (Main + Links + X) |
| 165 | +#TODO: LinksContent < LanguageLinksContent, CategoriesContent |
| 166 | +#EXAMPLE: CoordinatesContent |
| 167 | +#EXAMPLE: WikidataContent |
Index: branches/Wikidata/phase3/includes/ContentHandler.php |
— | — | @@ -12,7 +12,90 @@ |
13 | 13 | * |
14 | 14 | */ |
15 | 15 | abstract class ContentHandler { |
16 | | - |
| 16 | + |
| 17 | + public static function getDefaultModelFor( Title $title ) { |
| 18 | + global $wgNamespaceContentModels; |
| 19 | + |
| 20 | + # NOTE: this method must not rely on $title->getContentModelName() directly or indirectly, |
| 21 | + # because it is used to initialized the mContentModelName memebr. |
| 22 | + |
| 23 | + $ns = $title->getNamespace(); |
| 24 | + |
| 25 | + $ext = false; |
| 26 | + $m = null; |
| 27 | + $model = null; |
| 28 | + |
| 29 | + if ( !empty( $wgNamespaceContentModels[ $ns ] ) ) { |
| 30 | + $model = $wgNamespaceContentModels[ $ns ]; |
| 31 | + } |
| 32 | + |
| 33 | + # hook can determin default model |
| 34 | + if ( !wfRunHooks( 'DefaultModelFor', array( $title, &$model ) ) ) { #FIXME: document new hook! |
| 35 | + if ( $model ) return $model; |
| 36 | + } |
| 37 | + |
| 38 | + # Could this page contain custom CSS or JavaScript, based on the title? |
| 39 | + $isCssOrJsPage = ( NS_MEDIAWIKI == $ns && preg_match( "!\.(css|js)$!u", $title->getText(), $m ) ); |
| 40 | + if ( $isCssOrJsPage ) $ext = $m[1]; |
| 41 | + |
| 42 | + # hook can force js/css |
| 43 | + wfRunHooks( 'TitleIsCssOrJsPage', array( $title, &$isCssOrJsPage, &$ext ) ); #FIXME: add $ext to hook interface spec |
| 44 | + |
| 45 | + # Is this a .css subpage of a user page? |
| 46 | + $isJsCssSubpage = ( NS_USER == $ns && !$isCssOrJsPage && preg_match( "/\\/.*\\.(js|css)$/", $title->getText(), $m ) ); |
| 47 | + if ( $isJsCssSubpage ) $ext = $m[1]; |
| 48 | + |
| 49 | + # is this wikitext, according to $wgNamespaceContentModels or the DefaultModelFor hook? |
| 50 | + $isWikitext = ( $model == CONTENT_MODEL_WIKITEXT || $model === null ); |
| 51 | + $isWikitext = ( $isWikitext && !$isCssOrJsPage && !$isJsCssSubpage ); |
| 52 | + |
| 53 | + # hook can override $isWikitext |
| 54 | + wfRunHooks( 'TitleIsWikitextPage', array( $title, &$isWikitext ) ); |
| 55 | + |
| 56 | + if ( !$isWikitext ) { |
| 57 | + |
| 58 | + if ( $ext == 'js' ) |
| 59 | + return CONTENT_MODEL_JAVASCRIPT; |
| 60 | + else if ( $ext == 'css' ) |
| 61 | + return CONTENT_MODEL_CSS; |
| 62 | + |
| 63 | + if ( $model ) |
| 64 | + return $model; |
| 65 | + else |
| 66 | + return CONTENT_MODEL_TEXT; |
| 67 | + } |
| 68 | + |
| 69 | + # we established that is must be wikitext |
| 70 | + return CONTENT_MODEL_WIKITEXT; |
| 71 | + } |
| 72 | + |
| 73 | + public static function getForTitle( Title $title ) { |
| 74 | + $modelName = $title->getContentModelName(); |
| 75 | + return ContenteHandler::getForModelName( $modelName ); |
| 76 | + } |
| 77 | + |
| 78 | + public static function getForContent( Content $content ) { |
| 79 | + $modelName = $content->getModelName(); |
| 80 | + return ContenteHandler::getForModelName( $modelName ); |
| 81 | + } |
| 82 | + |
| 83 | + public static function getForModelName( $modelName ) { |
| 84 | + global $wgContentHandlers; |
| 85 | + |
| 86 | + if ( empty( $wgContentHandlers[$modelName] ) ) { |
| 87 | + #FIXME: hook here! |
| 88 | + throw new MWException( "No handler for model $modelName registered in \$wgContentHandlers" ); |
| 89 | + } |
| 90 | + |
| 91 | + if ( is_string( $wgContentHandlers[$modelName] ) ) { |
| 92 | + $class = $wgContentHandlers[$modelName]; |
| 93 | + $wgContentHandlers[$modelName] = new $class( $modelName ); |
| 94 | + } |
| 95 | + |
| 96 | + return $wgContentHandlers[$modelName]; |
| 97 | + } |
| 98 | + |
| 99 | + |
17 | 100 | public function __construct( $modelName, $formats ) { |
18 | 101 | $this->mModelName = $modelName; |
19 | 102 | $this->mSupportedFormats = $formats; |
— | — | @@ -62,7 +145,7 @@ |
63 | 146 | # returns a ParserOutput instance! |
64 | 147 | # are parser options, generic?! |
65 | 148 | |
66 | | - public function doPreSaveTransform( $title, $obj ); |
| 149 | + public abstract function doPreSaveTransform( $title, $obj ); |
67 | 150 | |
68 | 151 | # TODO: getPreloadText() |
69 | 152 | # TODO: preprocess() |
Index: branches/Wikidata/phase3/includes/EditPage.php |
— | — | @@ -2552,6 +2552,7 @@ |
2553 | 2553 | # don't parse non-wikitext pages, show message about preview |
2554 | 2554 | # XXX: stupid php bug won't let us use $this->getContextTitle()->isCssJsSubpage() here -- This note has been there since r3530. Sure the bug was fixed time ago? |
2555 | 2555 | |
| 2556 | + #FIXME: get appropriate content handler! |
2556 | 2557 | if ( $this->isCssJsSubpage || !$this->mTitle->isWikitextPage() ) { |
2557 | 2558 | if( $this->mTitle->isCssJsSubpage() ) { |
2558 | 2559 | $level = 'user'; |
— | — | @@ -2564,6 +2565,7 @@ |
2565 | 2566 | # Used messages to make sure grep find them: |
2566 | 2567 | # Messages: usercsspreview, userjspreview, sitecsspreview, sitejspreview |
2567 | 2568 | if( $level ) { |
| 2569 | + #FIXME: move this crud into ContentHandler class! |
2568 | 2570 | if (preg_match( "/\\.css$/", $this->mTitle->getText() ) ) { |
2569 | 2571 | $previewtext = "<div id='mw-{$level}csspreview'>\n" . wfMsg( "{$level}csspreview" ) . "\n</div>"; |
2570 | 2572 | $class = "mw-code mw-css"; |
Index: branches/Wikidata/phase3/includes/Revision.php |
— | — | @@ -20,6 +20,8 @@ |
21 | 21 | protected $mTextRow; |
22 | 22 | protected $mTitle; |
23 | 23 | protected $mCurrent; |
| 24 | + protected $mContentModelName; |
| 25 | + protected $mContentType; |
24 | 26 | |
25 | 27 | const DELETED_TEXT = 1; |
26 | 28 | const DELETED_COMMENT = 2; |
— | — | @@ -125,6 +127,8 @@ |
126 | 128 | 'deleted' => $row->ar_deleted, |
127 | 129 | 'len' => $row->ar_len, |
128 | 130 | 'sha1' => isset( $row->ar_sha1 ) ? $row->ar_sha1 : null, |
| 131 | + 'content_model' => isset( $row->ar_content_model ) ? $row->ar_content_model : null, |
| 132 | + 'content_type' => isset( $row->ar_content_type ) ? $row->ar_content_type : null, |
129 | 133 | ); |
130 | 134 | if ( isset( $row->ar_text ) && !$row->ar_text_id ) { |
131 | 135 | // Pre-1.5 ar_text row |
— | — | @@ -412,6 +416,18 @@ |
413 | 417 | $this->mTitle = null; |
414 | 418 | } |
415 | 419 | |
| 420 | + if( !isset( $row->rev_content_model ) || is_null( $row->rev_content_model ) ) { |
| 421 | + $this->mContentModelName = null; # determine on demand if needed |
| 422 | + } else { |
| 423 | + $this->mContentModelName = strval( $row->rev_content_model ); |
| 424 | + } |
| 425 | + |
| 426 | + if( !isset( $row->rev_content_type ) || is_null( $row->rev_content_type ) ) { |
| 427 | + $this->mContentType = null; # determine on demand if needed |
| 428 | + } else { |
| 429 | + $this->mContentType = strval( $row->rev_content_type ); |
| 430 | + } |
| 431 | + |
416 | 432 | // Lazy extraction... |
417 | 433 | $this->mText = null; |
418 | 434 | if( isset( $row->old_text ) ) { |
— | — | @@ -445,6 +461,15 @@ |
446 | 462 | $this->mParentId = isset( $row['parent_id'] ) ? intval( $row['parent_id'] ) : null; |
447 | 463 | $this->mSha1 = isset( $row['sha1'] ) ? strval( $row['sha1'] ) : null; |
448 | 464 | |
| 465 | + $this->mContentModelName = isset( $row['content_model'] ) ? strval( $row['content_model'] ) : null; |
| 466 | + $this->mContentType = isset( $row['content_type'] ) ? strval( $row['content_type'] ) : null; |
| 467 | + |
| 468 | + if( !isset( $row->rev_content_type ) || is_null( $row->rev_content_type ) ) { |
| 469 | + $this->mContentType = null; # determine on demand if needed |
| 470 | + } else { |
| 471 | + $this->mContentType = $row->rev_content_type; |
| 472 | + } |
| 473 | + |
449 | 474 | // Enforce spacing trimming on supplied text |
450 | 475 | $this->mComment = isset( $row['comment'] ) ? trim( strval( $row['comment'] ) ) : null; |
451 | 476 | $this->mText = isset( $row['text'] ) ? rtrim( strval( $row['text'] ) ) : null; |
— | — | @@ -460,6 +485,9 @@ |
461 | 486 | if ( $this->mSha1 === null ) { |
462 | 487 | $this->mSha1 = is_null( $this->mText ) ? null : self::base36Sha1( $this->mText ); |
463 | 488 | } |
| 489 | + |
| 490 | + $this->getContentModelName(); # force lazy init |
| 491 | + $this->getContentType(); # force lazy init |
464 | 492 | } else { |
465 | 493 | throw new MWException( 'Revision constructor passed invalid row format.' ); |
466 | 494 | } |
— | — | @@ -723,17 +751,48 @@ |
724 | 752 | * @param $user User object to check for, only if FOR_THIS_USER is passed |
725 | 753 | * to the $audience parameter |
726 | 754 | * @return String |
| 755 | + * @deprectaed in 1.20, use getContent() instead |
727 | 756 | */ |
728 | | - public function getText( $audience = self::FOR_PUBLIC, User $user = null ) { |
729 | | - if( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_TEXT ) ) { |
730 | | - return ''; |
731 | | - } elseif( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_TEXT, $user ) ) { |
732 | | - return ''; |
733 | | - } else { |
734 | | - return $this->getRawText(); |
735 | | - } |
| 757 | + public function getText( $audience = self::FOR_PUBLIC, User $user = null ) { #FIXME: deprecated, replace usage! |
| 758 | + wfDeprecated( __METHOD__, '1.20' ); |
| 759 | + $content = $this->getContent(); |
| 760 | + |
| 761 | + if ( $content == null ) { |
| 762 | + return ""; # not allowed |
| 763 | + } |
| 764 | + |
| 765 | + if ( $content instanceof TextContent ) { |
| 766 | + #FIXME: or check by model name? or define $content->allowRawData()? |
| 767 | + return $content->getRawData(); |
| 768 | + } |
| 769 | + |
| 770 | + #TODO: log this failure! |
| 771 | + return null; |
736 | 772 | } |
737 | 773 | |
| 774 | + /** |
| 775 | + * Fetch revision content if it's available to the specified audience. |
| 776 | + * If the specified audience does not have the ability to view this |
| 777 | + * revision, null will be returned. |
| 778 | + * |
| 779 | + * @param $audience Integer: one of: |
| 780 | + * Revision::FOR_PUBLIC to be displayed to all users |
| 781 | + * Revision::FOR_THIS_USER to be displayed to $wgUser |
| 782 | + * Revision::RAW get the text regardless of permissions |
| 783 | + * @param $user User object to check for, only if FOR_THIS_USER is passed |
| 784 | + * to the $audience parameter |
| 785 | + * @return Content |
| 786 | + */ |
| 787 | + public function getContent( $audience = self::FOR_PUBLIC, User $user = null ) { |
| 788 | + if( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_TEXT ) ) { |
| 789 | + return null; |
| 790 | + } elseif( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_TEXT, $user ) ) { |
| 791 | + return null; |
| 792 | + } else { |
| 793 | + return $this->getContentInternal(); |
| 794 | + } |
| 795 | + } |
| 796 | + |
738 | 797 | /** |
739 | 798 | * Alias for getText(Revision::FOR_THIS_USER) |
740 | 799 | * |
— | — | @@ -750,14 +809,55 @@ |
751 | 810 | * |
752 | 811 | * @return String |
753 | 812 | */ |
754 | | - public function getRawText() { |
755 | | - if( is_null( $this->mText ) ) { |
756 | | - // Revision text is immutable. Load on demand: |
757 | | - $this->mText = $this->loadText(); |
758 | | - } |
759 | | - return $this->mText; |
| 813 | + public function getRawText() { #FIXME: deprecated, replace usage! |
| 814 | + return $this->getText( self::RAW ); |
760 | 815 | } |
761 | 816 | |
| 817 | + protected function getContentInternal() { |
| 818 | + if( is_null( $this->mContent ) ) { |
| 819 | + // Revision is immutable. Load on demand: |
| 820 | + |
| 821 | + $handler = $this->getContentHandler(); |
| 822 | + $type = $this->getContentType(); |
| 823 | + |
| 824 | + if( is_null( $this->mText ) ) { |
| 825 | + // Load text on demand: |
| 826 | + $this->mText = $this->loadText(); |
| 827 | + } |
| 828 | + |
| 829 | + $this->mContent = $handler->unserialize( $this->mText, $type ); |
| 830 | + } |
| 831 | + |
| 832 | + return $this->mContent; |
| 833 | + } |
| 834 | + |
| 835 | + public function getContentModelName() { |
| 836 | + if ( !$this->mContentModelName ) { |
| 837 | + $title = $this->getTitle(); #XXX: never null? |
| 838 | + $this->mContentModelName = $title->getContentModelName(); |
| 839 | + } |
| 840 | + |
| 841 | + return $this->mContentModelName; |
| 842 | + } |
| 843 | + |
| 844 | + public function getContentType() { |
| 845 | + if ( !$this->mContentType ) { |
| 846 | + $handler = $this->getContentHandler(); |
| 847 | + $this->mContentType = $handler->getDefaultFormat(); |
| 848 | + } |
| 849 | + |
| 850 | + return $this->mContentType; |
| 851 | + } |
| 852 | + |
| 853 | + public function getContentHandler() { |
| 854 | + if ( !$this->mContentHandler ) { |
| 855 | + $m = $this->getModelName(); |
| 856 | + $this->mContentHandler = ContentHandler::getForModelName( $m ); |
| 857 | + } |
| 858 | + |
| 859 | + return $this->mContentHandler; |
| 860 | + } |
| 861 | + |
762 | 862 | /** |
763 | 863 | * @return String |
764 | 864 | */ |
— | — | @@ -996,7 +1096,9 @@ |
997 | 1097 | : $this->mParentId, |
998 | 1098 | 'rev_sha1' => is_null( $this->mSha1 ) |
999 | 1099 | ? Revision::base36Sha1( $this->mText ) |
1000 | | - : $this->mSha1 |
| 1100 | + : $this->mSha1, |
| 1101 | + 'rev_content_model' => $this->getContentModelName(), |
| 1102 | + 'rev_content_type' => $this->getContentType(), |
1001 | 1103 | ), __METHOD__ |
1002 | 1104 | ); |
1003 | 1105 | |
— | — | @@ -1096,7 +1198,8 @@ |
1097 | 1199 | |
1098 | 1200 | $current = $dbw->selectRow( |
1099 | 1201 | array( 'page', 'revision' ), |
1100 | | - array( 'page_latest', 'rev_text_id', 'rev_len', 'rev_sha1' ), |
| 1202 | + array( 'page_latest', 'rev_text_id', 'rev_len', 'rev_sha1', |
| 1203 | + 'rev_content_model', 'rev_content_type' ), |
1101 | 1204 | array( |
1102 | 1205 | 'page_id' => $pageId, |
1103 | 1206 | 'page_latest=rev_id', |
— | — | @@ -1111,7 +1214,9 @@ |
1112 | 1215 | 'text_id' => $current->rev_text_id, |
1113 | 1216 | 'parent_id' => $current->page_latest, |
1114 | 1217 | 'len' => $current->rev_len, |
1115 | | - 'sha1' => $current->rev_sha1 |
| 1218 | + 'sha1' => $current->rev_sha1, |
| 1219 | + 'content_model' => $current->rev_content_model, |
| 1220 | + 'content_type' => $current->rev_content_type |
1116 | 1221 | ) ); |
1117 | 1222 | } else { |
1118 | 1223 | $revision = null; |
Index: branches/Wikidata/phase3/includes/Title.php |
— | — | @@ -272,12 +272,17 @@ |
273 | 273 | if ( isset( $row->page_is_redirect ) ) |
274 | 274 | $this->mRedirect = (bool)$row->page_is_redirect; |
275 | 275 | if ( isset( $row->page_latest ) ) |
276 | | - $this->mLatestID = (int)$row->page_latest; |
| 276 | + $this->mLatestID = (int)$row->page_latest; # FIXME: whene3ver page_latest is updated, also update page_content_model |
| 277 | + if ( isset( $row->page_content_model ) ) |
| 278 | + $this->mContentModelName = $row->page_content_model; |
| 279 | + else |
| 280 | + $this->mContentModelName = null; # initialized lazily in getContentModelName() |
277 | 281 | } else { // page not found |
278 | 282 | $this->mArticleID = 0; |
279 | 283 | $this->mLength = 0; |
280 | 284 | $this->mRedirect = false; |
281 | 285 | $this->mLatestID = 0; |
| 286 | + $this->mContentModelName = null; # initialized lazily in getContentModelName() |
282 | 287 | } |
283 | 288 | } |
284 | 289 | |
— | — | @@ -303,6 +308,7 @@ |
304 | 309 | $t->mArticleID = ( $ns >= 0 ) ? -1 : 0; |
305 | 310 | $t->mUrlform = wfUrlencode( $t->mDbkeyform ); |
306 | 311 | $t->mTextform = str_replace( '_', ' ', $title ); |
| 312 | + $t->mContentModelName = null; # initialized lazily in getContentModelName() |
307 | 313 | return $t; |
308 | 314 | } |
309 | 315 | |
— | — | @@ -688,6 +694,29 @@ |
689 | 695 | return $this->mNamespace; |
690 | 696 | } |
691 | 697 | |
| 698 | + /** |
| 699 | + * Get the page's content model name |
| 700 | + * |
| 701 | + * @return Integer: Namespace index |
| 702 | + */ |
| 703 | + public function getContentModelName() { |
| 704 | + if ( empty( $this->mContentModelName ) ) { |
| 705 | + $this->mContentModelName = ContentHandler::getDefaultModelFor( $this ); |
| 706 | + } |
| 707 | + |
| 708 | + return $this->mContentModelName; |
| 709 | + } |
| 710 | + |
| 711 | + /** |
| 712 | + * Conveniance method for checking a title's content model name |
| 713 | + * |
| 714 | + * @param $name |
| 715 | + * @return true if $this->getContentModelName() == $name |
| 716 | + */ |
| 717 | + public function hasContentModel( $name ) { |
| 718 | + return $this->getContentModelName() == $name; |
| 719 | + } |
| 720 | + |
692 | 721 | /** |
693 | 722 | * Get the namespace text |
694 | 723 | * |
— | — | @@ -937,9 +966,7 @@ |
938 | 967 | * @return Bool |
939 | 968 | */ |
940 | 969 | public function isWikitextPage() { |
941 | | - $retval = !$this->isCssOrJsPage() && !$this->isCssJsSubpage(); |
942 | | - wfRunHooks( 'TitleIsWikitextPage', array( $this, &$retval ) ); |
943 | | - return $retval; |
| 970 | + return $this->hasContentModel( CONTENT_MODEL_WIKITEXT ); |
944 | 971 | } |
945 | 972 | |
946 | 973 | /** |
— | — | @@ -949,10 +976,8 @@ |
950 | 977 | * @return Bool |
951 | 978 | */ |
952 | 979 | public function isCssOrJsPage() { |
953 | | - $retval = $this->mNamespace == NS_MEDIAWIKI |
954 | | - && preg_match( '!\.(?:css|js)$!u', $this->mTextform ) > 0; |
955 | | - wfRunHooks( 'TitleIsCssOrJsPage', array( $this, &$retval ) ); |
956 | | - return $retval; |
| 980 | + return $this->hasContentModel( CONTENT_MODEL_CSS ) |
| 981 | + || $this->hasContentModel( CONTENT_MODEL_JAVASCRIPT ); |
957 | 982 | } |
958 | 983 | |
959 | 984 | /** |
— | — | @@ -960,7 +985,8 @@ |
961 | 986 | * @return Bool |
962 | 987 | */ |
963 | 988 | public function isCssJsSubpage() { |
964 | | - return ( NS_USER == $this->mNamespace and preg_match( "/\\/.*\\.(?:css|js)$/", $this->mTextform ) ); |
| 989 | + return ( NS_USER == $this->mNamespace && $this->isSubpage() |
| 990 | + && $this->isCssOrJsPage() ); |
965 | 991 | } |
966 | 992 | |
967 | 993 | /** |
— | — | @@ -983,7 +1009,8 @@ |
984 | 1010 | * @return Bool |
985 | 1011 | */ |
986 | 1012 | public function isCssSubpage() { |
987 | | - return ( NS_USER == $this->mNamespace && preg_match( "/\\/.*\\.css$/", $this->mTextform ) ); |
| 1013 | + return ( NS_USER == $this->mNamespace && $this->isSubpage() |
| 1014 | + && $this->hasContentModel( CONTENT_MODEL_CSS ) ); |
988 | 1015 | } |
989 | 1016 | |
990 | 1017 | /** |
— | — | @@ -992,7 +1019,8 @@ |
993 | 1020 | * @return Bool |
994 | 1021 | */ |
995 | 1022 | public function isJsSubpage() { |
996 | | - return ( NS_USER == $this->mNamespace && preg_match( "/\\/.*\\.js$/", $this->mTextform ) ); |
| 1023 | + return ( NS_USER == $this->mNamespace && $this->isSubpage() |
| 1024 | + && $this->hasContentModel( CONTENT_MODEL_JAVASCRIPT ) ); |
997 | 1025 | } |
998 | 1026 | |
999 | 1027 | /** |
Index: branches/Wikidata/phase3/includes/WikiPage.php |
— | — | @@ -800,7 +800,7 @@ |
801 | 801 | && $parserOptions->getStubThreshold() == 0 |
802 | 802 | && $this->mTitle->exists() |
803 | 803 | && ( $oldid === null || $oldid === 0 || $oldid === $this->getLatest() ) |
804 | | - && $this->mTitle->isWikitextPage(); |
| 804 | + && $this->mTitle->isWikitextPage(); #FIXME: ask ContentHandler if cachable! |
805 | 805 | } |
806 | 806 | |
807 | 807 | /** |
Index: branches/Wikidata/phase3/includes/DefaultSettings.php |
— | — | @@ -637,6 +637,17 @@ |
638 | 638 | ); |
639 | 639 | |
640 | 640 | /** |
| 641 | + * Plugins for page content model handling. |
| 642 | + * Each entry in the array maps a model name type to a class name |
| 643 | + */ |
| 644 | +$wgContentHandlers = array( |
| 645 | + CONTENT_MODEL_WIKITEXT => 'WikitextContentHandler', // the usual case |
| 646 | + CONTENT_MODEL_JAVASCRIPT => 'JavaScriptContentHandler', // dumb version, no syntax highlighting |
| 647 | + CONTENT_MODEL_CSS => 'CssContentHandler', // dumb version, no syntax highlighting |
| 648 | + CONTENT_MODEL_TEXT => 'TextContentHandler', // dumb plain text in <pre> |
| 649 | +); |
| 650 | + |
| 651 | +/** |
641 | 652 | * Resizing can be done using PHP's internal image libraries or using |
642 | 653 | * ImageMagick or another third-party converter, e.g. GraphicMagick. |
643 | 654 | * These support more file formats than PHP, which only supports PNG, |