Redirect-related bugfixes/features:
authorRyan Schmidt <skizzerz@users.mediawiki.org>
Wed, 21 Jan 2009 20:42:32 +0000 (20:42 +0000)
committerRyan Schmidt <skizzerz@users.mediawiki.org>
Wed, 21 Jan 2009 20:42:32 +0000 (20:42 +0000)
* (bug 11644) Add $wgMaxRedirects variable to control how many redirects are recursed through until the "destination" page is reached.
** update redirect page UI to show each step down to the destination page
** rdfrom text still links to original page visited
** pages still show up in DoubleRedirects
** setting to 1 (default) is current behavior of only going 1 step down. Setting to 0 disables automatic redirects.
** arrow image needs to be smoothed, couldn't figure out how to do it myself while keeping the image in indexed mode
* (bug 10569) Redirects to Special:Mypage and Special:Mytalk are no longer allowed to prevent redirect loops
** can be re-enabled by changing the $wgInvalidRedirectTargets array

RELEASE-NOTES
includes/Article.php
includes/DefaultSettings.php
includes/EditPage.php
includes/Title.php
skins/common/images/nextredirectltr.png [new file with mode: 0644]
skins/common/images/nextredirectrtl.png [new file with mode: 0644]

index d729f25..9a5b927 100644 (file)
@@ -48,7 +48,10 @@ it from source control: http://www.mediawiki.org/wiki/Download_from_SVN
 * (bugs 16957/16969) Add show/hide to preferences for RC patrol options on
   specialpages
 * (bug 11443) Auto-noindex user/user talk pages for blocked user
-
+* (bug 11644) Add $wgMaxRedirects variable to control how many redirects are recursed
+  through until the "destination" page is reached.
+* Add $wgInvalidRedirectTargets variable to prevent redirects to certain special pages.
+  
 === Bug fixes in 1.15 ===
 * (bug 16968) Special:Upload no longer throws useless warnings.
 * (bug 17000) Special:RevisionDelete now checks if the database is locked before
@@ -66,6 +69,8 @@ it from source control: http://www.mediawiki.org/wiki/Download_from_SVN
 * (bug 11527) Diff on page with one revision shows "Next" link to same diff
 * (bug 15936) New page's patrol button should always be visible
 * (bug 8065) Fix summary forcing for new pages
+* (bug 10569) redirects to Special:Mypage and Special:Mytalk are no longer allowed
+  by default. Change $wgInvalidRedirectTargets to re-enable.
 
 == API changes in 1.15 ==
 * (bug 16858) Revamped list=deletedrevs to make listing deleted contributions
