Merge branch 'Wikidata' of ssh://review/mediawiki/core into Wikidata
authorjeroendedauw <jeroendedauw@gmail.com>
Wed, 27 Jun 2012 14:18:18 +0000 (16:18 +0200)
committerjeroendedauw <jeroendedauw@gmail.com>
Wed, 27 Jun 2012 14:18:18 +0000 (16:18 +0200)
26 files changed:
includes/Content.php
includes/ContentHandler.php
includes/DefaultSettings.php
includes/Defines.php
includes/Export.php
includes/Import.php
includes/Revision.php
includes/SqlDataUpdate.php
includes/Title.php
includes/WikiPage.php
includes/api/ApiEditPage.php
includes/api/ApiQueryRevisions.php
includes/cache/LinkCache.php
languages/messages/MessagesEn.php
languages/messages/MessagesQqq.php
maintenance/archives/patch-archive-ar_content_format.sql
maintenance/archives/patch-archive-ar_content_model.sql
maintenance/archives/patch-page-page_content_model.sql
maintenance/archives/patch-revision-rev_content_format.sql
maintenance/archives/patch-revision-rev_content_model.sql
maintenance/storage/drop_content_model_info.sql [new file with mode: 0644]
tests/phpunit/includes/ContentHandlerTest.php
tests/phpunit/includes/RevisionTest.php
tests/phpunit/includes/api/ApiEditPageTest.php [new file with mode: 0644]
tests/phpunit/maintenance/DumpTestCase.php
tests/phpunit/maintenance/backupTextPassTest.php

