From: Brad Jorsch Date: Wed, 26 Jun 2013 20:48:53 +0000 (-0400) Subject: Separate RevDel logic from UI X-Git-Tag: 1.31.0-rc.0~18732^2 X-Git-Url: http://git.cyclocoop.org/%24image?a=commitdiff_plain;h=825c5e98aa8dc7e1ac76763f461e7640efa06752;p=lhc%2Fweb%2Fwiklou.git Separate RevDel logic from UI Move functions implementing the actual revision deletion out of SpecialRevisiondelete, so they can be used from elsewhere (e.g. ApiRevisiondelete). Change-Id: Id0f6b1c8dab474bdfd802c07b211885d398c94e5 --- diff --git a/RELEASE-NOTES-1.22 b/RELEASE-NOTES-1.22 index d0c9d1d870..34e474ad88 100644 --- a/RELEASE-NOTES-1.22 +++ b/RELEASE-NOTES-1.22 @@ -213,6 +213,7 @@ production. against allowed proxy lists. * Add deferrable update support for callback/closure * Add TitleMove hook before page renames +* Revision deletion backend code is moved out of SpecialRevisiondelete === Bug fixes in 1.22 === * Disable Special:PasswordReset when $wgEnableEmail is false. Previously one diff --git a/includes/revisiondelete/RevisionDelete.php b/includes/revisiondelete/RevisionDelete.php index 191286d10d..718508777b 100644 --- a/includes/revisiondelete/RevisionDelete.php +++ b/includes/revisiondelete/RevisionDelete.php @@ -41,6 +41,19 @@ class RevDel_RevisionList extends RevDel_List { return 'rev_id'; } + public static function getRestriction() { + return 'deleterevision'; + } + + public static function getRevdelConstant() { + return Revision::DELETED_TEXT; + } + + public static function suggestTarget( $target, array $ids ) { + $rev = Revision::newFromId( $ids[0] ); + return $rev ? $rev->getTitle() : $target; + } + /** * @param $db DatabaseBase * @return mixed @@ -441,6 +454,14 @@ class RevDel_FileList extends RevDel_List { return 'oi_archive_name'; } + public static function getRestriction() { + return 'deleterevision'; + } + + public static function getRevdelConstant() { + return File::DELETED_FILE; + } + var $storeBatch, $deleteBatch, $cleanupBatch; /** @@ -802,6 +823,28 @@ class RevDel_LogList extends RevDel_List { return 'log_id'; } + public static function getRestriction() { + return 'deletelogentry'; + } + + public static function getRevdelConstant() { + return LogPage::DELETED_ACTION; + } + + public static function suggestTarget( $target, array $ids ) { + $result = wfGetDB( DB_SLAVE )->select( 'logging', + 'log_type', + array( 'log_id' => $ids ), + __METHOD__, + array( 'DISTINCT' ) + ); + if ( $result->numRows() == 1 ) { + // If there's only one type, the target can be set to include it. + return SpecialPage::getTitleFor( 'Log', $result->current()->log_type ); + } + return SpecialPage::getTitleFor( 'Log' ); + } + /** * @param $db DatabaseBase * @return mixed diff --git a/includes/revisiondelete/RevisionDeleteAbstracts.php b/includes/revisiondelete/RevisionDeleteAbstracts.php index d822d0913b..803467e69e 100644 --- a/includes/revisiondelete/RevisionDeleteAbstracts.php +++ b/includes/revisiondelete/RevisionDeleteAbstracts.php @@ -37,12 +37,44 @@ abstract class RevDel_List extends RevisionListBase { * Get the DB field name associated with the ID list. * This used to populate the log_search table for finding log entries. * Override this function. - * @return null + * @return string|null */ public static function getRelationType() { return null; } + /** + * Get the user right required for this list type + * Override this function. + * @since 1.22 + * @return string|null + */ + public static function getRestriction() { + return null; + } + + /** + * Get the revision deletion constant for this list type + * Override this function. + * @since 1.22 + * @return int|null + */ + public static function getRevdelConstant() { + return null; + } + + /** + * Suggest a target for the revision deletion + * Optionally override this function. + * @since 1.22 + * @param Title|null $target User-supplied target + * @param array $ids + * @return Title|null + */ + public static function suggestTarget( $target, array $ids ) { + return $target; + } + /** * Set the visibility for the revisions in this list. Logging and * transactions are done here. @@ -72,7 +104,7 @@ abstract class RevDel_List extends RevisionListBase { $oldBits = $item->getBits(); // Build the actual new rev_deleted bitfield - $newBits = SpecialRevisionDelete::extractBitfield( $bitPars, $oldBits ); + $newBits = RevisionDeleter::extractBitfield( $bitPars, $oldBits ); if ( $oldBits == $newBits ) { $status->warning( 'revdelete-no-change', $item->formatDate(), $item->formatTime() ); diff --git a/includes/revisiondelete/RevisionDeleter.php b/includes/revisiondelete/RevisionDeleter.php index 2de19acf39..dbcb3d7dda 100644 --- a/includes/revisiondelete/RevisionDeleter.php +++ b/includes/revisiondelete/RevisionDeleter.php @@ -22,11 +22,71 @@ */ /** - * Temporary b/c interface, collection of static functions. - * @ingroup SpecialPage + * General controller for RevDel, used by both SpecialRevisiondelete and + * ApiRevisionDelete. * @ingroup RevisionDelete */ class RevisionDeleter { + /** List of known revdel types, with their corresponding list classes */ + private static $allowedTypes = array( + 'revision' => 'RevDel_RevisionList', + 'archive' => 'RevDel_ArchiveList', + 'oldimage' => 'RevDel_FileList', + 'filearchive' => 'RevDel_ArchivedFileList', + 'logging' => 'RevDel_LogList', + ); + + /** Type map to support old log entries */ + private static $deprecatedTypeMap = array( + 'oldid' => 'revision', + 'artimestamp' => 'archive', + 'oldimage' => 'oldimage', + 'fileid' => 'filearchive', + 'logid' => 'logging', + ); + + /** + * Lists the valid possible types for revision deletion. + * + * @since 1.22 + * @return array + */ + public static function getTypes() { + return array_keys( self::$allowedTypes ); + } + + /** + * Gets the canonical type name, if any. + * + * @since 1.22 + * @param string $typeName + * @return string|null + */ + public static function getCanonicalTypeName( $typeName ) { + if ( isset( self::$deprecatedTypeMap[$typeName] ) ) { + $typeName = self::$deprecatedTypeMap[$typeName]; + } + return isset( self::$allowedTypes[$typeName] ) ? $typeName : null; + } + + /** + * Instantiate the appropriate list class for a given list of IDs. + * + * @since 1.22 + * @param string $typeName RevDel type, see RevisionDeleter::getTypes() + * @param IContextSource $context + * @param Title $title + * @param array $ids + * @return RevDel_List + */ + public static function createList( $typeName, IContextSource $context, Title $title, array $ids ) { + $typeName = self::getCanonicalTypeName( $typeName ); + if ( !$typeName ) { + throw new MWException( __METHOD__ . ": Unknown RevDel type '$typeName'" ); + } + return new self::$allowedTypes[$typeName]( $context, $title, $ids ); + } + /** * Checks for a change in the bitfield for a certain option and updates the * provided array accordingly. @@ -86,20 +146,62 @@ class RevisionDeleter { /** Get DB field name for URL param... * Future code for other things may also track * other types of revision-specific changes. + * @param string $typeName * @return string One of log_id/rev_id/fa_id/ar_timestamp/oi_archive_name */ public static function getRelationType( $typeName ) { - if ( isset( SpecialRevisionDelete::$deprecatedTypeMap[$typeName] ) ) { - $typeName = SpecialRevisionDelete::$deprecatedTypeMap[$typeName]; + $typeName = self::getCanonicalTypeName( $typeName ); + if ( !$typeName ) { + return null; + } + return call_user_func( array( self::$allowedTypes[$typeName], 'getRelationType' ) ); + } + + /** + * Get the user right required for the RevDel type + * @since 1.22 + * @param string $typeName + * @return string User right + */ + public static function getRestriction( $typeName ) { + $typeName = self::getCanonicalTypeName( $typeName ); + if ( !$typeName ) { + return null; } - if ( isset( SpecialRevisionDelete::$allowedTypes[$typeName] ) ) { - $class = SpecialRevisionDelete::$allowedTypes[$typeName]['list-class']; - return call_user_func( array( $class, 'getRelationType' ) ); - } else { + return call_user_func( array( self::$allowedTypes[$typeName], 'getRestriction' ) ); + } + + /** + * Get the revision deletion constant for the RevDel type + * @since 1.22 + * @param string $typeName + * @return int RevDel constant + */ + public static function getRevdelConstant( $typeName ) { + $typeName = self::getCanonicalTypeName( $typeName ); + if ( !$typeName ) { return null; } + return call_user_func( array( self::$allowedTypes[$typeName], 'getRevdelConstant' ) ); } + /** + * Suggest a target for the revision deletion + * @since 1.22 + * @param string $typeName + * @param Title|null $title User-supplied target + * @param array $ids + * @return Title|null + */ + public static function suggestTarget( $typeName, $target, array $ids ) { + $typeName = self::getCanonicalTypeName( $typeName ); + if ( !$typeName ) { + return $target; + } + return call_user_func( array( self::$allowedTypes[$typeName], 'suggestTarget' ), $target, $ids ); + } + + /** * Checks if a revision still exists in the revision table. * If it doesn't, returns the corresponding ar_timestamp field @@ -125,4 +227,24 @@ class RevisionDeleter { return $timestamp; } + + /** + * Put together a rev_deleted bitfield + * @since 1.22 + * @param array $bitPars extractBitParams() params + * @param int $oldfield current bitfield + * @return array + */ + public static function extractBitfield( $bitPars, $oldfield ) { + // Build the actual new rev_deleted bitfield + $newBits = 0; + foreach ( $bitPars as $const => $val ) { + if ( $val == 1 ) { + $newBits |= $const; // $const is the *_deleted const + } elseif ( $val == -1 ) { + $newBits |= ( $oldfield & $const ); // use existing + } + } + return $newBits; + } } diff --git a/includes/specials/SpecialRevisiondelete.php b/includes/specials/SpecialRevisiondelete.php index 1e9adf5767..825be6c484 100644 --- a/includes/specials/SpecialRevisiondelete.php +++ b/includes/specials/SpecialRevisiondelete.php @@ -49,68 +49,43 @@ class SpecialRevisionDelete extends UnlistedSpecialPage { /** Array of checkbox specs (message, name, deletion bits) */ var $checks; - /** Information about the current type */ - var $typeInfo; + /** UI Labels about the current type */ + var $typeLabels; /** The RevDel_List object, storing the list of items to be deleted/undeleted */ var $list; /** - * Assorted information about each type, needed by the special page. - * TODO Move some of this to the list class + * UI labels for each type. */ - static $allowedTypes = array( + static $UILabels = array( 'revision' => array( 'check-label' => 'revdelete-hide-text', - 'deletion-bits' => Revision::DELETED_TEXT, 'success' => 'revdelete-success', 'failure' => 'revdelete-failure', - 'list-class' => 'RevDel_RevisionList', - 'permission' => 'deleterevision', ), 'archive' => array( 'check-label' => 'revdelete-hide-text', - 'deletion-bits' => Revision::DELETED_TEXT, 'success' => 'revdelete-success', 'failure' => 'revdelete-failure', - 'list-class' => 'RevDel_ArchiveList', - 'permission' => 'deleterevision', ), 'oldimage' => array( 'check-label' => 'revdelete-hide-image', - 'deletion-bits' => File::DELETED_FILE, 'success' => 'revdelete-success', 'failure' => 'revdelete-failure', - 'list-class' => 'RevDel_FileList', - 'permission' => 'deleterevision', ), 'filearchive' => array( 'check-label' => 'revdelete-hide-image', - 'deletion-bits' => File::DELETED_FILE, 'success' => 'revdelete-success', 'failure' => 'revdelete-failure', - 'list-class' => 'RevDel_ArchivedFileList', - 'permission' => 'deleterevision', ), 'logging' => array( 'check-label' => 'revdelete-hide-name', - 'deletion-bits' => LogPage::DELETED_ACTION, 'success' => 'logdelete-success', 'failure' => 'logdelete-failure', - 'list-class' => 'RevDel_LogList', - 'permission' => 'deletelogentry', ), ); - /** Type map to support old log entries */ - static $deprecatedTypeMap = array( - 'oldid' => 'revision', - 'artimestamp' => 'archive', - 'oldimage' => 'oldimage', - 'fileid' => 'filearchive', - 'logid' => 'logging', - ); - public function __construct() { parent::__construct( 'Revisiondelete', 'deletedhistory' ); } @@ -147,19 +122,6 @@ class SpecialRevisionDelete extends UnlistedSpecialPage { } else { $this->typeName = $request->getVal( 'type' ); $this->targetObj = Title::newFromText( $request->getText( 'target' ) ); - if ( $this->targetObj && $this->targetObj->isSpecial( 'Log' ) && count( $this->ids ) !== 0 ) { - $result = wfGetDB( DB_SLAVE )->select( 'logging', - 'log_type', - array( 'log_id' => $this->ids ), - __METHOD__, - array( 'DISTINCT' ) - ); - - if ( $result->numRows() == 1 ) { - // If there's only one type, the target can be set to include it. - $this->targetObj = SpecialPage::getTitleFor( 'Log', $result->current()->log_type ); - } - } } # For reviewing deleted files... @@ -170,24 +132,17 @@ class SpecialRevisionDelete extends UnlistedSpecialPage { return; } - if ( isset( self::$deprecatedTypeMap[$this->typeName] ) ) { - $this->typeName = self::$deprecatedTypeMap[$this->typeName]; - } + $this->typeName = RevisionDeleter::getCanonicalTypeName( $this->typeName ); # No targets? - if ( !isset( self::$allowedTypes[$this->typeName] ) || count( $this->ids ) == 0 ) { + if ( !$this->typeName || count( $this->ids ) == 0 ) { throw new ErrorPageError( 'revdelete-nooldid-title', 'revdelete-nooldid-text' ); } - $this->typeInfo = self::$allowedTypes[$this->typeName]; - $this->mIsAllowed = $user->isAllowed( $this->typeInfo['permission'] ); - - # If we have revisions, get the title from the first one - # since they should all be from the same page. This allows - # for more flexibility with page moves... - if ( $this->typeName == 'revision' ) { - $rev = Revision::newFromId( $this->ids[0] ); - $this->targetObj = $rev ? $rev->getTitle() : $this->targetObj; - } + $this->typeLabels = self::$UILabels[$this->typeName]; + $this->mIsAllowed = $user->isAllowed( RevisionDeleter::getRestriction( $this->typeName ) ); + + # Allow the list type to adjust the passed target + $this->targetObj = RevisionDeleter::suggestTarget( $this->typeName, $this->targetObj, $this->ids ); $this->otherReason = $request->getVal( 'wpReason' ); # We need a target page! @@ -200,7 +155,9 @@ class SpecialRevisionDelete extends UnlistedSpecialPage { # Initialise checkboxes $this->checks = array( - array( $this->typeInfo['check-label'], 'wpHidePrimary', $this->typeInfo['deletion-bits'] ), + array( $this->typeLabels['check-label'], 'wpHidePrimary', + RevisionDeleter::getRevdelConstant( $this->typeName ) + ), array( 'revdelete-hide-comment', 'wpHideComment', Revision::DELETED_COMMENT ), array( 'revdelete-hide-user', 'wpHideUser', Revision::DELETED_USER ) ); @@ -343,8 +300,9 @@ class SpecialRevisionDelete extends UnlistedSpecialPage { */ protected function getList() { if ( is_null( $this->list ) ) { - $class = $this->typeInfo['list-class']; - $this->list = new $class( $this->getContext(), $this->targetObj, $this->ids ); + $this->list = RevisionDeleter::createList( + $this->typeName, $this->getContext(), $this->targetObj, $this->ids + ); } return $this->list; } @@ -561,7 +519,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage { */ protected function success() { $this->getOutput()->setPageTitle( $this->msg( 'actioncomplete' ) ); - $this->getOutput()->wrapWikiMsg( "\n$1\n", $this->typeInfo['success'] ); + $this->getOutput()->wrapWikiMsg( "\n$1\n", $this->typeLabels['success'] ); $this->list->reloadFromMaster(); $this->showForm(); } @@ -571,7 +529,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage { */ protected function failure( $status ) { $this->getOutput()->setPageTitle( $this->msg( 'actionfailed' ) ); - $this->getOutput()->addWikiText( $status->getWikiText( $this->typeInfo['failure'] ) ); + $this->getOutput()->addWikiText( $status->getWikiText( $this->typeLabels['failure'] ) ); $this->showForm(); } @@ -598,21 +556,13 @@ class SpecialRevisionDelete extends UnlistedSpecialPage { /** * Put together a rev_deleted bitfield + * @deprecated since 1.22, use RevisionDeleter::extractBitfield instead * @param array $bitPars extractBitParams() params * @param int $oldfield current bitfield * @return array */ public static function extractBitfield( $bitPars, $oldfield ) { - // Build the actual new rev_deleted bitfield - $newBits = 0; - foreach ( $bitPars as $const => $val ) { - if ( $val == 1 ) { - $newBits |= $const; // $const is the *_deleted const - } elseif ( $val == -1 ) { - $newBits |= ( $oldfield & $const ); // use existing - } - } - return $newBits; + return RevisionDeleter::extractBitfield( $bitPars, $oldfield ); } /**