Redirect-related bugfixes/features:
[lhc/web/wiklou.git] / includes / Title.php
index 95b2e51..eb83b43 100644 (file)
@@ -295,10 +295,20 @@ class Title {
         * Extract a redirect destination from a string and return the
         * Title, or null if the text doesn't contain a valid redirect
         *
-        * @param $text \type{String} Text with possible redirect
+        * @param $text \type{\string} Text with possible redirect
+        * @param $getAllTargets \type{\bool} Should we get an array of every target or just the final one?
+        * @param $noRecurse \type{\bool} This will prevent any and all recursion, only getting the very next target
+        *      This is mainly meant for Article::insertRedirect so that the redirect table still works properly
+        *      (makes double redirect and broken redirect reports display accurate results).
         * @return \type{Title} The corresponding Title
-        */
-       public static function newFromRedirect( $text ) {
+        * @return \type{\array} Array of redirect targets (Title objects), with the destination being last
+        */
+       public static function newFromRedirect( $text, $getAllTargets = false, $noRecurse = false ) {
+               global $wgMaxRedirects;
+               // are redirects disabled?
+               // Note that we should get a Title object if possible if $noRecurse is true so that the redirect table functions properly
+               if( !$noRecurse && $wgMaxRedirects < 1 )
+                       return null;
                $redir = MagicWord::get( 'redirect' );
                $text = trim($text);
                if( $redir->matchStartAndRemove( $text ) ) {
@@ -316,11 +326,36 @@ class Title {
                                        $m[1] = urldecode( ltrim( $m[1], ':' ) );
                                }
                                $title = Title::newFromText( $m[1] );
-                               // Redirects to some special pages are not permitted
-                               if( $title instanceof Title 
-                                               && !$title->isSpecial( 'Userlogout' )
-                                               && !$title->isSpecial( 'Filepath' ) ) 
-                               {
+                               // If the initial title is a redirect to bad special pages or is invalid, quit early
+                               if( !$title instanceof Title || !$title->isValidRedirectTarget() ) {
+                                       return null;
+                               }
+                               // If $noRecurse is true, simply return here
+                               if( $noRecurse ) {
+                                       return $title;
+                               }
+                               // recursive check to follow double redirects
+                               $recurse = $wgMaxRedirects;
+                               $targets = array();
+                               while( --$recurse >= 0 ) {
+                                       // Redirects to some special pages are not permitted
+                                       if( $title instanceof Title && $title->isValidRedirectTarget() ) {
+                                               $targets[] = $title;
+                                               if( $title->isRedirect() ) {
+                                                       $article = new Article( $title, 0 );
+                                                       $title = $article->getRedirectTarget();
+                                               } else {
+                                                       break;
+                                               }
+                                       } else {
+                                               break;
+                                       }
+                               }
+                               if( $getAllTargets ) {
+                                       // return every target or null if no targets due to invalid title or whatever
+                                       return ( $targets === array() ) ? null : $targets;
+                               } else {
+                                       // return the final destination -- invalid titles are checked earlier
                                        return $title;
                                }
                        }
@@ -452,8 +487,12 @@ class Title {
         */
        static function escapeFragmentForURL( $fragment ) {
                global $wgEnforceHtmlIds;
+               # Note that we don't urlencode the fragment.  urlencoded Unicode
+               # fragments appear not to work in IE (at least up to 7) or in at least
+               # one version of Opera 9.x.  The W3C validator, for one, doesn't seem
+               # to care if they aren't encoded.
                return Sanitizer::escapeId( $fragment,
-                       $wgEnforceHtmlIds ? array() : 'xml' );
+                       $wgEnforceHtmlIds ? 'noninitial' : 'xml' );
        }
 
 #----------------------------------------------------------------------------
@@ -1113,9 +1152,7 @@ class Title {
                        else if( $result === false )
                                $errors[] = array('badaccess-group0'); # a generic "We don't want them to do that"
                }
-               if( $doExpensiveQueries && !wfRunHooks( 'getUserPermissionsErrorsExpensive', 
-                       array(&$this,&$user,$action,&$result) ) )
-               {
+               if( $doExpensiveQueries && !wfRunHooks( 'getUserPermissionsErrorsExpensive', array(&$this,&$user,$action,&$result) ) ) {
                        if( is_array($result) && count($result) && !is_array($result[0]) )
                                $errors[] = $result; # A single array representing an error
                        else if( is_array($result) && is_array($result[0]) )
@@ -1141,8 +1178,7 @@ class Title {
 
                # protect css/js subpages of user pages
                # XXX: this might be better using restrictions
-               # XXX: Find a way to work around the php bug that prevents using 
-               # $this->userCanEditCssJsSubpage() from working
+               # XXX: Find a way to work around the php bug that prevents using $this->userCanEditCssJsSubpage() from working
                if( $this->isCssJsSubpage() && !$user->isAllowed('editusercssjs')
                        && !preg_match('/^'.preg_quote($user->getName(), '/').'\//', $this->mTextform) )
                {
@@ -1164,41 +1200,39 @@ class Title {
                                        $right = ( $right == 'sysop' ) ? 'protect' : $right;
                                        if( '' != $right && !$user->isAllowed( $right ) ) {
                                                $pages = '';
-                                               foreach( $cascadingSources as $page ) {
+                                               foreach( $cascadingSources as $page )
                                                        $pages .= '* [[:' . $page->getPrefixedText() . "]]\n";
-                                               }
                                                $errors[] = array( 'cascadeprotected', count( $cascadingSources ), $pages );
                                        }
                                }
                        }
                }
 
-               # Get restrictions on each action, 'create' handled below
-               if( $action != 'create' ) {
-                       foreach( $this->getRestrictions($action) as $right ) {
-                               // Backwards compatibility, rewrite sysop -> protect
-                               if( $right == 'sysop' ) {
-                                       $right = 'protect';
-                               }
-                               if( '' != $right && !$user->isAllowed( $right ) ) {
-                                       // Users with 'editprotected' permission can edit protected pages
-                                       if( $action=='edit' && $user->isAllowed( 'editprotected' ) ) {
-                                               // Users with 'editprotected' permission cannot edit protected pages
-                                               // with cascading option turned on.
-                                               if( $this->mCascadeRestriction ) {
-                                                       $errors[] = array( 'protectedpagetext', $right );
-                                               } else {
-                                                       // Nothing, user can edit!
-                                               }
-                                       } else {
+               foreach( $this->getRestrictions($action) as $right ) {
+                       // Backwards compatibility, rewrite sysop -> protect
+                       if( $right == 'sysop' ) {
+                               $right = 'protect';
+                       }
+                       if( '' != $right && !$user->isAllowed( $right ) ) {
+                               // Users with 'editprotected' permission can edit protected pages
+                               if( $action=='edit' && $user->isAllowed( 'editprotected' ) ) {
+                                       // Users with 'editprotected' permission cannot edit protected pages
+                                       // with cascading option turned on.
+                                       if( $this->mCascadeRestriction ) {
                                                $errors[] = array( 'protectedpagetext', $right );
+                                       } else {
+                                               // Nothing, user can edit!
                                        }
+                               } else {
+                                       $errors[] = array( 'protectedpagetext', $right );
                                }
                        }
                }
 
-               if( $action == 'protect' && $this->getUserPermissionsErrors('edit',$user) != array() ) {
-                       $errors[] = array( 'protect-cantedit' ); // If they can't edit, they shouldn't protect.
+               if( $action == 'protect' ) {
+                       if( $this->getUserPermissionsErrors('edit', $user) != array() ) {
+                               $errors[] = array( 'protect-cantedit' ); // If they can't edit, they shouldn't protect.
+                       }
                }
 
                if( $action == 'create' ) {
@@ -2065,19 +2099,22 @@ class Title {
 
                # Namespace or interwiki prefix
                $firstPass = true;
+               $prefixRegexp = "/^(.+?)_*:_*(.*)$/S";
                do {
                        $m = array();
-                       if ( preg_match( "/^(.+?)_*:_*(.*)$/S", $dbkey, $m ) ) {
+                       if ( preg_match( $prefixRegexp, $dbkey, $m ) ) {
                                $p = $m[1];
-                               if ( $ns = $wgContLang->getNsIndex( $p )) {
+                               if ( $ns = $wgContLang->getNsIndex( $p ) ) {
                                        # Ordinary namespace
                                        $dbkey = $m[2];
-                                       # Disallow Talk:File:x type titles...
-                                       if( $this->mNamespace == NS_TALK && $ns > 0 )
-                                               return false; // bug 5280 title issues
                                        $this->mNamespace = $ns;
-                                       if( $ns == NS_TALK && $firstPass )
-                                               continue; # Do another namespace split...
+                                       # For Talk:X pages, check if X has a "namespace" prefix
+                                       if( $ns == NS_TALK && preg_match( $prefixRegexp, $dbkey, $x ) ) {
+                                               if( $wgContLang->getNsIndex( $x[1] ) )
+                                                       return false; # Disallow Talk:File:x type titles...
+                                               else if( Interwiki::isValidInterwiki( $x[1] ) )
+                                                       return false; # Disallow Talk:Interwiki:x type titles...
+                                       }
                                } elseif( Interwiki::isValidInterwiki( $p ) ) {
                                        if( !$firstPass ) {
                                                # Can't make a local interwiki link to an interwiki link.
@@ -2565,8 +2602,8 @@ class Title {
                        );
                        # Update the protection log
                        $log = new LogPage( 'protect' );
-                       $comment = wfMsgForContent('prot_1movedto2',$this->getPrefixedText(), $nt->getPrefixedText() );
-                       if( $reason ) $comment .= ': ' . $reason;
+                       $comment = wfMsgForContent( 'prot_1movedto2', $this->getPrefixedText(), $nt->getPrefixedText() );
+                       if( $reason ) $comment .= wfMsgForContent( 'colon-separator' ) . $reason;
                        $log->addEntry( 'move_prot', $nt, $comment, array($this->getPrefixedText()) ); // FIXME: $params?
                }
 
@@ -3039,6 +3076,28 @@ class Title {
                );
        }
        
+       /**
+        * Get the first revision of the page
+        *
+        * @param $flags \type{\int} GAID_FOR_UPDATE
+        * @return Revision (or NULL if page doesn't exist)
+        */
+       public function getFirstRevision( $flags=0 ) {
+               $db = ($flags & GAID_FOR_UPDATE) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
+               $pageId = $this->getArticleId($flags);
+               if( !$pageId ) return NULL;
+               $row = $db->selectRow( 'revision', '*',
+                       array( 'rev_page' => $pageId ),
+                       __METHOD__,
+                       array( 'ORDER BY' => 'rev_timestamp ASC', 'LIMIT' => 1 )
+               );
+               if( !$row ) {
+                       return NULL;
+               } else {
+                       return new Revision( $row );
+               }
+       }
+       
        /**
         * Check if this is a new page
         *
@@ -3402,4 +3461,26 @@ class Title {
                }
                return $redirs;
        }
+       
+       /**
+        * Check if this Title is a valid redirect target
+        *
+        * @return \type{\bool} TRUE or FALSE
+        */
+       public function isValidRedirectTarget() {
+               global $wgInvalidRedirectTargets;
+               
+               // invalid redirect targets are stored in a global array, but explicity disallow Userlogout here
+               if( $this->isSpecial( 'Userlogout' ) ) {
+                       return false;
+               }
+               
+               foreach( $wgInvalidRedirectTargets as $target ) {
+                       if( $this->isSpecial( $target ) ) {
+                               return false;
+                       }
+               }
+               
+               return true;
+       }
 }