From d5b6d4a423befe9faca6057a3a61aad66ac87d40 Mon Sep 17 00:00:00 2001 From: Daniel Kinzler Date: Tue, 20 Mar 2012 15:00:26 +0000 Subject: [PATCH] doEdit(), etc --- includes/Content.php | 43 ++++++++- includes/ContentHandler.php | 9 +- includes/EditPage.php | 2 +- includes/WikiPage.php | 182 +++++++++++++++++++++++++++++++----- 4 files changed, 203 insertions(+), 33 deletions(-) diff --git a/includes/Content.php b/includes/Content.php index 8f2a4e0676..1ef81b0424 100644 --- a/includes/Content.php +++ b/includes/Content.php @@ -16,6 +16,14 @@ abstract class Content { return $this->mModelName; } + public function getContentHandler() { + return ContentHandler::getForContent( $this ); + } + + public function serialize( $format = null ) { + return $this->getContentHandler()->serialize( $this, $format ); + } + public abstract function getTextForSearchIndex( ); public abstract function getWikitextForTransclusion( ); @@ -33,8 +41,10 @@ abstract class Content { /** * returns the content's nominal size in bogo-bytes. + * + * @return int */ - public abstract function getSize( ); #XXX: do we really need/want this here? we could just use the byte syse of the serialized form... + public abstract function getSize( ); public function isEmpty() { return $this->getSize() == 0; @@ -95,6 +105,18 @@ abstract class Content { return $this; } + /** + * Returns a Content object with pre-save transformations applied (or this object if no transformations apply). + * + * @param Title $title + * @param User $user + * @param null|ParserOptions $popts + * @return Content + */ + public function preSaveTransform( Title $title, User $user, ParserOptions $popts = null ) { + return $this; + } + #TODO: implement specialized ParserOutput for Wikidata model #TODO: provide "combined" ParserOutput for Multipart... somehow. @@ -102,7 +124,7 @@ abstract class Content { # TODO: EditPage::getPreloadedText( $preload ) // $wgParser->getPreloadText # TODO: tie into EditPage, make it use Content-objects throughout, make edit form aware of content model and format - # TODO: tie into WikiPage, make it use Content-objects throughout, especially in doEdit(), doDelete(), updateRevisionOn(), etc + # TODO: tie into WikiPage, make it use Content-objects throughout, especially in doEditUpdates(), doDelete(), updateRevisionOn(), etc # TODO: make model-aware diff view! # TODO: handle ImagePage and CategoryPage @@ -307,6 +329,23 @@ class WikitextContent extends TextContent { return $newContent; } + /** + * Returns a Content object with pre-save transformations applied (or this object if no transformations apply). + * + * @param Title $title + * @param User $user + * @param null|ParserOptions $popts + * @return Content + */ + public function preSaveTransform( Title $title, User $user, ParserOptions $popts = null ) { + global $wgParser; + + $text = $this->getNativeData(); + $pst = $wgParser->preSaveTransform( $text, $title, $user, $popts ); + + return new WikitextContent( $pst ); + } + public function getRedirectChain() { $text = $this->getNativeData(); return Title::newFromRedirectArray( $text ); diff --git a/includes/ContentHandler.php b/includes/ContentHandler.php index 96069adba4..c9b27d3786 100644 --- a/includes/ContentHandler.php +++ b/includes/ContentHandler.php @@ -13,7 +13,7 @@ */ abstract class ContentHandler { - public static function getContentText( Content $content ) { + public static function getContentText( Content $content = null ) { if ( !$content ) return ''; if ( $content instanceof TextContent ) { @@ -26,6 +26,7 @@ abstract class ContentHandler { #XXX: this must not be used for editing, otherwise we may loose data: #XXX: e.g. if this returns the "main" text from a multipart page, all attachments would be lost + #TODO: log this incident! return null; } @@ -167,8 +168,6 @@ abstract class ContentHandler { public abstract function emptyContent(); - # public abstract function doPreSaveTransform( $title, $obj ); #TODO... - /** * Return an Article object suitable for viewing the given object * @@ -385,10 +384,10 @@ abstract class ContentHandler { * between $undo and $undoafter. Revisions must belong to the same page, * must exist and must not be deleted * @param $undo Revision - * @param $undoafter Revision Must be an earlier revision than $undo + * @param $undoafter null|Revision Must be an earlier revision than $undo * @return mixed string on success, false on failure */ - public function getUndoContent( Revision $current, Revision $undo, Revision $undoafter ) { + public function getUndoContent( Revision $current, Revision $undo, Revision $undoafter = null ) { $cur_content = $current->getContent(); if ( empty( $cur_content ) ) { diff --git a/includes/EditPage.php b/includes/EditPage.php index d3719c70fd..690b0e538e 100644 --- a/includes/EditPage.php +++ b/includes/EditPage.php @@ -1433,7 +1433,7 @@ class EditPage { ( ( $this->minoredit && !$this->isNew ) ? EDIT_MINOR : 0 ) | ( $bot ? EDIT_FORCE_BOT : 0 ); - $doEditStatus = $this->mArticle->doEdit( $text, $this->summary, $flags ); + $doEditStatus = $this->mArticle->doEdit( $text, $this->summary, $flags ); # FIXME: use WikiPage::doEditContent() if ( $doEditStatus->isOK() ) { $result['redirect'] = Title::newFromRedirect( $text ) !== null; diff --git a/includes/WikiPage.php b/includes/WikiPage.php index 0c4bd2c58c..6bd5e4cf4e 100644 --- a/includes/WikiPage.php +++ b/includes/WikiPage.php @@ -1231,8 +1231,64 @@ class WikiPage extends Page { * revision: The revision object for the inserted revision, or null * * Compatibility note: this function previously returned a boolean value indicating success/failure + * @deprecated since 1.20: use doEditContent() instead. */ - public function doEdit( $text, $summary, $flags = 0, $baseRevId = false, $user = null ) { #FIXME: change $text to $content + public function doEdit( $text, $summary, $flags = 0, $baseRevId = false, $user = null ) { #FIXME: use doEditContent() instead + #TODO: log use of deprecated function + $content = ContentHandler::makeContent( $text, $this->getTitle() ); + + return $this->doEditContent( $content, $summary, $flags, $baseRevId, $user ); + } + + /** + * Change an existing article or create a new article. Updates RC and all necessary caches, + * optionally via the deferred update array. + * + * @param $content Content: new content + * @param $summary String: edit summary + * @param $flags Integer bitfield: + * EDIT_NEW + * Article is known or assumed to be non-existent, create a new one + * EDIT_UPDATE + * Article is known or assumed to be pre-existing, update it + * EDIT_MINOR + * Mark this edit minor, if the user is allowed to do so + * EDIT_SUPPRESS_RC + * Do not log the change in recentchanges + * EDIT_FORCE_BOT + * Mark the edit a "bot" edit regardless of user rights + * EDIT_DEFER_UPDATES + * Defer some of the updates until the end of index.php + * EDIT_AUTOSUMMARY + * Fill in blank summaries with generated text where possible + * + * If neither EDIT_NEW nor EDIT_UPDATE is specified, the status of the article will be detected. + * If EDIT_UPDATE is specified and the article doesn't exist, the function will return an + * edit-gone-missing error. If EDIT_NEW is specified and the article does exist, an + * edit-already-exists error will be returned. These two conditions are also possible with + * auto-detection due to MediaWiki's performance-optimised locking strategy. + * + * @param $baseRevId the revision ID this edit was based off, if any + * @param $user User the user doing the edit + * @param $serialisation_format String: format for storing the content in the database + * + * @return Status object. Possible errors: + * edit-hook-aborted: The ArticleSave hook aborted the edit but didn't set the fatal flag of $status + * edit-gone-missing: In update mode, but the article didn't exist + * edit-conflict: In update mode, the article changed unexpectedly + * edit-no-change: Warning that the text was the same as before + * edit-already-exists: In creation mode, but the article already exists + * + * Extensions may define additional errors. + * + * $return->value will contain an associative array with members as follows: + * new: Boolean indicating if the function attempted to create a new article + * revision: The revision object for the inserted revision, or null + * + * Compatibility note: this function previously returned a boolean value indicating success/failure + */ + public function doEditContent( Content $content, $summary, $flags = 0, $baseRevId = false, + User $user = null, $serialisation_format = null ) { #FIXME: use this global $wgUser, $wgDBtransactions, $wgUseAutomaticEditSummaries; # Low-level sanity check @@ -1250,10 +1306,25 @@ class WikiPage extends Page { $flags = $this->checkFlags( $flags ); - if ( !wfRunHooks( 'ArticleSave', array( &$this, &$user, &$text, &$summary, - $flags & EDIT_MINOR, null, null, &$flags, &$status ) ) ) - { - wfDebug( __METHOD__ . ": ArticleSave hook aborted save!\n" ); + # call legacy hook + $hook_ok = wfRunHooks( 'ArticleContentSave', array( &$this, &$user, &$content, &$summary, #FIXME: document new hook! + $flags & EDIT_MINOR, null, null, &$flags, &$status ) ); + + if ( $hook_ok && !empty( $wgHooks['ArticleSave'] ) ) { # avoid serialization overhead if the hook isn't present + $content_text = $content->serialize(); + $txt = $content_text; # clone + + $hook_ok = wfRunHooks( 'ArticleSave', array( &$this, &$user, &$txt, &$summary, #FIXME: deprecate legacy hook! + $flags & EDIT_MINOR, null, null, &$flags, &$status ) ); + + if ( $txt !== $content_text ) { + # if the text changed, unserialize the new version to create an updated Content object. + $content = $content->getContentHandler()->unserialize( $txt ); + } + } + + if ( !$hook_ok ) { + wfDebug( __METHOD__ . ": ArticleSave or ArticleSaveContent hook aborted save!\n" ); if ( $status->isOK() ) { $status->fatal( 'edit-hook-aborted' ); @@ -1267,20 +1338,24 @@ class WikiPage extends Page { $isminor = ( $flags & EDIT_MINOR ) && $user->isAllowed( 'minoredit' ); $bot = $flags & EDIT_FORCE_BOT; - $oldtext = $this->getNativeData(); // current revision #FIXME: may not be a string. check Content model! - $oldsize = strlen( $oldtext ); + $old_content = $this->getContent( Revision::RAW ); // current revision's content + + $oldsize = $old_content ? $old_content->getSize() : 0; $oldid = $this->getLatest(); $oldIsRedirect = $this->isRedirect(); $oldcountable = $this->isCountable(); + $handler = $content->getContentHandler(); + # Provide autosummaries if one is not provided and autosummaries are enabled. if ( $wgUseAutomaticEditSummaries && $flags & EDIT_AUTOSUMMARY && $summary == '' ) { - $summary = self::getAutosummary( $oldtext, $text, $flags ); #FIXME: ContentHandler::getAutosummary() + $summary = $handler->getAutosummary( $old_content, $content, $flags ); } - $editInfo = $this->prepareTextForEdit( $text, null, $user ); - $text = $editInfo->pst; - $newsize = strlen( $text ); + $editInfo = $this->prepareContentForEdit( $content, null, $user, $serialisation_format ); + $serialized = $editInfo->pst; + $content = $editInfo->pstContent; + $newsize = $content->getSize(); $dbw = wfGetDB( DB_MASTER ); $now = wfTimestampNow(); @@ -1308,14 +1383,15 @@ class WikiPage extends Page { 'page' => $this->getId(), 'comment' => $summary, 'minor_edit' => $isminor, - 'text' => $text, #FIXME: set content instead, leave serialization to revision?! + 'text' => $serialized, + 'len' => $newsize, 'parent_id' => $oldid, 'user' => $user->getId(), 'user_text' => $user->getName(), 'timestamp' => $now ) ); - $changed = ( strcmp( $text, $oldtext ) != 0 ); + $changed = !$content->equals( $old_content ); if ( $changed ) { $dbw->begin(); @@ -1413,7 +1489,8 @@ class WikiPage extends Page { 'page' => $newid, 'comment' => $summary, 'minor_edit' => $isminor, - 'text' => $text, + 'text' => $serialized, + 'len' => $newsize, 'user' => $user->getId(), 'user_text' => $user->getName(), 'timestamp' => $now @@ -1434,7 +1511,7 @@ class WikiPage extends Page { $this->mTitle->getUserPermissionsErrors( 'autopatrol', $user ) ); # Add RC row to the DB $rc = RecentChange::notifyNew( $now, $this->mTitle, $isminor, $user, $summary, $bot, - '', strlen( $text ), $revisionId, $patrolled ); + '', $content->getSize(), $revisionId, $patrolled ); # Log auto-patrolled edits if ( $patrolled ) { @@ -1447,8 +1524,11 @@ class WikiPage extends Page { # Update links, etc. $this->doEditUpdates( $revision, $user, array( 'created' => true ) ); - wfRunHooks( 'ArticleInsertComplete', array( &$this, &$user, $text, $summary, + wfRunHooks( 'ArticleInsertComplete', array( &$this, &$user, $serialized, $summary, #FIXME: deprecate legacy hook $flags & EDIT_MINOR, null, null, &$flags, $revision ) ); + + wfRunHooks( 'ArticleContentInsertComplete', array( &$this, &$user, $content, $summary, #FIXME: document new hook + $flags & EDIT_MINOR, null, null, &$flags, $revision ) ); } # Do updates right now unless deferral was requested @@ -1459,9 +1539,12 @@ class WikiPage extends Page { // Return the new revision (or null) to the caller $status->value['revision'] = $revision; - wfRunHooks( 'ArticleSaveComplete', array( &$this, &$user, $text, $summary, + wfRunHooks( 'ArticleSaveComplete', array( &$this, &$user, $serialized, $summary, #FIXME: deprecate legacy hook $flags & EDIT_MINOR, null, null, &$flags, $revision, &$status, $baseRevId ) ); + wfRunHooks( 'ArticleContentSaveComplete', array( &$this, &$user, $content, $summary, #FIXME: document new hook + $flags & EDIT_MINOR, null, null, &$flags, $revision, &$status, $baseRevId ) ); + # Promote user to any groups they meet the criteria for $user->addAutopromoteOnceGroups( 'onEdit' ); @@ -1489,14 +1572,35 @@ class WikiPage extends Page { /** * Prepare text which is about to be saved. * Returns a stdclass with source, pst and output members + * @deprecated in 1.20: use prepareContentForEdit instead. */ - public function prepareTextForEdit( $text, $revid = null, User $user = null ) { + public function prepareTextForEdit( $text, $revid = null, User $user = null ) { #FIXME: use prepareContentForEdit() instead #XXX: who uses this?! + #TODO: log use of deprecated function + $content = ContentHandler::makeContent( $text, $this->getTitle() ); + return $this->prepareContentForEdit( $content, $revid , $user ); + } + + /** + * Prepare content which is about to be saved. + * Returns a stdclass with source, pst and output members + * + * @param \Content $content + * @param null $revid + * @param null|\User $user + * @param null $serialization_format + * @return bool|object + */ + public function prepareContentForEdit( Content $content, $revid = null, User $user = null, $serialization_format = null ) { #FIXME: use this #XXX: really public?! global $wgParser, $wgContLang, $wgUser; $user = is_null( $user ) ? $wgUser : $user; // @TODO fixme: check $user->getId() here??? + if ( $this->mPreparedEdit - && $this->mPreparedEdit->newText == $text + && $this->mPreparedEdit->newContent + && $this->mPreparedEdit->newContent->equals( $content ) && $this->mPreparedEdit->revid == $revid + && $this->mPreparedEdit->format == $serialization_format + #XXX: also check $user here? ) { // Already prepared return $this->mPreparedEdit; @@ -1507,11 +1611,19 @@ class WikiPage extends Page { $edit = (object)array(); $edit->revid = $revid; - $edit->newText = $text; - $edit->pst = $wgParser->preSaveTransform( $text, $this->mTitle, $user, $popts ); + + $edit->pstContent = $content->preSaveTransform( $this->mTitle, $user, $popts ); + $edit->pst = $edit->pstContent->serialize( $serialization_format ); + $edit->format = $serialization_format; + $edit->popts = $this->makeParserOptions( 'canonical' ); - $edit->output = $wgParser->parse( $edit->pst, $this->mTitle, $edit->popts, true, true, $revid ); - $edit->oldText = $this->getRawText(); #FIXME: $oldcontent instead?! + $edit->output = $edit->pstContent->getParserOutput( $this->mTitle, $revid, $edit->popts ); + + $edit->newContent = $content; + $edit->oldContent = $this->getContent( Revision::RAW ); + + $edit->newText = ContentHandler::getContentText( $edit->newContent ); #FIXME: B/C only! don't use this field! + $edit->oldText = $edit->oldContent ? ContentHandler::getContentText( $edit->oldContent ) : ''; #FIXME: B/C only! don't use this field! $this->mPreparedEdit = $edit; @@ -1653,13 +1765,33 @@ class WikiPage extends Page { * @param $comment String: comment submitted * @param $minor Boolean: whereas it's a minor modification */ - public function doQuickEdit( $text, User $user, $comment = '', $minor = 0 ) { + public function doQuickEdit( $text, User $user, $comment = '', $minor = 0 ) { + #TODO: log use of deprecated function + $content = ContentHandler::makeContent( $text, $this->getTitle() ); + return $this->doQuickEdit( $content, $user, $comment , $minor ); + } + + /** + * Edit an article without doing all that other stuff + * The article must already exist; link tables etc + * are not updated, caches are not flushed. + * + * @param $content Content: content submitted + * @param $user User The relevant user + * @param $comment String: comment submitted + * @param $serialisation_format String: format for storing the content in the database + * @param $minor Boolean: whereas it's a minor modification + */ + public function doQuickEditContent( Content $content, User $user, $comment = '', $minor = 0, $serialisation_format = null ) { wfProfileIn( __METHOD__ ); + $serialized = $content->serialize( $serialisation_format ); + $dbw = wfGetDB( DB_MASTER ); $revision = new Revision( array( 'page' => $this->getId(), - 'text' => $text, + 'text' => $serialized, + 'length' => $content->getSize(), 'comment' => $comment, 'minor_edit' => $minor ? 1 : 0, ) ); -- 2.20.1