Merge "Track key authentication metrics"
[lhc/web/wiklou.git] / includes / MovePage.php
index fdece8d..9891106 100644 (file)
@@ -42,6 +42,183 @@ class MovePage {
                $this->newTitle = $newTitle;
        }
 
+       public function checkPermissions( User $user, $reason ) {
+               $status = new Status();
+
+               $errors = wfMergeErrorArrays(
+                       $this->oldTitle->getUserPermissionsErrors( 'move', $user ),
+                       $this->oldTitle->getUserPermissionsErrors( 'edit', $user ),
+                       $this->newTitle->getUserPermissionsErrors( 'move-target', $user ),
+                       $this->newTitle->getUserPermissionsErrors( 'edit', $user )
+               );
+
+               // Convert into a Status object
+               if ( $errors ) {
+                       foreach ( $errors as $error ) {
+                               call_user_func_array( array( $status, 'fatal' ), $error );
+                       }
+               }
+
+               if ( EditPage::matchSummarySpamRegex( $reason ) !== false ) {
+                       // This is kind of lame, won't display nice
+                       $status->fatal( 'spamprotectiontext' );
+               }
+
+               $tp = $this->newTitle->getTitleProtection();
+               if ( $tp !== false && !$user->isAllowed( $tp['permission'] ) ) {
+                               $status->fatal( 'cantmove-titleprotected' );
+               }
+
+               Hooks::run( 'MovePageCheckPermissions',
+                       array( $this->oldTitle, $this->newTitle, $user, $reason, $status )
+               );
+
+               return $status;
+       }
+
+       /**
+        * Does various sanity checks that the move is
+        * valid. Only things based on the two titles
+        * should be checked here.
+        *
+        * @return Status
+        */
+       public function isValidMove() {
+               global $wgContentHandlerUseDB;
+               $status = new Status();
+
+               if ( $this->oldTitle->equals( $this->newTitle ) ) {
+                       $status->fatal( 'selfmove' );
+               }
+               if ( !$this->oldTitle->isMovable() ) {
+                       $status->fatal( 'immobile-source-namespace', $this->oldTitle->getNsText() );
+               }
+               if ( $this->newTitle->isExternal() ) {
+                       $status->fatal( 'immobile-target-namespace-iw' );
+               }
+               if ( !$this->newTitle->isMovable() ) {
+                       $status->fatal( 'immobile-target-namespace', $this->newTitle->getNsText() );
+               }
+
+               $oldid = $this->oldTitle->getArticleID();
+
+               if ( strlen( $this->newTitle->getDBkey() ) < 1 ) {
+                       $status->fatal( 'articleexists' );
+               }
+               if (
+                       ( $this->oldTitle->getDBkey() == '' ) ||
+                       ( !$oldid ) ||
+                       ( $this->newTitle->getDBkey() == '' )
+               ) {
+                       $status->fatal( 'badarticleerror' );
+               }
+
+               # The move is allowed only if (1) the target doesn't exist, or
+               # (2) the target is a redirect to the source, and has no history
+               # (so we can undo bad moves right after they're done).
+               if ( $this->newTitle->getArticleID() && !$this->isValidMoveTarget() ) {
+                       $status->fatal( 'articleexists' );
+               }
+
+               // Content model checks
+               if ( !$wgContentHandlerUseDB &&
+                       $this->oldTitle->getContentModel() !== $this->newTitle->getContentModel() ) {
+                       // can't move a page if that would change the page's content model
+                       $status->fatal(
+                               'bad-target-model',
+                               ContentHandler::getLocalizedName( $this->oldTitle->getContentModel() ),
+                               ContentHandler::getLocalizedName( $this->newTitle->getContentModel() )
+                       );
+               }
+
+               // Image-specific checks
+               if ( $this->oldTitle->inNamespace( NS_FILE ) ) {
+                       $status->merge( $this->isValidFileMove() );
+               }
+
+               if ( $this->newTitle->inNamespace( NS_FILE ) && !$this->oldTitle->inNamespace( NS_FILE ) ) {
+                       $status->fatal( 'nonfile-cannot-move-to-file' );
+               }
+
+               // Hook for extensions to say a title can't be moved for technical reasons
+               Hooks::run( 'MovePageIsValidMove', array( $this->oldTitle, $this->newTitle, $status ) );
+
+               return $status;
+       }
+
+       /**
+        * Sanity checks for when a file is being moved
+        *
+        * @return Status
+        */
+       protected function isValidFileMove() {
+               $status = new Status();
+               $file = wfLocalFile( $this->oldTitle );
+               $file->load( File::READ_LATEST );
+               if ( $file->exists() ) {
+                       if ( $this->newTitle->getText() != wfStripIllegalFilenameChars( $this->newTitle->getText() ) ) {
+                               $status->fatal( 'imageinvalidfilename' );
+                       }
+                       if ( !File::checkExtensionCompatibility( $file, $this->newTitle->getDBkey() ) ) {
+                               $status->fatal( 'imagetypemismatch' );
+                       }
+               }
+
+               if ( !$this->newTitle->inNamespace( NS_FILE ) ) {
+                       $status->fatal( 'imagenocrossnamespace' );
+               }
+
+               return $status;
+       }
+
+       /**
+        * Checks if $this can be moved to a given Title
+        * - Selects for update, so don't call it unless you mean business
+        *
+        * @since 1.25
+        * @return bool
+        */
+       protected function isValidMoveTarget() {
+               # Is it an existing file?
+               if ( $this->newTitle->inNamespace( NS_FILE ) ) {
+                       $file = wfLocalFile( $this->newTitle );
+                       $file->load( File::READ_LATEST );
+                       if ( $file->exists() ) {
+                               wfDebug( __METHOD__ . ": file exists\n" );
+                               return false;
+                       }
+               }
+               # Is it a redirect with no history?
+               if ( !$this->newTitle->isSingleRevRedirect() ) {
+                       wfDebug( __METHOD__ . ": not a one-rev redirect\n" );
+                       return false;
+               }
+               # Get the article text
+               $rev = Revision::newFromTitle( $this->newTitle, false, Revision::READ_LATEST );
+               if ( !is_object( $rev ) ) {
+                       return false;
+               }
+               $content = $rev->getContent();
+               # Does the redirect point to the source?
+               # Or is it a broken self-redirect, usually caused by namespace collisions?
+               $redirTitle = $content ? $content->getRedirectTarget() : null;
+
+               if ( $redirTitle ) {
+                       if ( $redirTitle->getPrefixedDBkey() !== $this->oldTitle->getPrefixedDBkey() &&
+                               $redirTitle->getPrefixedDBkey() !== $this->newTitle->getPrefixedDBkey() ) {
+                               wfDebug( __METHOD__ . ": redirect points to other page\n" );
+                               return false;
+                       } else {
+                               return true;
+                       }
+               } else {
+                       # Fail safe (not a redirect after all. strange.)
+                       wfDebug( __METHOD__ . ": failsafe: database says " . $this->newTitle->getPrefixedDBkey() .
+                               " is a redirect, but it doesn't contain a valid redirect.\n" );
+                       return false;
+               }
+       }
+
        /**
         * @param User $user
         * @param string $reason
@@ -51,11 +228,14 @@ class MovePage {
        public function move( User $user, $reason, $createRedirect ) {
                global $wgCategoryCollation;
 
+               Hooks::run( 'TitleMove', array( $this->oldTitle, $this->newTitle, $user ) );
+
                // If it is a file, move it first.
                // It is done before all other moving stuff is done because it's hard to revert.
                $dbw = wfGetDB( DB_MASTER );
                if ( $this->oldTitle->getNamespace() == NS_FILE ) {
                        $file = wfLocalFile( $this->oldTitle );
+                       $file->load( File::READ_LATEST );
                        if ( $file->exists() ) {
                                $status = $file->move( $this->newTitle );
                                if ( !$status->isOk() ) {
@@ -188,9 +368,11 @@ class MovePage {
 
                $dbw->commit( __METHOD__ );
 
-               wfRunHooks( 'TitleMoveComplete', array( &$this->oldTitle, &$this->newTitle, &$user, $pageid, $redirid, $reason ) );
+               Hooks::run(
+                       'TitleMoveComplete',
+                       array( &$this->oldTitle, &$this->newTitle, &$user, $pageid, $redirid, $reason )
+               );
                return Status::newGood();
-
        }
 
        /**
@@ -258,6 +440,9 @@ class MovePage {
 
                $dbw = wfGetDB( DB_MASTER );
 
+               $oldpage = WikiPage::factory( $this->oldTitle );
+               $oldcountable = $oldpage->isCountable();
+
                $newpage = WikiPage::factory( $nt );
 
                if ( $moveOverRedirect ) {
@@ -302,10 +487,11 @@ class MovePage {
 
                $newpage->updateRevisionOn( $dbw, $nullRevision );
 
-               wfRunHooks( 'NewRevisionFromEditComplete',
+               Hooks::run( 'NewRevisionFromEditComplete',
                        array( $newpage, $nullRevision, $nullRevision->getParentId(), $user ) );
 
-               $newpage->doEditUpdates( $nullRevision, $user, array( 'changed' => false ) );
+               $newpage->doEditUpdates( $nullRevision, $user,
+                       array( 'changed' => false, 'moved' => true, 'oldcountable' => $oldcountable ) );
 
                if ( !$moveOverRedirect ) {
                        WikiPage::onArticleCreate( $nt );
@@ -328,7 +514,7 @@ class MovePage {
                                $redirectRevision->insertOn( $dbw );
                                $redirectArticle->updateRevisionOn( $dbw, $redirectRevision, 0 );
 
-                               wfRunHooks( 'NewRevisionFromEditComplete',
+                               Hooks::run( 'NewRevisionFromEditComplete',
                                        array( $redirectArticle, $redirectRevision, false, $user ) );
 
                                $redirectArticle->doEditUpdates( $redirectRevision, $user, array( 'created' => true ) );
@@ -339,5 +525,4 @@ class MovePage {
                $logid = $logEntry->insert();
                $logEntry->publish( $logid );
        }
-
-}
\ No newline at end of file
+}