index 524c5c3..6b2c497 100644 (file)
@@ -103,7 +103,8 @@ class Article {
         * @return Title object
         */
        public function insertRedirect() {
-               $retval = Title::newFromRedirect( $this->getContent() );
+               // set noRecurse so that we always get an entry even if redirects are "disabled"
+               $retval = Title::newFromRedirect( $this->getContent(), false, true );
                if( !$retval ) {
                        return null;
                }
@@ -135,7 +136,7 @@ class Article {
         * @return mixed false, Title of in-wiki target, or string with URL
         */
        public function followRedirectText( $text ) {
-               $rt = Title::newFromRedirect( $text );
+               $rt = Title::newFromRedirect( $text ); // only get the final target
                # process if title object is valid and not special:userlogout
                if( $rt ) {
                        if( $rt->getInterwiki() != '' ) {
@@ -584,9 +585,10 @@ class Article {
                        }
                        // Apparently loadPageData was never called
                        $this->loadContent();
-                       $titleObj = Title::newFromRedirect( $this->fetchContent() );
+                       // Only get the next target to reduce load times
+                       $titleObj = Title::newFromRedirect( $this->fetchContent(), false, true );
                } else {
-                       $titleObj = Title::newFromRedirect( $text );
+                       $titleObj = Title::newFromRedirect( $text, false, true );
                }
                return $titleObj !== NULL;
        }
@@ -919,7 +921,7 @@ class Article {
                                        $wgOut->addHTML( htmlspecialchars( $this->mContent ) );
                                        $wgOut->addHTML( "\n</pre>\n" );
                                }
-                       } else if( $rt = Title::newFromRedirect( $text ) ) {
+                       } else if( $rt = Title::newFromRedirect( $text, true ) ) { # get an array of redirect targets
                                # Don't append the subtitle if this was an old revision
                                $wgOut->addHTML( $this->viewRedirect( $rt, !$wasRedirected && $this->isCurrent() ) );
                                $parseout = $wgParser->parse($text, $this->mTitle, ParserOptions::newFromUser($wgUser));
@@ -1039,24 +1041,41 @@ class Article {
 
        /**
         * View redirect
-        * @param $target Title object of destination to redirect
+        * @param $target Title object or Array of destination(s) to redirect
         * @param $appendSubtitle Boolean [optional]
         * @param $forceKnown Boolean: should the image be shown as a bluelink regardless of existence?
         */
        public function viewRedirect( $target, $appendSubtitle = true, $forceKnown = false ) {
                global $wgParser, $wgOut, $wgContLang, $wgStylePath, $wgUser;
                # Display redirect
+               if( !is_array( $target ) ) {
+                       $target = array( $target );
+               }
                $imageDir = $wgContLang->isRTL() ? 'rtl' : 'ltr';
-               $imageUrl = $wgStylePath.'/common/images/redirect' . $imageDir . '.png';
-
+               $imageUrl = $wgStylePath . '/common/images/redirect' . $imageDir . '.png';
+               $imageUrl2 = $wgStylePath . '/common/images/nextredirect' . $imageDir . '.png';
+               $alt2 = $wgContLang->isRTL() ? '&larr;' : '&rarr;'; // should -> and <- be used instead of entities?
+               
                if( $appendSubtitle ) {
                        $wgOut->appendSubtitle( wfMsgHtml( 'redirectpagesub' ) );
                }
                $sk = $wgUser->getSkin();
+               // the loop prepends the arrow image before the link, so the first case needs to be outside
+               $title = array_shift( $target );
                if( $forceKnown ) {
-                       $link = $sk->makeKnownLinkObj( $target, htmlspecialchars( $target->getFullText() ) );
+                       $link = $sk->makeKnownLinkObj( $title, htmlspecialchars( $title->getFullText() ) );
                } else {
-                       $link = $sk->makeLinkObj( $target, htmlspecialchars( $target->getFullText() ) );
+                       $link = $sk->makeLinkObj( $title, htmlspecialchars( $title->getFullText() ) );
+               }
+               // automatically append redirect=no to each link, since most of them are redirect pages themselves
+               foreach( $target as $rt ) {
+                       if( $forceKnown ) {
+                               $link .= '<img src="'.$imageUrl2.'" alt="'.$alt2.' " />'
+                                       . $sk->makeKnownLinkObj( $rt, htmlspecialchars( $rt->getFullText() ) );
+                       } else {
+                               $link .= '<img src="'.$imageUrl2.'" alt="'.$alt2.' " />'
+                                       . $sk->makeLinkObj( $rt, htmlspecialchars( $rt->getFullText() ) );
+                       }
                }
                return '<img src="'.$imageUrl.'" alt="#REDIRECT " />' .
                        '<span class="redirectText">'.$link.'</span>';
@@ -3428,9 +3447,9 @@ class Article {
        public static function getAutosummary( $oldtext, $newtext, $flags ) {
                # Decide what kind of autosummary is needed.
 
-               # Redirect autosummaries
-               $ot = Title::newFromRedirect( $oldtext );
-               $rt = Title::newFromRedirect( $newtext );
+               # Redirect autosummaries -- should only get the next target and not recurse
+               $ot = Title::newFromRedirect( $oldtext, false, true );
+               $rt = Title::newFromRedirect( $newtext, false, true );
                if( is_object( $rt ) && ( !is_object( $ot ) || !$rt->equals( $ot ) || $ot->getFragment() != $rt->getFragment() ) ) {
                        return wfMsgForContent( 'autoredircomment', $rt->getFullText() );
                }
index b21a61e..ab60b06 100644 (file)
@@ -3611,6 +3611,25 @@ $wgMaximumMovedPages = 100;
  */
 $wgFixDoubleRedirects = false;
 
+/**
+ * Max number of redirects to follow when resolving redirects.
+ * 1 means only the first redirect is followed (default behavior).
+ * 0 or less means no redirects are followed.
+ */
+$wgMaxRedirects = 1;
+
+/**
+ * Array of invalid page redirect targets.
+ * Attempting to create a redirect to any of the pages in this array
+ * will make the redirect fail.
+ * Userlogout is hard-coded, so it does not need to be listed here.
+ * (bug 10569) Disallow Mypage and Mytalk as well.
+ *
+ * As of now, this only checks special pages. Redirects to pages in
+ * other namespaces cannot be invalidated by this variable.
+ */
+$wgInvalidRedirectTargets = array( 'Filepath', 'Mypage', 'Mytalk' );
 /**
  * Array of namespaces to generate a sitemap for when the
  * maintenance/generateSitemap.php script is run, or false if one is to be ge-
index 90af9d2..946a596 100644 (file)
@@ -1687,7 +1687,7 @@ END
                        $parserOptions->setTidy(true);
                        $parserOutput = $wgParser->parse( $previewtext, $this->mTitle, $parserOptions );
                        $previewHTML = $parserOutput->mText;
-               } elseif ( $rt = Title::newFromRedirect( $this->textbox1 ) ) {
+               } elseif ( $rt = Title::newFromRedirect( $this->textbox1, true ) ) {
                        $previewHTML = $this->mArticle->viewRedirect( $rt, false );
                } else {
                        $toparse = $this->textbox1;
index 5aa1ffe..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;
                                }
                        }
@@ -3426,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;
+       }
 }
diff --git a/skins/common/images/nextredirectltr.png b/skins/common/images/nextredirectltr.png
new file mode 100644 (file)
index 0000000..7d60cdc
Binary files /dev/null and b/skins/common/images/nextredirectltr.png differ
diff --git a/skins/common/images/nextredirectrtl.png b/skins/common/images/nextredirectrtl.png
new file mode 100644 (file)
index 0000000..3d5b395
Binary files /dev/null and b/skins/common/images/nextredirectrtl.png differ