index 7d211c1..f7f0346 100644 (file)
@@ -71,7 +71,7 @@ interface Content {
         *
         * @since WD.1
         *
-        * @return int The model id
+        * @return String The model id
         */
        public function getModel();
 
@@ -95,7 +95,7 @@ interface Content {
         *
         * @since WD.1
         *
-        * @return ContentHandler
+        * @return String
         */
        public function getDefaultFormat();
 
@@ -357,6 +357,27 @@ interface Content {
         */
        public function preloadTransform( Title $title, ParserOptions $popts );
 
+       /**
+        * Prepare Content for saving. Called before Content is saved by WikiPage::doEditContent().
+        * This may be used to store additional information in the database, or check the content's
+        * consistency with global state.
+        *
+        * Note that this method will be called inside the same transaction bracket that will be used
+        * to save the new revision.
+        *
+        * @param WikiPage $page The page to be saved.
+        * @param int      $flags bitfield for use with EDIT_XXX constants, see WikiPage::doEditContent()
+        * @param int      $baseRevId the ID of the current revision
+        * @param User     $user
+        *
+        * @return Status A status object indicating whether the content was successfully prepared for saving.
+        *                If the returned status indicates an error, a rollback will be performed and the
+        *                transaction aborted.
+        *
+        * @see see WikiPage::doEditContent()
+        */
+       public function prepareSave( WikiPage $page, $flags, $baseRevId, User $user );
+
        # TODO: handle ImagePage and CategoryPage
        # TODO: make sure we cover lucene search / wikisearch.
        # TODO: make sure ReplaceTemplates still works
@@ -375,9 +396,6 @@ interface Content {
        # FUTURE: special type for redirects?!
        # FUTURE: MultipartMultipart < WikipageContent (Main + Links + X)
        # FUTURE: LinksContent < LanguageLinksContent, CategoriesContent
-
-       // @TODO: add support for ar_content_format, ar_content_model, 
-       // rev_content_format, rev_content_model to API
 }
 
 
@@ -398,7 +416,7 @@ abstract class AbstractContent implements Content {
        protected $model_id;
 
        /**
-        * @param $model_id int
+        * @param String $model_id
         */
        public function __construct( $model_id = null ) {
                $this->model_id = $model_id;
@@ -421,12 +439,9 @@ abstract class AbstractContent implements Content {
         */
        protected function checkModelID( $model_id ) {
                if ( $model_id !== $this->model_id ) {
-                       $model_name = ContentHandler::getContentModelName( $model_id );
-                       $own_model_name = ContentHandler::getContentModelName( $this->model_id );
-
-                       throw new MWException( "Bad content model: " . 
-                               "expected {$this->model_id} ($own_model_name) " . 
-                               "but got $model_id ($model_name)." );
+                       throw new MWException( "Bad content model: " .
+                               "expected {$this->model_id}  " .
+                               "but got $model_id." );
                }
        }
 
@@ -617,6 +632,17 @@ abstract class AbstractContent implements Content {
        public function preloadTransform( Title $title, ParserOptions $popts ) {
                return $this;
        }
+
+       /**
+        * @see  Content::prepareSave()
+        */
+       public function prepareSave( WikiPage $page, $flags, $baseRevId, User $user ) {
+               if ( $this->isValid() ) {
+                       return Status::newGood();
+               } else {
+                       return Status::newFatal( "invalid-content-data" );
+               }
+       }
 }
 
 /**
@@ -777,12 +803,9 @@ class WikitextContent extends TextContent {
                $sectionModelId = $with->getModel();
 
                if ( $sectionModelId != $myModelId  ) {
-                       $myModelName = ContentHandler::getContentModelName( $myModelId );
-                       $sectionModelName = ContentHandler::getContentModelName( $sectionModelId );
-
-                       throw new MWException( "Incompatible content model for section: " . 
-                               "document uses $myModelId ($myModelName), " .
-                               "section uses $sectionModelId ($sectionModelName)." );
+                       throw new MWException( "Incompatible content model for section: " .
+                               "document uses $myModelId but " .
+                               "section uses $sectionModelId." );
                }
 
                $oldtext = $this->getNativeData();
index 7bf33c5..c90e140 100644 (file)
@@ -272,7 +272,7 @@ abstract class ContentHandler {
         * @since WD.1
         *
         * @static
-        * @param $modelId int The ID of the content model for which to get a
+        * @param $modelId String The ID of the content model for which to get a
         *    handler. Use CONTENT_MODEL_XXX constants.
         * @return ContentHandler The ContentHandler singleton for handling the
         *    model given by $modelId
@@ -310,70 +310,6 @@ abstract class ContentHandler {
                return ContentHandler::$handlers[$modelId];
        }
 
-       /**
-        * Returns the appropriate MIME type for a given content format,
-        * or null if no MIME type is known for this format.
-        *
-        * MIME types can be registered in the global array $wgContentFormatMimeTypes.
-        *
-        * @static
-        * @param $id int The content format id, as given by a CONTENT_FORMAT_XXX
-        *    constant or returned by Revision::getContentFormat().
-        *
-        * @return string|null The content format's MIME type.
-        */
-       public static function getContentFormatMimeType( $id ) {
-               global $wgContentFormatMimeTypes;
-
-               if ( !isset( $wgContentFormatMimeTypes[ $id ] ) ) {
-                       return null;
-               }
-
-               return $wgContentFormatMimeTypes[ $id ];
-       }
-
-       /**
-        * Returns the content format if for a given MIME type,
-        * or null if no format ID if known for this MIME type.
-        *
-        * Mime types can be registered in the global array $wgContentFormatMimeTypes.
-        *
-        * @static
-        * @param $mime string the MIME type
-        *
-        * @return int|null The format ID, as defined by a CONTENT_FORMAT_XXX constant
-        */
-       public static function getContentFormatID( $mime ) {
-               global $wgContentFormatMimeTypes;
-
-               static $format_ids = null;
-
-               if ( $format_ids === null ) {
-                       $format_ids = array_flip( $wgContentFormatMimeTypes );
-               }
-
-               if ( !isset( $format_ids[ $mime ] ) ) {
-                       return null;
-               }
-
-               return $format_ids[ $mime ];
-       }
-
-       /**
-        * Returns the symbolic name for a given content model.
-        *
-        * @param $id int The content model ID, as given by a CONTENT_MODEL_XXX
-        *    constant or returned by Revision::getContentModel().
-        *
-        * @return string The content model's symbolic name.
-        * @throws MWException if the model id isn't known.
-        */
-       public static function getContentModelName( $id ) {
-               $handler = self::getForModelID( $id );
-               return $handler->getModelName();
-       }
-
-
        /**
         * Returns the localized name for a given content model.
         *
@@ -381,25 +317,36 @@ abstract class ContentHandler {
         * have the form content-model-$name, where $name is getContentModelName( $id ).
         *
         * @static
-        * @param $id int The content model ID, as given by a CONTENT_MODEL_XXX
+        * @param $name String The content model ID, as given by a CONTENT_MODEL_XXX
         *    constant or returned by Revision::getContentModel().
-        * @todo also accept a symbolic name instead of a numeric id
         *
         * @return string The content format's localized name.
         * @throws MWException if the model id isn't known.
         */
-       public static function getLocalizedName( $id ) {
-               $name = self::getContentModelName( $id );
+       public static function getLocalizedName( $name ) {
                $key = "content-model-$name";
 
                if ( wfEmptyMsg( $key ) ) return $name;
                else return wfMsg( $key );
        }
 
+       public static function getAllContentFormats() {
+               global $wgContentHandlers;
+
+               $formats = array();
+
+               foreach ( $wgContentHandlers as $model => $class ) {
+                       $handler = ContentHandler::getForModelID( $model );
+                       $formats = array_merge( $formats, $handler->getSupportedFormats() );
+               }
+
+               $formats = array_unique( $formats );
+               return $formats;
+       }
+
        // ------------------------------------------------------------------------
 
        protected $mModelID;
-       protected $mModelName;
        protected $mSupportedFormats;
 
        /**
@@ -407,7 +354,7 @@ abstract class ContentHandler {
         * and a list of supported formats. Values for the parameters are typically
         * provided as literals by subclass's constructors.
         *
-        * @param $modelId int (use CONTENT_MODEL_XXX constants).
+        * @param $modelId String (use CONTENT_MODEL_XXX constants).
         * @param $formats array List for supported serialization formats
         *    (typically as MIME types)
         */
@@ -427,7 +374,7 @@ abstract class ContentHandler {
         *
         * @abstract
         * @param $content Content The Content object to serialize
-        * @param $format null The desired serialization format
+        * @param $format null|String The desired serialization format
         * @return string Serialized form of the content
         */
        public abstract function serializeContent( Content $content, $format = null );
@@ -439,7 +386,7 @@ abstract class ContentHandler {
         *
         * @abstract
         * @param $blob string serialized form of the content
-        * @param $format null the format used for serialization
+        * @param $format null|String the format used for serialization
         * @return Content the Content object created by deserializing $blob
         */
        public abstract function unserializeContent( $blob, $format = null );
@@ -460,44 +407,27 @@ abstract class ContentHandler {
         *
         * @since WD.1
         *
-        * @return int The model ID
+        * @return String The model ID
         */
        public function getModelID() {
                return $this->mModelID;
        }
 
-       /**
-        * Returns the content model's symbolic name.
-        *
-        * The symbolic name is is this object's class name in lower case with the trailing "ContentHandler"
-        * and and special characters removed.
-        *
-        * @since WD.1
-        *
-        * @return String The content model's name
-        */
-       public function getModelName() {
-               return $this->mModelName;
-       }
-
        /**
         * Throws an MWException if $model_id is not the ID of the content model
         * supported by this ContentHandler.
         *
         * @since WD.1
         *
-        * @param $model_id int The model to check
+        * @param String $model_id The model to check
         *
         * @throws MWException
         */
        protected function checkModelID( $model_id ) {
                if ( $model_id !== $this->mModelID ) {
-                       $model_name = ContentHandler::getContentModelName( $model_id );
-                       $own_model_name = ContentHandler::getContentModelName( $this->mModelID );
-
                        throw new MWException( "Bad content model: " .
-                               "expected {$this->mModelID} ($own_model_name) " .
-                               "but got $model_id ($model_name)." );
+                               "expected {$this->mModelID} " .
+                               "but got $model_id." );
                }
        }
 
@@ -568,24 +498,6 @@ abstract class ContentHandler {
                }
        }
 
-       /**
-        * Returns true if the content is consistent with the database, that is if
-        * saving it to the database would not violate any global constraints.
-        *
-        * Content needs to be valid using this method before it can be saved.
-        *
-        * This default implementation always returns true.
-        *
-        * @since WD.1
-        *
-        * @param $content \Content
-        *
-        * @return boolean
-        */
-       public function isConsistentWithDatabase( Content $content ) {
-               return true;
-       }
-
        /**
         * Returns overrides for action handlers.
         * Classes listed here will be used instead of the default one when
@@ -627,6 +539,29 @@ abstract class ContentHandler {
                return new $diffEngineClass( $context, $old, $new, $rcid, $refreshCache, $unhide );
        }
 
+       /**
+        * Get the language in which the content of the given page is written.
+        *
+        * This default implementation returns $wgContLang->getCode().
+        *
+        * Note that a page's language must be permanent and cacheable, that is, it must not depend
+        * on user preferences, request parameters or session state.
+        *
+        * Also note that the page language may or may not depend on the actual content of the page,
+        * that is, this method may load the content in order to determine the language.
+        *
+        * @since 1.WD
+        *
+        * @param Title        $title the page to determine the language for.
+        * @param Content|null $content the page's content, if you have it handy, to avoid reloading it.
+        *
+        * @return Language the page's language code
+        */
+       public function getPageLanguage( Title $title, Content $content = null ) {
+               global $wgContLang;
+               return $wgContLang;
+       }
+
        /**
         * Returns the name of the diff engine to use.
         *
@@ -1196,6 +1131,17 @@ class JavaScriptContentHandler extends TextContentHandler {
                return new JavaScriptContent( '' );
        }
 
+       /**
+        * Returns the english language, because JS is english, and should be handled as such.
+        *
+        * @return Language wfGetLangObj( 'en' )
+        *
+        * @see ContentHandler::getPageLanguage()
+        */
+       public function getPageLanguage( Title $title, Content $content = null ) {
+               return wfGetLangObj( 'en' );
+       }
+
        protected function getHtml( Content $content ) {
                $html = "";
                $html .= "<pre class=\"mw-code mw-js\" dir=\"ltr\">\n";
@@ -1225,6 +1171,16 @@ class CssContentHandler extends TextContentHandler {
                return new CssContent( '' );
        }
 
+       /**
+        * Returns the english language, because CSS is english, and should be handled as such.
+        *
+        * @return Language wfGetLangObj( 'en' )
+        *
+        * @see ContentHandler::getPageLanguage()
+        */
+       public function getPageLanguage( Title $title, Content $content = null ) {
+               return wfGetLangObj( 'en' );
+       }
 
        protected function getHtml( Content $content ) {
                $html = "";
index 39d799d..7ed1173 100644 (file)
@@ -677,30 +677,6 @@ $wgContentHandlers = array(
        CONTENT_MODEL_WIKITEXT => 'WikitextContentHandler', // the usual case
        CONTENT_MODEL_JAVASCRIPT => 'JavaScriptContentHandler', // dumb version, no syntax highlighting
        CONTENT_MODEL_CSS => 'CssContentHandler', // dumb version, no syntax highlighting
-       CONTENT_MODEL_TEXT => 'TextContentHandler', // dumb plain text in <pre>
-);
-
-/**
- * Mime types for content formats.
- * Each entry in the array maps a content format to a mime type.
- *
- * Extensions that define their own content formats can register
- * the appropriate mime types in this array.
- *
- * Such extensions shall use content format IDs
- * larger than 100 and register the ids they use at
- * <http://mediawiki.org/ContentHandler/registry>
- * to avoid conflicts with other extensions.
- */
-$wgContentFormatMimeTypes = array(
-       CONTENT_FORMAT_WIKITEXT => 'text/x-wiki',
-       CONTENT_FORMAT_JAVASCRIPT => 'text/javascript',
-       CONTENT_FORMAT_CSS => 'text/css',
-       CONTENT_FORMAT_TEXT => 'text/plain',
-       CONTENT_FORMAT_HTML => 'text/html',
-       CONTENT_FORMAT_XML => 'application/xml',
-       CONTENT_FORMAT_JSON => 'application/json',
-       CONTENT_FORMAT_SERIALIZED => 'application/vnd.php.serialized',
 );
 
 /**
index fdbda95..2d85e71 100644 (file)
@@ -271,37 +271,31 @@ define( 'PROTO_INTERNAL', 2 );
 /**@}*/
 
 /**@{
- * Content model ids, used by Content and ContentHandler
+ * Content model ids, used by Content and ContentHandler.
+ * These IDs will be exposed in the API and XML dumps.
  *
- * Extensions that define their own content models shall use IDs
- * larger than 100 and register the ids they use at
- * <http://mediawiki.org/ContentHandler/registry>
- * to avoid conflicts with other extensions.
+ * Extensions that define their own content model IDs should take
+ * care to avoid conflicts. Using the extension name as a prefix is recommended.
  */
-define( 'CONTENT_MODEL_WIKITEXT', 1 );
-define( 'CONTENT_MODEL_JAVASCRIPT', 2 );
-define( 'CONTENT_MODEL_CSS', 3 );
-define( 'CONTENT_MODEL_TEXT', 4 );
+define( 'CONTENT_MODEL_WIKITEXT', 'wikitext' );
+define( 'CONTENT_MODEL_JAVASCRIPT', 'javascript' );
+define( 'CONTENT_MODEL_CSS', 'css' );
+define( 'CONTENT_MODEL_TEXT', 'text' );
 /**@}*/
 
 /**@{
- * Content format ids, used by Content and ContentHandler.
- * Use ContentHander::getFormatMimeType() to get the associated mime type.
- * Register mime types in $wgContentFormatMimeTypes.
+ * Content formats, used by Content and ContentHandler.
+ * These should be MIME types, and will be exposed in the API and XML dumps.
  *
- * Extensions that define their own content formats shall use IDs
- * larger than 100 and register the ids they use at
- * <http://mediawiki.org/ContentHandler/registry>
- * to avoid conflicts with other extensions.
+ * Extensions are free to use the below formats, or define their own.
+ * It is recommended to stick with the conventions for MIME types.
  */
-define( 'CONTENT_FORMAT_WIKITEXT', 1 ); // wikitext
-define( 'CONTENT_FORMAT_JAVASCRIPT', 2 ); // for js pages
-define( 'CONTENT_FORMAT_CSS', 3 );  // for css pages
-define( 'CONTENT_FORMAT_TEXT', 4 ); // for future use, e.g. with some plain-html messages.
-define( 'CONTENT_FORMAT_HTML', 5 ); // for future use, e.g. with some plain-html messages.
-define( 'CONTENT_FORMAT_SERIALIZED', 11 ); // for future use with the api, and for use by extensions
-define( 'CONTENT_FORMAT_JSON', 12 ); // for future use with the api, and for use by extensions
-define( 'CONTENT_FORMAT_XML', 13 ); // for future use with the api, and for use by extensions
+define( 'CONTENT_FORMAT_WIKITEXT', 'text/x-wiki' ); // wikitext
+define( 'CONTENT_FORMAT_JAVASCRIPT', 'text/javascript' ); // for js pages
+define( 'CONTENT_FORMAT_CSS', 'text/css' );  // for css pages
+define( 'CONTENT_FORMAT_TEXT', 'text/plain' ); // for future use, e.g. with some plain-html messages.
+define( 'CONTENT_FORMAT_HTML', 'text/html' ); // for future use, e.g. with some plain-html messages.
+define( 'CONTENT_FORMAT_SERIALIZED', 'application/vnd.php.serialized' ); // for future use with the api, and for use by extensions
+define( 'CONTENT_FORMAT_JSON', 'application/json' ); // for future use with the api, and for use by extensions
+define( 'CONTENT_FORMAT_XML', 'application/xml' ); // for future use with the api, and for use by extensions
 /**@}*/
-
-
index eb240cc..fd0c765 100644 (file)
@@ -670,7 +670,7 @@ class XmlDumpWriter {
                }
 
                if ( isset( $row->rev_content_model ) && !is_null( $row->rev_content_model )  ) {
-                       $content_model = intval( $row->rev_content_model );
+                       $content_model = strval( $row->rev_content_model );
                } else {
                        // probably using $wgContentHandlerUseDB = false;
                        // @todo: test!
@@ -678,11 +678,10 @@ class XmlDumpWriter {
                        $content_model = ContentHandler::getDefaultModelFor( $title );
                }
 
-               $name = ContentHandler::getContentModelName( $content_model );
-               $out .= "      " . Xml::element('model', array( 'name' => $name ), strval( $content_model ) ) . "\n";
+               $out .= "      " . Xml::element('model', null, strval( $content_model ) ) . "\n";
 
                if ( isset( $row->rev_content_format ) && !is_null( $row->rev_content_format ) ) {
-                       $content_format = intval( $row->rev_content_format );
+                       $content_format = strval( $row->rev_content_format );
                } else {
                        // probably using $wgContentHandlerUseDB = false;
                        // @todo: test!
@@ -690,8 +689,7 @@ class XmlDumpWriter {
                        $content_format = $content_handler->getDefaultFormat();
                }
 
-               $mime = ContentHandler::getContentFormatMimeType( $content_format );
-               $out .= "      " . Xml::element('format', array( 'mime' => $mime ), strval( $content_format ) ) . "\n";
+               $out .= "      " . Xml::element('format', null, strval( $content_format ) ) . "\n";
 
                wfRunHooks( 'XmlDumpWriterWriteRevision', array( &$this, &$out, $row, $text ) );
 
index 5b182ad..6996d2a 100644 (file)
@@ -1179,7 +1179,7 @@ class WikiRevision {
        }
 
        /**
-        * @return int
+        * @return String
         */
        function getModel() {
                if ( is_null( $this->model ) ) {
@@ -1190,7 +1190,7 @@ class WikiRevision {
        }
 
        /**
-        * @return int
+        * @return String
         */
        function getFormat() {
                if ( is_null( $this->model ) ) {
index a9cc72e..7f872d7 100644 (file)
@@ -487,13 +487,13 @@ class Revision {
                        if( !isset( $row->rev_content_model ) || is_null( $row->rev_content_model ) ) {
                                $this->mContentModel = null; # determine on demand if needed
                        } else {
-                               $this->mContentModel = intval( $row->rev_content_model );
+                               $this->mContentModel = strval( $row->rev_content_model );
                        }
 
                        if( !isset( $row->rev_content_format ) || is_null( $row->rev_content_format ) ) {
                                $this->mContentFormat = null; # determine on demand if needed
                        } else {
-                               $this->mContentFormat = intval( $row->rev_content_format );
+                               $this->mContentFormat = strval( $row->rev_content_format );
                        }
 
                        // Lazy extraction...
@@ -542,15 +542,17 @@ class Revision {
                        $this->mParentId  = isset( $row['parent_id']  ) ? intval( $row['parent_id']  ) : null;
                        $this->mSha1      = isset( $row['sha1']  )      ? strval( $row['sha1']  )      : null;
 
-                       $this->mContentModel = isset( $row['content_model']  )  ? intval( $row['content_model'] )  : null;
-                       $this->mContentFormat    = isset( $row['content_format']  ) ? intval( $row['content_format'] ) : null;
+                       $this->mContentModel = isset( $row['content_model']  )  ? strval( $row['content_model'] )  : null;
+                       $this->mContentFormat    = isset( $row['content_format']  ) ? strval( $row['content_format'] ) : null;
 
                        // Enforce spacing trimming on supplied text
                        $this->mComment   = isset( $row['comment']    ) ?  trim( strval( $row['comment'] ) ) : null;
                        $this->mText      = isset( $row['text']       ) ? rtrim( strval( $row['text']    ) ) : null;
                        $this->mTextRow   = null;
 
-                       # if we have a content object, override mText and mContentModel
+                       $this->mTitle     = isset( $row['title']      ) ? $row['title'] : null;
+
+                       // if we have a Content object, override mText and mContentModel
                        if ( !empty( $row['content'] ) ) {
                                $handler = $this->getContentHandler();
                                $this->mContent = $row['content'];
@@ -564,10 +566,17 @@ class Revision {
                                $this->mContent = $handler->unserializeContent( $this->mText );
                        }
 
-                       $this->mTitle     = null; # Load on demand if needed
-                       $this->mCurrent   = false; # XXX: really? we are about to create a revision. it will usually then be the current one.
+                       // if we have a Title object, override mPage. Useful for testing and convenience.
+                       if ( isset( $row['title'] ) ) {
+                               $this->mTitle     = $row['title'];
+                               $this->mPage      = $this->mTitle->getArticleID();
+                       } else {
+                               $this->mTitle     = null; // Load on demand if needed
+                       }
+
+                       $this->mCurrent   = false; // @todo: XXX: really? we are about to create a revision. it will usually then be the current one.
 
-                       # If we still have no length, see it we have the text to figure it out
+                       // If we still have no length, see it we have the text to figure it out
                        if ( !$this->mSize ) {
                                if ( !is_null( $this->mContent ) ) {
                                        $this->mSize = $this->mContent->getSize();
@@ -577,13 +586,14 @@ class Revision {
                                }
                        }
 
-                       # Same for sha1
+                       // Same for sha1
                        if ( $this->mSha1 === null ) {
                                $this->mSha1 = is_null( $this->mText ) ? null : self::base36Sha1( $this->mText );
                        }
 
-                       $this->getContentModel(); # force lazy init
-                       $this->getContentFormat();    # force lazy init
+                       // force lazy init
+                       $this->getContentModel();
+                       $this->getContentFormat();
                } else {
                        throw new MWException( 'Revision constructor passed invalid row format.' );
                }
@@ -669,7 +679,10 @@ class Revision {
                        }
                }
 
-               //@todo: as a last resort, perhaps load from page table, if $this->mPage is given?!
+               if ( !$this->mTitle && !is_null( $this->mPage ) && $this->mPage > 0 ) {
+                       $this->mTitle = Title::newFromID( $this->mPage );
+               }
+
                return $this->mTitle;
        }
 
@@ -949,7 +962,7 @@ class Revision {
         * used to determine the content model to use. If no title is know, CONTENT_MODEL_WIKITEXT
         * is used as a last resort.
         *
-        * @return int the content model id associated with this revision, see the CONTENT_MODEL_XXX constants.
+        * @return String the content model id associated with this revision, see the CONTENT_MODEL_XXX constants.
         **/
        public function getContentModel() {
                if ( !$this->mContentModel ) {
@@ -968,7 +981,7 @@ class Revision {
         * If no content format was stored in the database, the default format for this
         * revision's content model is returned.
         *
-        * @return int the content format id associated with this revision, see the CONTENT_FORMAT_XXX constants.
+        * @return String the content format id associated with this revision, see the CONTENT_FORMAT_XXX constants.
         **/
        public function getContentFormat() {
                if ( !$this->mContentFormat ) {
@@ -994,10 +1007,7 @@ class Revision {
                        $format = $this->getContentFormat();
 
                        if ( !$this->mContentHandler->isSupportedFormat( $format ) ) {
-                               $formatName = ContentHandler::getContentFormatMimeType( $format );
-                               $modelName = ContentHandler::getContentModelName( $model );
-
-                               throw new MWException( "Oops, the content format #$format ($formatName) is not supported for this content model, #$model ($modelName)" );
+                               throw new MWException( "Oops, the content format $format is not supported for this content model, $model" );
                        }
                }
 
@@ -1190,6 +1200,8 @@ class Revision {
 
                wfProfileIn( __METHOD__ );
 
+               $this->checkContentModel();
+
                $data = $this->mText;
                $flags = Revision::compressRevisionText( $data );
 
@@ -1246,11 +1258,18 @@ class Revision {
                );
 
                if ( $wgContentHandlerUseDB ) {
-                       $row[ 'rev_content_model' ] = $this->getContentModel();
-                       $row[ 'rev_content_format' ] = $this->getContentFormat();
-               }
+                       //NOTE: Store null for the default model and format, to save space.
+                       //XXX: Makes the DB sensitive to changed defaults. Make this behaviour optional? Only in miser mode?
 
-               $this->checkContentModel();
+                       $model = $this->getContentModel();
+                       $format = $this->getContentFormat();
+
+                       $defaultModel = ContentHandler::getDefaultModelFor( $this->getTitle() );
+                       $defaultFormat = ContentHandler::getForModelID( $defaultModel )->getDefaultFormat();
+
+                       $row[ 'rev_content_model' ] = ( $model === $defaultModel ) ? null : $model;
+                       $row[ 'rev_content_format' ] = ( $format === $defaultFormat ) ? null : $format;
+               }
 
                $dbw->insert( 'revision', $row, __METHOD__ );
 
@@ -1265,7 +1284,7 @@ class Revision {
        protected function checkContentModel() {
                global $wgContentHandlerUseDB;
 
-               $title = $this->getTitle(); //note: returns null for revisions that have not yet been inserted.
+               $title = $this->getTitle(); //note: may return null for revisions that have not yet been inserted.
 
                $model = $this->getContentModel();
                $format = $this->getContentFormat();
@@ -1273,10 +1292,8 @@ class Revision {
 
                if ( !$handler->isSupportedFormat( $format ) ) {
                        $t = $title->getPrefixedDBkey();
-                       $modelName = ContentHandler::getContentModelName( $model );
-                       $formatName = ContentHandler::getContentFormatMimeType( $format );
 
-                       throw new MWException( "Can't use format #$format ($formatName) with content model #$model ($modelName) on $t" );
+                       throw new MWException( "Can't use format $format with content model $model on $t" );
                }
 
                if ( !$wgContentHandlerUseDB && $title ) {
@@ -1287,19 +1304,15 @@ class Revision {
                        $defaultFormat = $defaultHandler->getDefaultFormat();
 
                        if ( $this->getContentModel() != $defaultModel ) {
-                               $defaultModelName = ContentHandler::getContentModelName( $defaultModel );
-                               $modelName = ContentHandler::getContentModelName( $model );
                                $t = $title->getPrefixedDBkey();
 
-                               throw new MWException( "Can't save non-default content model with \$wgContentHandlerUseDB disabled: model is #$model ($modelName), default for $t is #$defaultModel ($defaultModelName)" );
+                               throw new MWException( "Can't save non-default content model with \$wgContentHandlerUseDB disabled: model is $model , default for $t is $defaultModel" );
                        }
 
                        if ( $this->getContentFormat() != $defaultFormat ) {
-                               $defaultFormatName = ContentHandler::getContentFormatMimeType( $defaultFormat );
-                               $formatName = ContentHandler::getContentFormatMimeType( $format );
                                $t = $title->getPrefixedDBkey();
 
-                               throw new MWException( "Can't use non-default content format with \$wgContentHandlerUseDB disabled: format is #$format ($formatName), default for $t is #$defaultFormat ($defaultFormatName)" );
+                               throw new MWException( "Can't use non-default content format with \$wgContentHandlerUseDB disabled: format is $format, default for $t is $defaultFormat" );
                        }
                }
 
@@ -1307,9 +1320,8 @@ class Revision {
 
                if ( !$content->isValid() ) {
                        $t = $title->getPrefixedDBkey();
-                       $modelName = ContentHandler::getContentModelName( $model );
 
-                       throw new MWException( "Content of $t is not valid! Content model is #$model ($modelName)" );
+                       throw new MWException( "Content of $t is not valid! Content model is $model" );
                }
        }
 
index a56a454..f6b183f 100644 (file)
@@ -78,7 +78,7 @@ abstract class SqlDataUpdate extends DataUpdate {
         * Abort the database transaction started via beginTransaction (if any).
         */
        public function abortTransaction() {
-               if ( $this->mHasTransaction ) {
+               if ( $this->mHasTransaction ) { //XXX: actually... maybe always?
                        $this->mDb->rollback( get_class( $this ) . '::abortTransaction' );
                }
        }
index 7f63d64..f80a74d 100644 (file)
@@ -300,7 +300,7 @@ class Title {
                        if ( isset( $row->page_latest ) )
                                $this->mLatestID = (int)$row->page_latest;
                        if ( isset( $row->page_content_model ) )
-                               $this->mContentModel = intval( $row->page_content_model );
+                               $this->mContentModel = strval( $row->page_content_model );
                        else
                                $this->mContentModel = false; # initialized lazily in getContentModel()
                } else { // page not found
@@ -670,7 +670,7 @@ class Title {
        /**
         * Get the page's content model id, see the CONTENT_MODEL_XXX constants.
         *
-        * @return Integer: Content model id
+        * @return String: Content model id
         */
        public function getContentModel() {
                if ( !$this->mContentModel ) {
@@ -3819,7 +3819,7 @@ class Title {
                $this->mArticleID = $row ? intval( $row->page_id ) : 0;
                $this->mRedirect = $row ? (bool)$row->page_is_redirect : false;
                $this->mLatestID = $row ? intval( $row->page_latest ) : false;
-               $this->mContentModel = $row && isset( $row->page_content_model ) ? intval( $row->page_content_model ) : false;
+               $this->mContentModel = $row && isset( $row->page_content_model ) ? strval( $row->page_content_model ) : false;
                if ( !$this->mRedirect ) {
                        return false;
                }
@@ -4548,17 +4548,17 @@ class Title {
                if ( $this->isSpecialPage() ) {
                        // special pages are in the user language
                        return $wgLang;
-               } elseif ( $this->isCssOrJsPage() || $this->isCssJsSubpage() ) {
-                       // css/js should always be LTR and is, in fact, English
-                       return wfGetLangObj( 'en' );
                } elseif ( $this->getNamespace() == NS_MEDIAWIKI ) {
                        // Parse mediawiki messages with correct target language
                        list( /* $unused */, $lang ) = MessageCache::singleton()->figureMessage( $this->getText() );
                        return wfGetLangObj( $lang );
                }
-               global $wgContLang;
-               // If nothing special, it should be in the wiki content language
-               $pageLang = $wgContLang;
+
+               //TODO: use the LinkCache to cache this!
+               //NOTE: ContentHandler::getPageLanguage() may need to load the content to determine the page language!
+               $contentHandler = ContentHandler::getForTitle( $this );
+               $pageLang = $contentHandler->getPageLanguage( $this );
+
                // Hook at the end because we don't want to override the above stuff
                wfRunHooks( 'PageContentLanguage', array( $this, &$pageLang, $wgLang ) );
                return wfGetLangObj( $pageLang );
index 5d14ad4..ad1fded 100644 (file)
@@ -500,7 +500,7 @@ class WikiPage extends Page {
         * Will use the revisions actual content model if the page exists,
         * and the page's default if the page doesn't exist yet.
         *
-        * @return int
+        * @return String
         *
         * @since 1.WD
         */
@@ -1729,6 +1729,17 @@ class WikiPage extends Page {
                                }
 
                                $dbw->begin( __METHOD__ );
+
+                               $prepStatus = $content->prepareSave( $this, $flags, $baseRevId, $user );
+                               $status->merge( $prepStatus );
+
+                               if ( !$status->isOK() ) {
+                                       $dbw->rollback();
+
+                                       wfProfileOut( __METHOD__ );
+                                       return $status;
+                               }
+
                                $revisionId = $revision->insertOn( $dbw );
 
                                # Update page
@@ -1803,6 +1814,18 @@ class WikiPage extends Page {
 
                        $dbw->begin( __METHOD__ );
 
+                       $prepStatus = $content->prepareSave( $this, $flags, $baseRevId, $user );
+                       $status->merge( $prepStatus );
+
+                       if ( !$status->isOK() ) {
+                               $dbw->rollback();
+
+                               wfProfileOut( __METHOD__ );
+                               return $status;
+                       }
+
+                       $status->merge( $prepStatus );
+
                        # Add the page record; stake our claim on this title!
                        # This will return false if the article already exists
                        $newid = $this->insertOn( $dbw );
index 814f0d4..21b72d9 100644 (file)
@@ -54,6 +54,23 @@ class ApiEditPage extends ApiBase {
                        $this->dieUsageMsg( array( 'invalidtitle', $params['title'] ) );
                }
 
+               $contentHandler = $pageObj->getContentHandler();
+
+               // @todo ask handler whether direct editing is supported at all! make allowFlatEdit() method or some such
+
+               if ( !isset( $params['contentformat'] ) || $params['contentformat'] == '' ) {
+                       $params['contentformat'] = $contentHandler->getDefaultFormat();
+               }
+
+               $contentFormat = $params['contentformat'];
+
+               if ( !$contentHandler->isSupportedFormat( $contentFormat ) ) {
+                       $name = $titleObj->getPrefixedDBkey();
+                       $model = $contentHandler->getModelID();
+
+                       $this->dieUsage( "The requested format $contentFormat is not supported for content model $model used by $name", 'badformat' );
+               }
+
                $apiResult = $this->getResult();
 
                if ( $params['redirect'] ) {
@@ -99,32 +116,52 @@ class ApiEditPage extends ApiBase {
                        $this->dieUsageMsg( $errors[0] );
                }
 
-               $articleObj = Article::newFromTitle( $titleObj, $this->getContext() );
-
                $toMD5 = $params['text'];
                if ( !is_null( $params['appendtext'] ) || !is_null( $params['prependtext'] ) )
                {
-                       // For non-existent pages, Article::getContent()
-                       // returns an interface message rather than ''
-                       // We do want getContent()'s behavior for non-existent
-                       // MediaWiki: pages, though
-                       if ( $articleObj->getID() == 0 && $titleObj->getNamespace() != NS_MEDIAWIKI ) {
-                               $content = null;
-                               $text = '';
-                       } else {
-                               $content = $articleObj->getContentObject();
-                               $text = ContentHandler::getContentText( $content ); #FIXME: serialize?! get format from params?...
+                       $content = $pageObj->getContent();
+
+                       if ( !( $content instanceof TextContent ) ) {
+                               // @todo: ContentHandler should have an isFlat() method or some such
+                               // @todo: XXX: or perhaps there should be Content::append(), Content::prepend() and Content::supportsConcatenation()
+                               $mode = $contentHandler->getModelID();
+                               $this->dieUsage( "Can't append to pages using content model $mode", 'appendnotsupported' );
+                       }
+
+                       if ( !$content ) {
+                               # If this is a MediaWiki:x message, then load the messages
+                               # and return the message value for x.
+                               if ( $titleObj->getNamespace() == NS_MEDIAWIKI ) {
+                                       $text = $titleObj->getDefaultMessageText();
+                                       if ( $text === false ) {
+                                               $text = '';
+                                       }
+
+                                       $content = ContentHandler::makeContent( $text, $this->getTitle() );
+                               }
                        }
 
                        if ( !is_null( $params['section'] ) ) {
+                               if ( !$contentHandler->supportsSections() ) {
+                                       $modelName = $contentHandler->getModelID();
+                                       $this->dieUsage( "Sections are not supported for this content model: $modelName.", 'sectionsnotsupported' );
+                               }
+
                                // Process the content for section edits
                                $section = intval( $params['section'] );
-                               $sectionContent = $content->getSection( $section );
-                               $text = ContentHandler::getContentText( $sectionContent ); #FIXME: serialize?! get format from params?...
-                               if ( $text === false || $text === null ) {
+                               $content = $content->getSection( $section );
+
+                               if ( !$content ) {
                                        $this->dieUsage( "There is no section {$section}.", 'nosuchsection' );
                                }
                        }
+
+                       if ( !$content ) {
+                               $text = '';
+                       } else {
+                               $text = $content->serialize( $contentFormat );
+                       }
+
                        $params['text'] = $params['prependtext'] . $text . $params['appendtext'];
                        $toMD5 = $params['prependtext'] . $params['appendtext'];
                }
@@ -149,18 +186,21 @@ class ApiEditPage extends ApiBase {
                                $this->dieUsageMsg( array( 'nosuchrevid', $params['undoafter'] ) );
                        }
 
-                       if ( $undoRev->getPage() != $articleObj->getID() ) {
+                       if ( $undoRev->getPage() != $pageObj->getID() ) {
                                $this->dieUsageMsg( array( 'revwrongpage', $undoRev->getID(), $titleObj->getPrefixedText() ) );
                        }
-                       if ( $undoafterRev->getPage() != $articleObj->getID() ) {
+                       if ( $undoafterRev->getPage() != $pageObj->getID() ) {
                                $this->dieUsageMsg( array( 'revwrongpage', $undoafterRev->getID(), $titleObj->getPrefixedText() ) );
                        }
 
-                       $newtext = $articleObj->getUndoText( $undoRev, $undoafterRev );
-                       if ( $newtext === false ) {
+                       $newContent = $contentHandler->getUndoContent( $undoRev, $undoafterRev );
+                       if ( !$newContent ) {
                                $this->dieUsageMsg( 'undo-failure' );
                        }
-                       $params['text'] = $newtext;
+
+                       $params['contentformat'] = $contentHandler->getDefaultFormat();
+                       $params['text'] = $newContent->serialize( $params['contentformat'] );
+
                        // If no summary was given and we only undid one rev,
                        // use an autosummary
                        if ( is_null( $params['summary'] ) && $titleObj->getNextRevisionID( $undoafterRev->getID() ) == $params['undo'] ) {
@@ -177,6 +217,8 @@ class ApiEditPage extends ApiBase {
                // That interface kind of sucks, but it's workable
                $requestArray = array(
                        'wpTextbox1' => $params['text'],
+                       'format' => $contentFormat,
+                       'model' => $contentHandler->getModelID(),
                        'wpEditToken' => $params['token'],
                        'wpIgnoreBlankSummary' => ''
                );
@@ -194,7 +236,7 @@ class ApiEditPage extends ApiBase {
                if ( !is_null( $params['basetimestamp'] ) && $params['basetimestamp'] != '' ) {
                        $requestArray['wpEdittime'] = wfTimestamp( TS_MW, $params['basetimestamp'] );
                } else {
-                       $requestArray['wpEdittime'] = $articleObj->getTimestamp();
+                       $requestArray['wpEdittime'] = $pageObj->getTimestamp();
                }
 
                if ( !is_null( $params['starttimestamp'] ) && $params['starttimestamp'] != '' ) {
@@ -242,8 +284,8 @@ class ApiEditPage extends ApiBase {
                // TODO: Make them not or check if they still do
                $wgTitle = $titleObj;
 
-               $handler = ContentHandler::getForTitle( $titleObj );
-               $ep = $handler->createEditPage( $articleObj );
+               $articleObject = new Article( $titleObj );
+               $ep = new EditPage( $articleObject );
 
                $ep->setContextTitle( $titleObj );
                $ep->importFormData( $req );
@@ -262,7 +304,7 @@ class ApiEditPage extends ApiBase {
                }
 
                // Do the actual save
-               $oldRevId = $articleObj->getRevIdFetched();
+               $oldRevId = $articleObject->getRevIdFetched();
                $result = null;
                // Fake $wgRequest for some hooks inside EditPage
                // @todo FIXME: This interface SUCKS
@@ -329,14 +371,15 @@ class ApiEditPage extends ApiBase {
                                $r['result'] = 'Success';
                                $r['pageid'] = intval( $titleObj->getArticleID() );
                                $r['title'] = $titleObj->getPrefixedText();
-                               $newRevId = $articleObj->getLatest();
+                               $r['contentmodel'] = $titleObj->getContentModel();
+                               $newRevId = $articleObject->getLatest();
                                if ( $newRevId == $oldRevId ) {
                                        $r['nochange'] = '';
                                } else {
                                        $r['oldrevid'] = intval( $oldRevId );
                                        $r['newrevid'] = intval( $newRevId );
                                        $r['newtimestamp'] = wfTimestamp( TS_ISO_8601,
-                                               $articleObj->getTimestamp() );
+                                               $pageObj->getTimestamp() );
                                }
                                break;
 
@@ -397,6 +440,10 @@ class ApiEditPage extends ApiBase {
                                array( 'unknownerror', 'retval' ),
                                array( 'code' => 'nosuchsection', 'info' => 'There is no section section.' ),
                                array( 'code' => 'invalidsection', 'info' => 'The section parameter must be set to an integer or \'new\'' ),
+                               array( 'code' => 'sectionsnotsupported', 'info' => 'Sections are not supported for this type of page.' ),
+                               array( 'code' => 'editnotsupported', 'info' => 'Editing of this type of page is not supported using the text based edit API.' ),
+                               array( 'code' => 'appendnotsupported', 'info' => 'This type of page can not be edited by appending or prepending text.' ),
+                               array( 'code' => 'badformat', 'info' => 'The requested serialization format can not be applied to the page\'s content model' ),
                                array( 'customcssprotected' ),
                                array( 'customjsprotected' ),
                        )
index e3f13be..921f8a5 100644 (file)
@@ -161,13 +161,7 @@ class ApiQueryRevisions extends ApiQueryBase {
                $this->token = $params['token'];
 
                if ( !empty( $params['contentformat'] ) ) {
-                       $n = ContentHandler::getContentFormatID( $params['contentformat'] );
-
-                       if ( is_int( $n ) ) {
-                               $this->contentFormat = $n;
-                       } else {
-                               $this->dieUsage( "Unknown format " . $params['contentformat'], 'badformat' );
-                       }
+                       $this->contentFormat = $params['contentformat'];
                }
 
                // Possible indexes used
@@ -516,8 +510,7 @@ class ApiQueryRevisions extends ApiQueryBase {
                                } else {
                                        $this->setWarning( "Conversion to XML is supported for wikitext only, " .
                                                                                $title->getPrefixedDBkey() .
-                                                                               " uses content model #" . $content->getModel() .
-                                                                               " (" . ContentHandler::getContentModelName( $content->getModel() ). ")" );
+                                                                               " uses content model " . $content->getModel() . ")" );
                                }
                        }
 
@@ -530,8 +523,7 @@ class ApiQueryRevisions extends ApiQueryBase {
                                } else {
                                        $this->setWarning( "Template expansion is supported for wikitext only, " .
                                                $title->getPrefixedDBkey() .
-                                               " uses content model #" . $content->getModel() .
-                                               " (" . ContentHandler::getContentModelName( $content->getModel() ). ")" );
+                                               " uses content model " . $content->getModel() . ")" );
 
                                        $text = false;
                                }
@@ -546,15 +538,13 @@ class ApiQueryRevisions extends ApiQueryBase {
 
                                if ( !$content->isSupportedFormat( $format ) ) {
                                        $model = $content->getModel();
-                                       $formatName = ContentHandler::getContentFormatMimeType( $format );
-                                       $modelName = ContentHandler::getContentModelName( $model );
                                        $name = $title->getPrefixedDBkey();
 
-                                       $this->dieUsage( "The requested format #{$this->contentFormat} ($formatName) is not supported for content model #$model ($modelName) used by $name", 'badformat' );
+                                       $this->dieUsage( "The requested format {$this->contentFormat} is not supported for content model $model used by $name", 'badformat' );
                                }
 
                                $text = $content->serialize( $format );
-                               $vals['contentformat'] = ContentHandler::getContentFormatMimeType( $format );
+                               $vals['contentformat'] = $format;
                        }
 
                        if ( $text !== false ) {
@@ -577,11 +567,9 @@ class ApiQueryRevisions extends ApiQueryBase {
                                        $model = $title->getContentModel();
 
                                        if ( $this->contentFormat && !ContentHandler::getForModelID( $model )->isSupportedFormat( $this->contentFormat ) ) {
-                                               $formatName = ContentHandler::getContentFormatMimeType( $this->contentFormat );
-                                               $modelName = ContentHandler::getContentModelName( $model );
                                                $name = $title->getPrefixedDBkey();
 
-                                               $this->dieUsage( "The requested format #{$this->contentFormat} ($formatName) is not supported for content model #$model ($modelName) used by $name", 'badformat' );
+                                               $this->dieUsage( "The requested format {$this->contentFormat} is not supported for content model $model used by $name", 'badformat' );
                                        }
 
                                        $difftocontent = ContentHandler::makeContent( $this->difftotext, $title, $model, $this->contentFormat );
@@ -680,7 +668,7 @@ class ApiQueryRevisions extends ApiQueryBase {
                        'diffto' => null,
                        'difftotext' => null,
                        'contentformat' => array(
-                               ApiBase::PARAM_TYPE => array_values( $GLOBALS[ 'wgContentFormatMimeTypes' ] ),
+                               ApiBase::PARAM_TYPE => ContentHandler::getAllContentFormats(),
                                ApiBase::PARAM_DFLT => null
                        ),
                );
index a48d007..623f545 100644 (file)
@@ -128,7 +128,7 @@ class LinkCache {
                        'length' => intval( $row->page_len ),
                        'redirect' => intval( $row->page_is_redirect ),
                        'revision' => intval( $row->page_latest ),
-                       'model' => !empty( $row->page_content_model ) ? intval( $row->page_content_model ) : null,
+                       'model' => !empty( $row->page_content_model ) ? strval( $row->page_content_model ) : null,
                );
        }
 
index 8b3cde5..fd1f6d5 100644 (file)
@@ -1413,7 +1413,7 @@ If you save it, any changes made since this revision will be lost.",
 'yourdiff'                         => 'Differences',
 'copyrightwarning'                 => "Please note that all contributions to {{SITENAME}} are considered to be released under the $2 (see $1 for details).
 If you do not want your writing to be edited mercilessly and redistributed at will, then do not submit it here.<br />
-You are also promising us that you wrote this yourself, or copied it from a public domain or similar free resource.
+You are also promising us that you wrote this yourself, or copied editpageit from a public domain or similar free resource.
 '''Do not submit copyrighted work without permission!'''",
 'copyrightwarning2'                => "Please note that all contributions to {{SITENAME}} may be edited, altered, or removed by other contributors.
 If you do not want your writing to be edited mercilessly, then do not submit it here.<br />
@@ -1469,6 +1469,7 @@ It already exists.',
 'addsection-preload'               => '', # do not translate or duplicate this message to other languages
 'addsection-editintro'             => '', # do not translate or duplicate this message to other languages
 'defaultmessagetext'               => 'Default message text',
+'invalid-content-data'             => 'Invalid content data',
 
 # Parser/template warnings
 'expensive-parserfunction-warning'        => "'''Warning:''' This page contains too many expensive parser function calls.
index c8e4357..ab7f7a3 100644 (file)
@@ -1023,6 +1023,7 @@ Please report at [[Support]] if you are unable to properly translate this messag
 'moveddeleted-notice' => 'Shown on top of a deleted page in normal view modus ([http://translatewiki.net/wiki/Test example]).',
 'edit-conflict' => "An 'Edit conflict' happens when more than one edit is being made to a page at the same time. This would usually be caused by separate individuals working on the same page. However, if the system is slow, several edits from one individual could back up and attempt to apply simultaneously - causing the conflict.",
 'defaultmessagetext' => 'Caption above the default message text shown on the left-hand side of a diff displayed after clicking “Show changes” when creating a new page in the MediaWiki: namespace',
+'invalid-content-data'             => 'Error message indicating that the page\'s content can not be saved because it is invalid. This may occurr for some non-text content types.',
 
 # Parser/template warnings
 'expensive-parserfunction-warning' => 'On some (expensive) [[MetaWikipedia:Help:ParserFunctions|parser functions]] (e.g. <code><nowiki>{{#ifexist:}}</nowiki></code>) there is a limit of how many times it may be used. This is an error message shown when the limit is exceeded.
index c62ddfb..81f9fca 100644 (file)
@@ -1,2 +1,2 @@
 ALTER TABLE /*$wgDBprefix*/archive
-  ADD ar_content_format int unsigned DEFAULT NULL;
+  ADD ar_content_format varbinary(64) DEFAULT NULL;
index 8c18bba..1a8b630 100644 (file)
@@ -1,2 +1,2 @@
 ALTER TABLE /*$wgDBprefix*/archive
-  ADD ar_content_model int unsigned DEFAULT NULL;
+  ADD ar_content_model varbinary(32) DEFAULT NULL;
index 89df112..30434d9 100644 (file)
@@ -1,2 +1,2 @@
 ALTER TABLE /*$wgDBprefix*/page
-  ADD page_content_model int unsigned DEFAULT NULL;
+  ADD page_content_model varbinary(32) DEFAULT NULL;
index eed0306..22aeb8a 100644 (file)
@@ -1,2 +1,2 @@
 ALTER TABLE /*$wgDBprefix*/revision
-  ADD rev_content_format int unsigned DEFAULT NULL;
+  ADD rev_content_format varbinary(64) DEFAULT NULL;
index 1834b75..1ba0572 100644 (file)
@@ -1,2 +1,2 @@
 ALTER TABLE /*$wgDBprefix*/revision
-  ADD rev_content_model int unsigned DEFAULT NULL;
+  ADD rev_content_model varbinary(32) DEFAULT NULL;
diff --git a/maintenance/storage/drop_content_model_info.sql b/maintenance/storage/drop_content_model_info.sql
new file mode 100644 (file)
index 0000000..7bd9aba
--- /dev/null
@@ -0,0 +1,7 @@
+ALTER TABLE /*$wgDBprefix*/archive  DROP COLUMN ar_content_model;
+ALTER TABLE /*$wgDBprefix*/archive  DROP COLUMN ar_content_format;
+
+ALTER TABLE /*$wgDBprefix*/revision  DROP COLUMN rev_content_model;
+ALTER TABLE /*$wgDBprefix*/revision  DROP COLUMN rev_content_format;
+
+ALTER TABLE /*$wgDBprefix*/page  DROP COLUMN page_content_model;
index 6c06049..2eaa285 100644 (file)
@@ -16,8 +16,8 @@ class ContentHandlerTest extends MediaWikiTestCase {
                $wgExtraNamespaces[ 12312 ] = 'Dummy';
                $wgExtraNamespaces[ 12313 ] = 'Dummy_talk';
 
-               $wgNamespaceContentModels[ 12312 ] = 999999;
-               $wgContentHandlers[ 999999 ] = 'DummyContentHandlerForTesting';
+               $wgNamespaceContentModels[ 12312 ] = "testing";
+               $wgContentHandlers[ "testing" ] = 'DummyContentHandlerForTesting';
 
                MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache
                $wgContLang->resetNamespaces(); # reset namespace cache
@@ -30,7 +30,7 @@ class ContentHandlerTest extends MediaWikiTestCase {
                unset( $wgExtraNamespaces[ 12313 ] );
 
                unset( $wgNamespaceContentModels[ 12312 ] );
-               unset( $wgContentHandlers[ 999999 ] );
+               unset( $wgContentHandlers[ "testing" ] );
 
                MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache
                $wgContLang->resetNamespaces(); # reset namespace cache
@@ -72,121 +72,59 @@ class ContentHandlerTest extends MediaWikiTestCase {
                $this->assertEquals( $expectedContentModel, $handler->getModelID() );
        }
 
-       public function dataGetContentFormatMimeType( ) {
+       public function dataGetLocalizedName() {
                return array(
-                       array( 0, null ),
                        array( null, null ),
-                       array( 99887766, null ),
-
-                       array( CONTENT_FORMAT_WIKITEXT, 'text/x-wiki' ),
-                       array( CONTENT_FORMAT_JAVASCRIPT, 'text/javascript' ),
-                       array( CONTENT_FORMAT_CSS, 'text/css' ),
-                       array( CONTENT_FORMAT_JSON, 'application/json' ),
-                       array( CONTENT_FORMAT_XML, 'application/xml' ),
-                       array( CONTENT_FORMAT_SERIALIZED, 'application/vnd.php.serialized' ),
-               );
-       }
-
-       /**
-        * @dataProvider dataGetContentFormatMimeType
-        */
-       public function testGetContentFormatMimeType( $id, $expectedMime ) {
-               $mime = ContentHandler::getContentFormatMimeType( $id );
-
-               $this->assertEquals( $expectedMime, $mime );
-       }
-
-       public function dataGetContentFormatID( ) {
-               return array(
-                       array( '', null ),
-                       array( 'foo', null ),
-                       array( null, null ),
-
-                       array( 'text/x-wiki', CONTENT_FORMAT_WIKITEXT ),
-                       array( 'text/javascript', CONTENT_FORMAT_JAVASCRIPT ),
-                       array( 'text/css', CONTENT_FORMAT_CSS ),
-                       array( 'application/json', CONTENT_FORMAT_JSON ),
-                       array( 'application/xml', CONTENT_FORMAT_XML ),
-                       array( 'application/vnd.php.serialized', CONTENT_FORMAT_SERIALIZED ),
-               );
-       }
-
-       /**
-        * @dataProvider dataGetContentFormatID
-        */
-       public function testGetContentFormatID( $mime, $expectedId ) {
-               $id = ContentHandler::getContentFormatID( $mime );
-
-               $this->assertEquals( $expectedId, $id );
-       }
-
-       public function dataGetLocalizedNameName() {
-               return array(
-                       array( 0, null ),
-                       array( null, null ),
-                       array( 99887766, null ),
+                       array( "xyzzy", null ),
 
                        array( CONTENT_MODEL_JAVASCRIPT, '/javascript/i' ), //XXX: depends on content language
                );
        }
 
        /**
-        * @dataProvider dataGetLocalizedNameName
+        * @dataProvider dataGetLocalizedName
         */
        public function testGetLocalizedName( $id, $expected ) {
-               try{
-                       $name = ContentHandler::getLocalizedName( $id );
+               $name = ContentHandler::getLocalizedName( $id );
 
-                       if ( !$expected ) $this->fail("should not have a name for content id #$id");
-
-                       $this->assertNotNull( $name, "no name found for content model #$id" );
+               if ( $expected ) {
+                       $this->assertNotNull( $name, "no name found for content model $id" );
                        $this->assertTrue( preg_match( $expected, $name ) > 0 , "content model name for #$id did not match pattern $expected" );
-               } catch (MWException $e) {
-                       if ( $expected ) $this->fail("failed to get name for content id #$id");
+               } else {
+                       $this->assertEquals( $id, $name, "localization of unknown model $id should have fallen back to use the model id directly." );
                }
        }
 
-       public function dataGetContentModelName() {
+       public function dataGetPageLanguage() {
+               global $wgLanguageCode;
+
                return array(
-                       array( 0, null ),
-                       array( null, null ),
-                       array( 99887766, null ),
+                       array( "Main", $wgLanguageCode ),
+                       array( "Dummy:Foo", $wgLanguageCode ),
+                       array( "MediaWiki:common.js", 'en' ),
+                       array( "User:Foo/common.js", 'en' ),
+                       array( "MediaWiki:common.css", 'en' ),
+                       array( "User:Foo/common.css", 'en' ),
+                       array( "User:Foo", $wgLanguageCode ),
 
                        array( CONTENT_MODEL_JAVASCRIPT, 'javascript' ),
                );
        }
 
        /**
-        * @dataProvider dataGetContentModelName
+        * @dataProvider dataGetPageLanguage
         */
-       public function testGetContentModelName( $id, $expected ) {
-               try {
-                       $name = ContentHandler::getContentModelName( $id );
-
-                       if ( !$expected ) $this->fail("should not have a name for content id #$id");
-
-                       $this->assertNotNull( $name, "no name found for content model #$id" );
-                       $this->assertEquals( $expected, $name);
-               } catch (MWException $e) {
-                       if ( $expected ) $this->fail("failed to get name for content id #$id");
+       public function testGetPageLanguage( $title, $expected ) {
+               if ( is_string( $title ) ) {
+                       $title = Title::newFromText( $title );
                }
-       }
 
-       /**
-        * @dataProvider dataGetContentModelName
-        */
-       public function testGetModelName( $id, $expected ) {
-               try {
-                       $handler = ContentHandler::getForModelID( $id );
-                       $name = $handler->getModelName();
+               $expected = wfGetLangObj( $expected );
 
-                       if ( !$expected ) $this->fail("should not have a name for content id #$id");
+               $handler = ContentHandler::getForTitle( $title );
+               $lang = $handler->getPageLanguage( $title );
 
-                       $this->assertNotNull( $name, "no name found for content model #$id" );
-                       $this->assertEquals( $expected, $name);
-               } catch (MWException $e) {
-                       if ( $expected ) $this->fail("failed to get name for content id #$id");
-               }
+               $this->assertEquals( $expected->getCode(), $lang->getCode() );
        }
 
        public function testGetContentText_Null( ) {
@@ -255,19 +193,19 @@ class ContentHandlerTest extends MediaWikiTestCase {
                return array(
                        array( 'hallo', 'Test', null, null, CONTENT_MODEL_WIKITEXT, 'hallo', false ),
                        array( 'hallo', 'MediaWiki:Test.js', null, null, CONTENT_MODEL_JAVASCRIPT, 'hallo', false ),
-                       array( serialize('hallo'), 'Dummy:Test', null, null, 999999, 'hallo', false ),
+                       array( serialize('hallo'), 'Dummy:Test', null, null, "testing", 'hallo', false ),
 
                        array( 'hallo', 'Test', null, CONTENT_FORMAT_WIKITEXT, CONTENT_MODEL_WIKITEXT, 'hallo', false ),
                        array( 'hallo', 'MediaWiki:Test.js', null, CONTENT_FORMAT_JAVASCRIPT, CONTENT_MODEL_JAVASCRIPT, 'hallo', false ),
-                       array( serialize('hallo'), 'Dummy:Test', null, 999999, 999999, 'hallo', false ),
+                       array( serialize('hallo'), 'Dummy:Test', null, "testing", "testing", 'hallo', false ),
 
                        array( 'hallo', 'Test', CONTENT_MODEL_CSS, null, CONTENT_MODEL_CSS, 'hallo', false ),
                        array( 'hallo', 'MediaWiki:Test.js', CONTENT_MODEL_CSS, null, CONTENT_MODEL_CSS, 'hallo', false ),
                        array( serialize('hallo'), 'Dummy:Test', CONTENT_MODEL_CSS, null, CONTENT_MODEL_CSS, serialize('hallo'), false ),
 
-                       array( 'hallo', 'Test', CONTENT_MODEL_WIKITEXT, 999999, null, null, true ),
-                       array( 'hallo', 'MediaWiki:Test.js', CONTENT_MODEL_CSS, 999999, null, null, true ),
-                       array( 'hallo', 'Dummy:Test', CONTENT_MODEL_JAVASCRIPT, 999999, null, null, true ),
+                       array( 'hallo', 'Test', CONTENT_MODEL_WIKITEXT, "testing", null, null, true ),
+                       array( 'hallo', 'MediaWiki:Test.js', CONTENT_MODEL_CSS, "testing", null, null, true ),
+                       array( 'hallo', 'Dummy:Test', CONTENT_MODEL_JAVASCRIPT, "testing", null, null, true ),
                );
        }
 
@@ -408,7 +346,7 @@ class ContentHandlerTest extends MediaWikiTestCase {
 class DummyContentHandlerForTesting extends ContentHandler {
 
        public function __construct( $dataModel ) {
-               parent::__construct( $dataModel, array( 999999 ) );
+               parent::__construct( $dataModel, array( "testing" ) );
        }
 
        /**
@@ -465,7 +403,7 @@ class DummyContentHandlerForTesting extends ContentHandler {
 class DummyContentForTesting extends AbstractContent {
 
        public function __construct( $data ) {
-               parent::__construct( 999999 );
+               parent::__construct( "testing" );
 
                $this->data = $data;
        }
index ed7d919..0f247c3 100644 (file)
@@ -29,8 +29,8 @@ class RevisionTest extends MediaWikiTestCase {
                $wgExtraNamespaces[ 12312 ] = 'Dummy';
                $wgExtraNamespaces[ 12313 ] = 'Dummy_talk';
 
-               $wgNamespaceContentModels[ 12312 ] = 999999;
-               $wgContentHandlers[ 999999 ] = 'DummyContentHandlerForTesting';
+               $wgNamespaceContentModels[ 12312 ] = "testing";
+               $wgContentHandlers[ "testing" ] = 'DummyContentHandlerForTesting';
 
                MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache
                $wgContLang->resetNamespaces(); # reset namespace cache
@@ -186,7 +186,7 @@ class RevisionTest extends MediaWikiTestCase {
                return array(
                        array( 'hello world', 'Hello', null, null, CONTENT_MODEL_WIKITEXT ),
                        array( 'hello world', 'User:hello/there.css', null, null, CONTENT_MODEL_CSS ),
-                       array( serialize('hello world'), 'Dummy:Hello', null, null, 999999 ),
+                       array( serialize('hello world'), 'Dummy:Hello', null, null, "testing" ),
                );
        }
 
@@ -204,7 +204,7 @@ class RevisionTest extends MediaWikiTestCase {
                        array( 'hello world', 'Hello', null, null, CONTENT_FORMAT_WIKITEXT ),
                        array( 'hello world', 'Hello', CONTENT_MODEL_CSS, null, CONTENT_FORMAT_CSS ),
                        array( 'hello world', 'User:hello/there.css', null, null, CONTENT_FORMAT_CSS ),
-                       array( serialize('hello world'), 'Dummy:Hello', null, null, 999999 ),
+                       array( serialize('hello world'), 'Dummy:Hello', null, null, "testing" ),
                );
        }
 
@@ -237,7 +237,7 @@ class RevisionTest extends MediaWikiTestCase {
        function dataGetContent() {
                return array(
                        array( 'hello world', 'Hello', null, null, Revision::FOR_PUBLIC, 'hello world' ),
-                       array( serialize('hello world'), 'Hello', 999999, null, Revision::FOR_PUBLIC, serialize('hello world') ),
+                       array( serialize('hello world'), 'Hello', "testing", null, Revision::FOR_PUBLIC, serialize('hello world') ),
                        array( serialize('hello world'), 'Dummy:Hello', null, null, Revision::FOR_PUBLIC, serialize('hello world') ),
                );
        }
@@ -255,7 +255,7 @@ class RevisionTest extends MediaWikiTestCase {
        function dataGetText() {
                return array(
                        array( 'hello world', 'Hello', null, null, Revision::FOR_PUBLIC, 'hello world' ),
-                       array( serialize('hello world'), 'Hello', 999999, null, Revision::FOR_PUBLIC, null ),
+                       array( serialize('hello world'), 'Hello', "testing", null, Revision::FOR_PUBLIC, null ),
                        array( serialize('hello world'), 'Dummy:Hello', null, null, Revision::FOR_PUBLIC, null ),
                );
        }
@@ -282,7 +282,7 @@ class RevisionTest extends MediaWikiTestCase {
        public function dataGetSize( ) {
                return array(
                        array( "hello world.", null, 12 ),
-                       array( serialize( "hello world." ), 999999, 12 ),
+                       array( serialize( "hello world." ), "testing", 12 ),
                );
        }
 
@@ -299,7 +299,7 @@ class RevisionTest extends MediaWikiTestCase {
        public function dataGetSha1( ) {
                return array(
                        array( "hello world.", null, Revision::base36Sha1( "hello world." ) ),
-                       array( serialize( "hello world." ), 999999, Revision::base36Sha1( serialize( "hello world." ) ) ),
+                       array( serialize( "hello world." ), "testing", Revision::base36Sha1( serialize( "hello world." ) ) ),
                );
        }
 
diff --git a/tests/phpunit/includes/api/ApiEditPageTest.php b/tests/phpunit/includes/api/ApiEditPageTest.php
new file mode 100644 (file)
index 0000000..3ed983b
--- /dev/null
@@ -0,0 +1,104 @@
+<?php
+
+/**
+ * @group API
+ * @group Database
+ */
+class ApiEditPageTest extends ApiTestCase {
+
+       function setUp() {
+               parent::setUp();
+               $this->doLogin();
+       }
+
+       function getTokens() {
+               return $this->getTokenList( self::$users['sysop'] );
+       }
+
+       function testEdit() {
+               $name = 'ApiEditPageTest_testEdit';
+
+               $tokenData = $this->getTokens();
+
+               if( !isset( $tokenData[0]['query']['pages'] ) ) {
+                       $this->markTestIncomplete( "No edit token found" );
+               }
+
+               $keys = array_keys( $tokenData[0]['query']['pages'] );
+               $key = array_pop( $keys );
+               $pageinfo = $tokenData[0]['query']['pages'][$key];
+               $session = $tokenData[2];
+
+               // -----------------------------------------------------------------------
+
+               $data = $this->doApiRequest( array(
+                               'action' => 'edit',
+                               'title' => $name,
+                               'text' => 'some text',
+                               'token' => $pageinfo['edittoken'] ),
+                       $session,
+                       false,
+                       self::$users['sysop']->user );
+
+               $this->assertArrayHasKey( 'edit', $data[0] );
+               $this->assertArrayHasKey( 'result', $data[0]['edit'] );
+               $this->assertEquals( 'Success', $data[0]['edit']['result'] );
+
+               $this->assertArrayHasKey( 'new', $data[0]['edit'] );
+               $this->assertArrayNotHasKey( 'nochange', $data[0]['edit'] );
+
+               $this->assertArrayHasKey( 'pageid', $data[0]['edit'] );
+               $this->assertArrayHasKey( 'contentmodel', $data[0]['edit'] );
+               $this->assertEquals( CONTENT_MODEL_WIKITEXT, $data[0]['edit']['contentmodel'] );
+
+               // -----------------------------------------------------------------------
+               $data = $this->doApiRequest( array(
+                               'action' => 'edit',
+                               'title' => $name,
+                               'text' => 'some text',
+                               'token' => $pageinfo['edittoken'] ),
+                       $session,
+                       false,
+                       self::$users['sysop']->user );
+
+               $this->assertEquals( 'Success', $data[0]['edit']['result'] );
+
+               $this->assertArrayNotHasKey( 'new', $data[0]['edit'] );
+               $this->assertArrayHasKey( 'nochange', $data[0]['edit'] );
+
+               // -----------------------------------------------------------------------
+               $data = $this->doApiRequest( array(
+                               'action' => 'edit',
+                               'title' => $name,
+                               'text' => 'different text',
+                               'token' => $pageinfo['edittoken'] ),
+                       $session,
+                       false,
+                       self::$users['sysop']->user );
+
+               $this->assertEquals( 'Success', $data[0]['edit']['result'] );
+
+               $this->assertArrayNotHasKey( 'new', $data[0]['edit'] );
+               $this->assertArrayNotHasKey( 'nochange', $data[0]['edit'] );
+
+               $this->assertArrayHasKey( 'oldrevid', $data[0]['edit'] );
+               $this->assertArrayHasKey( 'newrevid', $data[0]['edit'] );
+               $this->assertTrue( $data[0]['edit']['newrevid'] !== $data[0]['edit']['oldrevid'], "revision id should change after edit" );
+       }
+
+       function testEditAppend() {
+               $this->markTestIncomplete( "not yet implemented" );
+       }
+
+       function testEditSection() {
+               $this->markTestIncomplete( "not yet implemented" );
+       }
+
+       function testUndo() {
+               $this->markTestIncomplete( "not yet implemented" );
+       }
+
+       function testEditNonText() {
+               $this->markTestIncomplete( "not yet implemented" );
+       }
+}
index dc69e6b..f573c74 100644 (file)
@@ -295,8 +295,8 @@ abstract class DumpTestCase extends MediaWikiLangTestCase {
         * @param $text_sha1 string: the base36 SHA-1 of the revision's text
         * @param $text string|false: (optional) The revision's string, or false to check for a
         *            revision stub
-        * @param $model int: the expected content model id (default: CONTENT_MODEL_WIKITEXT)
-        * @param $format int: the expected format model id (default: CONTENT_FORMAT_WIKITEXT)
+        * @param $model String: the expected content model id (default: CONTENT_MODEL_WIKITEXT)
+        * @param $format String: the expected format model id (default: CONTENT_FORMAT_WIKITEXT)
         * @param $parentid int|false: (optional) id of the parent revision
         */
        protected function assertRevision( $id, $summary, $text_id, $text_bytes, $text_sha1, $text = false, $parentid = false,
index 1aa9326..227d2c8 100644 (file)
@@ -480,8 +480,8 @@ class TextPassDumperTest extends DumpTestCase {
       </contributor>
       <comment>BackupDumperTestP1Summary1</comment>
       <sha1>0bolhl6ol7i6x0e7yq91gxgaan39j87</sha1>
-      <model name="wikitext">1</model>
-      <format mime="text/x-wiki">1</format>
+      <model>wikitext</model>
+      <format>text/x-wiki</format>
       <text id="' . $this->textId1_1 . '" bytes="23" />
     </revision>
   </page>
@@ -499,8 +499,8 @@ class TextPassDumperTest extends DumpTestCase {
       </contributor>
       <comment>BackupDumperTestP2Summary1</comment>
       <sha1>jprywrymfhysqllua29tj3sc7z39dl2</sha1>
-      <model name="wikitext">1</model>
-      <format mime="text/x-wiki">1</format>
+      <model>wikitext</model>
+      <format>text/x-wiki</format>
       <text id="' . $this->textId2_1 . '" bytes="23" />
     </revision>
     <revision>
@@ -512,8 +512,8 @@ class TextPassDumperTest extends DumpTestCase {
       </contributor>
       <comment>BackupDumperTestP2Summary2</comment>
       <sha1>b7vj5ks32po5m1z1t1br4o7scdwwy95</sha1>
-      <model name="wikitext">1</model>
-      <format mime="text/x-wiki">1</format>
+      <model>wikitext</model>
+      <format>text/x-wiki</format>
       <text id="' . $this->textId2_2 . '" bytes="23" />
     </revision>
     <revision>
@@ -525,8 +525,8 @@ class TextPassDumperTest extends DumpTestCase {
       </contributor>
       <comment>BackupDumperTestP2Summary3</comment>
       <sha1>jfunqmh1ssfb8rs43r19w98k28gg56r</sha1>
-      <model name="wikitext">1</model>
-      <format mime="text/x-wiki">1</format>
+      <model>wikitext</model>
+      <format>text/x-wiki</format>
       <text id="' . $this->textId2_3 . '" bytes="23" />
     </revision>
     <revision>
@@ -537,8 +537,8 @@ class TextPassDumperTest extends DumpTestCase {
       </contributor>
       <comment>BackupDumperTestP2Summary4 extra</comment>
       <sha1>6o1ciaxa6pybnqprmungwofc4lv00wv</sha1>
-      <model name="wikitext">1</model>
-      <format mime="text/x-wiki">1</format>
+      <model>wikitext</model>
+      <format>text/x-wiki</format>
       <text id="' . $this->textId2_4 . '" bytes="44" />
     </revision>
   </page>
@@ -557,8 +557,8 @@ class TextPassDumperTest extends DumpTestCase {
       </contributor>
       <comment>Talk BackupDumperTestP1 Summary1</comment>
       <sha1>nktofwzd0tl192k3zfepmlzxoax1lpe</sha1>
-      <model name="wikitext">1</model>
-      <format mime="text/x-wiki">1</format>
+      <model>wikitext</model>
+      <format>text/x-wiki</format>
       <text id="' . $this->textId4_1 . '" bytes="35" />
     </revision>
   </page>