adapted WikiPage and Article (work in progress)
authorDaniel Kinzler <daniel.kinzler@wikimedia.de>
Mon, 5 Mar 2012 15:53:25 +0000 (15:53 +0000)
committerDaniel Kinzler <daniel.kinzler@wikimedia.de>
Wed, 4 Apr 2012 17:44:44 +0000 (19:44 +0200)
includes/Article.php
includes/Content.php
includes/ContentHandler.php
includes/Revision.php
includes/WikiPage.php
includes/specials/SpecialUndelete.php

index 06c460d..895619f 100644 (file)
@@ -38,6 +38,7 @@ class Article extends Page {
        public $mParserOptions;
 
        var $mContent;                    // !<  #FIXME: use Content object!
+    var $mContentObject;              // !<
        var $mContentLoaded = false;      // !<
        var $mOldId;                      // !<
 
@@ -189,8 +190,25 @@ class Article extends Page {
         * only want the real revision text if any.
         *
         * @return Return the text of this revision
-        */
-       public function getContent() {
+     * @deprecated in 1.20; use getContentObject() instead
+        */
+       public function getContent() { #FIXME: deprecated! replace usage! use content object!
+        wfDeprecated( __METHOD__, '1.20' );
+        $content = $this->getContentObject();
+        return ContentHandler::getContentText( $content );
+    }
+
+    /**
+     * Note that getContent/loadContent do not follow redirects anymore.
+     * If you need to fetch redirectable content easily, try
+     * the shortcut in WikiPage::getRedirectTarget()
+     *
+     * This function has side effects! Do not use this function if you
+     * only want the real revision text if any.
+     *
+     * @return Return the content of this revision
+     */
+   public function getContentObject() {
                global $wgUser;
 
                wfProfileIn( __METHOD__ );
@@ -208,12 +226,12 @@ class Article extends Page {
                        }
                        wfProfileOut( __METHOD__ );
 
-                       return $text;
+                       return new WikitextContent( $text, $this->getTitle() );
                } else {
-                       $this->fetchContent();
+                       $this->fetchContentObject();
                        wfProfileOut( __METHOD__ );
 
-                       return $this->mContent;
+                       return $this->mContentObject;
                }
        }
 
@@ -292,61 +310,89 @@ class Article extends Page {
         * Does *NOT* follow redirects.
         *
         * @return mixed string containing article contents, or false if null
+     * @deprecated in 1.20, use getContentObject() instead
         */
-       function fetchContent() {
-               if ( $this->mContentLoaded ) {
+       function fetchContent() { #BC cruft! #FIXME: deprecated, replace usage
+        wfDeprecated( __METHOD__, '1.20' );
+
+               if ( $this->mContentLoaded && $this->mContent ) {
                        return $this->mContent;
                }
 
                wfProfileIn( __METHOD__ );
 
-               $this->mContentLoaded = true;
-
-               $oldid = $this->getOldID();
-
-               # Pre-fill content with error message so that if something
-               # fails we'll have something telling us what we intended.
-               $t = $this->getTitle()->getPrefixedText();
-               $d = $oldid ? wfMsgExt( 'missingarticle-rev', array( 'escape' ), $oldid ) : '';
-               $this->mContent = wfMsgNoTrans( 'missing-article', $t, $d ) ;
-
-               if ( $oldid ) {
-                       # $this->mRevision might already be fetched by getOldIDFromRequest()
-                       if ( !$this->mRevision ) {
-                               $this->mRevision = Revision::newFromId( $oldid );
-                               if ( !$this->mRevision ) {
-                                       wfDebug( __METHOD__ . " failed to retrieve specified revision, id $oldid\n" );
-                                       wfProfileOut( __METHOD__ );
-                                       return false;
-                               }
-                       }
-               } else {
-                       if ( !$this->mPage->getLatest() ) {
-                               wfDebug( __METHOD__ . " failed to find page data for title " . $this->getTitle()->getPrefixedText() . "\n" );
-                               wfProfileOut( __METHOD__ );
-                               return false;
-                       }
-
-                       $this->mRevision = $this->mPage->getRevision();
-                       if ( !$this->mRevision ) {
-                               wfDebug( __METHOD__ . " failed to retrieve current page, rev_id " . $this->mPage->getLatest() . "\n" );
-                               wfProfileOut( __METHOD__ );
-                               return false;
-                       }
-               }
-
-               // @todo FIXME: Horrible, horrible! This content-loading interface just plain sucks.
-               // We should instead work with the Revision object when we need it...
-               $this->mContent = $this->mRevision->getText( Revision::FOR_THIS_USER ); // Loads if user is allowed
-               $this->mRevIdFetched = $this->mRevision->getId();
+        $content = $this->fetchContentObject();
 
-               wfRunHooks( 'ArticleAfterFetchContent', array( &$this, &$this->mContent ) );
+        $this->mContent = ContentHandler::getContentText( $content );
+               wfRunHooks( 'ArticleAfterFetchContent', array( &$this, &$this->mContent ) ); #BC cruft!
 
                wfProfileOut( __METHOD__ );
 
                return $this->mContent;
        }
 
+
+    /**
+     * Get text content object
+     * Does *NOT* follow redirects.
+     *
+     * @return Content object containing article contents, or null
+     */
+    function fetchContentObject() {
+        if ( $this->mContentLoaded ) {
+            return $this->mContentObject;
+        }
+
+        wfProfileIn( __METHOD__ );
+
+        $this->mContentLoaded = true;
+        $this->mContent = null;
+
+        $oldid = $this->getOldID();
+
+        # Pre-fill content with error message so that if something
+        # fails we'll have something telling us what we intended.
+        $t = $this->getTitle()->getPrefixedText();
+        $d = $oldid ? wfMsgExt( 'missingarticle-rev', array( 'escape' ), $oldid ) : '';
+        $this->mContentObject = new MessageContent( 'missing-article', array($t, $d), array() ) ;
+
+        if ( $oldid ) {
+            # $this->mRevision might already be fetched by getOldIDFromRequest()
+            if ( !$this->mRevision ) {
+                $this->mRevision = Revision::newFromId( $oldid );
+                if ( !$this->mRevision ) {
+                    wfDebug( __METHOD__ . " failed to retrieve specified revision, id $oldid\n" );
+                    wfProfileOut( __METHOD__ );
+                    return false;
+                }
+            }
+        } else {
+            if ( !$this->mPage->getLatest() ) {
+                wfDebug( __METHOD__ . " failed to find page data for title " . $this->getTitle()->getPrefixedText() . "\n" );
+                wfProfileOut( __METHOD__ );
+                return false;
+            }
+
+            $this->mRevision = $this->mPage->getRevision();
+            if ( !$this->mRevision ) {
+                wfDebug( __METHOD__ . " failed to retrieve current page, rev_id " . $this->mPage->getLatest() . "\n" );
+                wfProfileOut( __METHOD__ );
+                return false;
+            }
+        }
+
+        // @todo FIXME: Horrible, horrible! This content-loading interface just plain sucks.
+        // We should instead work with the Revision object when we need it...
+        $this->mContentObject = $this->mRevision->getContent( Revision::FOR_THIS_USER ); // Loads if user is allowed
+        $this->mRevIdFetched = $this->mRevision->getId();
+
+        wfRunHooks( 'ArticleAfterFetchContentObject', array( &$this, &$this->mContentObject ) ); #FIXME: register new hook
+
+        wfProfileOut( __METHOD__ );
+
+        return $this->mContentObject;
+    }
+
        /**
         * No-op
         * @deprecated since 1.18
index b8e138e..1a445c6 100644 (file)
@@ -68,6 +68,8 @@ class TextContent extends Content {
         $html = $this->getHtml( $options );
         $po = new ParserOutput( $html );
 
+        if ( $this->mTitle ) $po->setTitleText( $this->mTitle->getText() );
+
         #TODO: cache settings, etc?
 
         return $po;
@@ -84,8 +86,6 @@ class TextContent extends Content {
 
 
     public function getRawData( ) {
-        global $wgParser, $wgUser;
-
         $text = $this->mText;
         return $text;
     }
@@ -128,6 +128,35 @@ class WikitextContent extends TextContent {
 
 }
 
+class MessageContent extends TextContent {
+    public function __construct( $msg_key, $params = null, $options = null ) {
+        parent::__construct(null, null, null, CONTENT_MODEL_WIKITEXT);
+
+        $this->mMessageKey = $msg_key;
+
+        $this->mParameters = $params;
+
+        if ( !$options ) $options = array();
+        $this->mOptions = $options;
+
+        $this->mHtmlOptions = null;
+    }
+
+
+    public function getHtml( ParserOptions $options ) {
+        $opt = array_merge( $this->mOptions, array('parse') );
+        return wfMsgExt( $this->mMessageKey, $this->mParameters, $opt );
+    }
+
+
+    public function getRawData( ) {
+        $opt = array_diff( $this->mOptions, array('parse', 'parseinline') );
+
+        return wfMsgExt( $this->mMessageKey, $this->mParameters, $opt );
+    }
+
+}
+
 
 class JavaScriptContent extends TextContent {
     public function __construct( $text, Title $title, $revId = null ) {
@@ -160,6 +189,9 @@ class CssContent extends TextContent {
     }
 }
 
+#FIXME: special type for redirects?!
+#FIXME: special type for message-based pseudo-content? with raw html?
+
 #TODO: MultipartMultipart < WikipageContent (Main + Links + X)
 #TODO: LinksContent < LanguageLinksContent, CategoriesContent
 #EXAMPLE: CoordinatesContent
index b7761d6..025c740 100644 (file)
  */
 abstract class ContentHandler {
 
+    public static function getContentText( Content $content ) {
+        if ( !$content ) return '';
+
+        if ( $content instanceof TextContent ) {
+            #XXX: or check by model name?
+            #XXX: or define $content->allowRawData()?
+            return $content->getRawData();
+        }
+
+        return null;
+    }
+
     public static function getDefaultModelFor( Title $title ) {
         global $wgNamespaceContentModels;
 
index 4e2a091..9bc9469 100644 (file)
@@ -21,7 +21,9 @@ class Revision {
        protected $mTitle;
        protected $mCurrent;
     protected $mContentModelName;
-    protected $mContentType;
+    protected $mContentFormat;
+    protected $mContent;
+    protected $mContentHandler;
 
        const DELETED_TEXT = 1;
        const DELETED_COMMENT = 2;
@@ -128,7 +130,7 @@ class Revision {
                        'len'        => $row->ar_len,
                        'sha1'       => isset( $row->ar_sha1 ) ? $row->ar_sha1 : null,
             'content_model' => isset( $row->ar_content_model ) ? $row->ar_content_model : null,
-            'content_type'  => isset( $row->ar_content_type ) ? $row->ar_content_type : null,
+            'content_format'  => isset( $row->ar_content_format ) ? $row->ar_content_format : null,
                );
                if ( isset( $row->ar_text ) && !$row->ar_text_id ) {
                        // Pre-1.5 ar_text row
@@ -339,7 +341,9 @@ class Revision {
                        'rev_deleted',
                        'rev_len',
                        'rev_parent_id',
-                       'rev_sha1'
+                       'rev_sha1',
+                       'rev_content_format',
+                       'rev_content_model'
                );
        }
 
@@ -422,10 +426,10 @@ class Revision {
                 $this->mContentModelName = strval( $row->rev_content_model );
             }
 
-            if( !isset( $row->rev_content_type ) || is_null( $row->rev_content_type ) ) {
-                $this->mContentType = null; # determine on demand if needed
+            if( !isset( $row->rev_content_format ) || is_null( $row->rev_content_format ) ) {
+                $this->mContentFormat = null; # determine on demand if needed
             } else {
-                $this->mContentType = strval( $row->rev_content_type );
+                $this->mContentFormat = strval( $row->rev_content_format );
             }
 
                        // Lazy extraction...
@@ -449,6 +453,19 @@ class Revision {
                        // Build a new revision to be saved...
                        global $wgUser; // ugh
 
+
+            # if we have a content object, use it to set the model and type
+            if ( !empty( $row['content'] ) ) {
+                if ( !empty( $row['text_id'] ) ) { #FIXME: when is that set? test with external store setup! check out insertOn()
+                    throw new MWException( "Text already stored in external store (id {$row['text_id']}), can't serialize content object" );
+                }
+
+                $row['content_model'] = $row['content']->getModelName();
+                # note: mContentFormat is initializes later accordingly
+                # note: content is serialized later in this method!
+                # also set text to null?
+            }
+
                        $this->mId        = isset( $row['id']         ) ? intval( $row['id']         ) : null;
                        $this->mPage      = isset( $row['page']       ) ? intval( $row['page']       ) : null;
                        $this->mTextId    = isset( $row['text_id']    ) ? intval( $row['text_id']    ) : null;
@@ -462,12 +479,12 @@ class Revision {
                        $this->mSha1      = isset( $row['sha1']  )      ? strval( $row['sha1']  )      : null;
 
             $this->mContentModelName = isset( $row['content_model']  )  ? strval( $row['content_model'] ) : null;
-            $this->mContentType      = isset( $row['content_type']  )   ? strval( $row['content_type'] ) : null;
+            $this->mContentFormat      = isset( $row['content_format']  )   ? strval( $row['content_format'] ) : null;
 
-            if( !isset( $row->rev_content_type ) || is_null( $row->rev_content_type ) ) {
-                $this->mContentType = null; # determine on demand if needed
+            if( !isset( $row->rev_content_format ) || is_null( $row->rev_content_format ) ) {
+                $this->mContentFormat = null; # determine on demand if needed
             } else {
-                $this->mContentType = $row->rev_content_type;
+                $this->mContentFormat = $row->rev_content_format;
             }
 
                        // Enforce spacing trimming on supplied text
@@ -487,11 +504,20 @@ class Revision {
                        }
 
             $this->getContentModelName(); # force lazy init
-            $this->getContentType();      # force lazy init
+            $this->getContentFormat();      # force lazy init
+
+            # if we have a content object, serialize it, overriding mText
+            if ( !empty( $row['content'] ) ) {
+                $handler = $this->getContentHandler();
+                $this->mText = $handler->serialize( $row['content'], $this->getContentFormat() );
+            }
                } else {
                        throw new MWException( 'Revision constructor passed invalid row format.' );
                }
                $this->mUnpatrolled = null;
+
+        #FIXME: add patch for ar_content_format, ar_content_model, rev_content_format, rev_content_model to installer
+        #FIXME: add support for ar_content_format, ar_content_model, rev_content_format, rev_content_model to API
        }
 
        /**
@@ -755,19 +781,9 @@ class Revision {
         */
        public function getText( $audience = self::FOR_PUBLIC, User $user = null ) { #FIXME: deprecated, replace usage!
         wfDeprecated( __METHOD__, '1.20' );
-        $content = $this->getContent();
-
-        if ( $content == null ) {
-            return ""; # not allowed
-        }
 
-        if ( $content instanceof TextContent ) {
-            #FIXME: or check by model name? or define $content->allowRawData()?
-            return $content->getRawData();
-        }
-
-        #TODO: log this failure!
-        return null;
+        $content = $this->getContent();
+        return ContentHandler::getContentText( $content ); # returns the raw content text, if applicable
        }
 
     /**
@@ -818,14 +834,14 @@ class Revision {
             // Revision is immutable. Load on demand:
 
             $handler = $this->getContentHandler();
-            $type = $this->getContentType();
+            $format = $this->getContentFormat();
 
             if( is_null( $this->mText ) ) {
                 // Load text on demand:
                 $this->mText = $this->loadText();
             }
 
-            $this->mContent = $handler->unserialize( $this->mText, $type );
+            $this->mContent = $handler->unserialize( $this->mText, $format );
         }
 
         return $this->mContent;
@@ -840,19 +856,22 @@ class Revision {
         return $this->mContentModelName;
     }
 
-    public function getContentType() {
-        if ( !$this->mContentType ) {
+    public function getContentFormat() {
+        if ( !$this->mContentFormat ) {
             $handler = $this->getContentHandler();
-            $this->mContentType = $handler->getDefaultFormat();
+            $this->mContentFormat = $handler->getDefaultFormat();
         }
 
-        return $this->mContentType;
+        return $this->mContentFormat;
     }
 
     public function getContentHandler() {
         if ( !$this->mContentHandler ) {
             $m = $this->getModelName();
             $this->mContentHandler = ContentHandler::getForModelName( $m );
+
+            #XXX: do we need to verify that mContentHandler supports mContentFormat?
+            #     otherwise, a fixed content format may cause problems on insert.
         }
 
         return $this->mContentHandler;
@@ -1098,7 +1117,7 @@ class Revision {
                                        ? Revision::base36Sha1( $this->mText )
                                        : $this->mSha1,
                 'rev_content_model'       => $this->getContentModelName(),
-                'rev_content_type'        => $this->getContentType(),
+                'rev_content_format'        => $this->getContentFormat(),
                        ), __METHOD__
                );
 
@@ -1199,7 +1218,7 @@ class Revision {
                $current = $dbw->selectRow(
                        array( 'page', 'revision' ),
                        array( 'page_latest', 'rev_text_id', 'rev_len', 'rev_sha1',
-                    'rev_content_model', 'rev_content_type' ),
+                    'rev_content_model', 'rev_content_format' ),
                        array(
                                'page_id' => $pageId,
                                'page_latest=rev_id',
@@ -1216,7 +1235,7 @@ class Revision {
                                'len'        => $current->rev_len,
                                'sha1'       => $current->rev_sha1,
                                'content_model'  => $current->rev_content_model,
-                               'content_type'   => $current->rev_content_type
+                               'content_format'   => $current->rev_content_format
                                ) );
                } else {
                        $revision = null;
index a0709b8..e68a697 100644 (file)
@@ -161,6 +161,7 @@ class WikiPage extends Page {
                        'page_touched',
                        'page_latest',
                        'page_len',
+            'page_content_model',
                );
        }
 
@@ -383,6 +384,23 @@ class WikiPage extends Page {
                return null;
        }
 
+    /**
+     * Get the content of the current revision. No side-effects...
+     *
+     * @param $audience Integer: one of:
+     *      Revision::FOR_PUBLIC       to be displayed to all users
+     *      Revision::FOR_THIS_USER    to be displayed to $wgUser
+     *      Revision::RAW              get the text regardless of permissions
+     * @return String|null The content of the current revision
+     */
+    public function getContent( $audience = Revision::FOR_PUBLIC ) {
+        $this->loadLastEdit();
+        if ( $this->mLastRevision ) {
+            return $this->mLastRevision->getContent( $audience );
+        }
+        return false;
+    }
+
        /**
         * Get the text of the current revision. No side-effects...
         *
@@ -391,8 +409,10 @@ class WikiPage extends Page {
         *      Revision::FOR_THIS_USER    to be displayed to $wgUser
         *      Revision::RAW              get the text regardless of permissions
         * @return String|false The text of the current revision
+     * @deprecated as of 1.20, getContent() should be used instead.
         */
-       public function getText( $audience = Revision::FOR_PUBLIC ) {
+       public function getText( $audience = Revision::FOR_PUBLIC ) { #FIXME: deprecated, replace usage!
+        wfDeprecated( __METHOD__, '1.20' );
                $this->loadLastEdit();
                if ( $this->mLastRevision ) {
                        return $this->mLastRevision->getText( $audience );
@@ -405,12 +425,8 @@ class WikiPage extends Page {
         *
         * @return String|false The text of the current revision
         */
-       public function getRawText() {
-               $this->loadLastEdit();
-               if ( $this->mLastRevision ) {
-                       return $this->mLastRevision->getRawText();
-               }
-               return false;
+       public function getRawText() { #FIXME: deprecated, replace usage!
+               return $this->getText( Revision::RAW );
        }
 
        /**
@@ -1293,7 +1309,7 @@ class WikiPage extends Page {
                                'page'       => $this->getId(),
                                'comment'    => $summary,
                                'minor_edit' => $isminor,
-                               'text'       => $text,
+                               'text'       => $text, #FIXME: set content instead, leavfe serialization to revision?!
                                'parent_id'  => $oldid,
                                'user'       => $user->getId(),
                                'user_text'  => $user->getName(),
@@ -1963,7 +1979,9 @@ class WikiPage extends Page {
                                'ar_len'        => 'rev_len',
                                'ar_page_id'    => 'page_id',
                                'ar_deleted'    => $bitfield,
-                               'ar_sha1'       => 'rev_sha1'
+                               'ar_sha1'       => 'rev_content_model',
+                               'ar_content_format'       => 'rev_content_format',
+                               'ar_content_format'       => 'rev_sha1'
                        ), array(
                                'page_id' => $id,
                                'page_id = rev_page'
index 9ed20f3..1cf63dd 100644 (file)
@@ -116,7 +116,8 @@ class PageArchive {
                $res = $dbr->select( 'archive',
                        array(
                                'ar_minor_edit', 'ar_timestamp', 'ar_user', 'ar_user_text',
-                               'ar_comment', 'ar_len', 'ar_deleted', 'ar_rev_id', 'ar_sha1'
+                               'ar_comment', 'ar_len', 'ar_deleted', 'ar_rev_id', 'ar_sha1',
+                'ar_content_format', 'ar_content_model'
                        ),
                        array( 'ar_namespace' => $this->title->getNamespace(),
                                   'ar_title' => $this->title->getDBkey() ),
@@ -189,6 +190,8 @@ class PageArchive {
                                'ar_deleted',
                                'ar_len',
                                'ar_sha1',
+                'ar_content_format',
+                'ar_content_model',
                        ),
                        array( 'ar_namespace' => $this->title->getNamespace(),
                                        'ar_title' => $this->title->getDBkey(),
@@ -463,7 +466,9 @@ class PageArchive {
                                'ar_deleted',
                                'ar_page_id',
                                'ar_len',
-                               'ar_sha1' ),
+                               'ar_sha1',
+                'ar_content_format',
+                'ar_content_model' ),
                        /* WHERE */ array(
                                'ar_namespace' => $this->title->getNamespace(),
                                'ar_title'     => $this->title->getDBkey(),