add Last-Modified header
[lhc/web/wiklou.git] / includes / Title.php
index b5f2a6e..87d7e96 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 # See title.doc
 
-/* private static */ $title_interwiki_cache = array();
+$wgTitleInterwikiCache = array();
 
 # Title class
 # 
@@ -55,25 +55,15 @@ class Title {
        # From text, such as what you would find in a link
        /* static */ function newFromText( $text, $defaultNamespace = 0 )
        {       
-               static $trans;
                $fname = "Title::newFromText";
                wfProfileIn( $fname );
-               
-               # Note - mixing latin1 named entities and unicode numbered
-               # ones will result in a bad link.
-               if( !isset( $trans ) ) {
-                       global $wgInputEncoding;
-                       $trans = array_flip( get_html_translation_table( HTML_ENTITIES ) );
-                       if( strcasecmp( "utf-8", $wgInputEncoding ) == 0 ) {
-                           $trans = array_map( "utf8_encode", $trans );
-                       }
-               }
 
                if( is_object( $text ) ) {
                        wfDebugDieBacktrace( "Called with object instead of string." );
                }
-               $text = strtr( $text, $trans );
-               
+               global $wgInputEncoding;
+               $text = do_html_entity_decode( $text, ENT_COMPAT, $wgInputEncoding );
+
                $text = wfMungeToUtf8( $text );
                
                
@@ -82,9 +72,12 @@ class Title {
 
                $t = new Title();
                $t->mDbkeyform = str_replace( " ", "_", $text );
-        $t->mDefaultNamespace = $defaultNamespace;
-        
+               $t->mDefaultNamespace = $defaultNamespace;
+
                wfProfileOut( $fname );
+               if ( !is_object( $t ) ) {
+                       var_dump( debug_backtrace() );
+               }
                if( $t->secureAndSplit() ) {
                        return $t;
                } else {
@@ -95,13 +88,13 @@ class Title {
        # From a URL-encoded title
        /* static */ function newFromURL( $url )
        {
-               global $wgLang, $wgServer;
+               global $wgLang, $wgServer, $wgIsMySQL, $wgIsPg;
                $t = new Title();
-               $s = urldecode( $url ); # This is technically wrong, as anything
-                                                               # we've gotten is already decoded by PHP.
-                                                               # Kept for backwards compatibility with
-                                                               # buggy URLs we had for a while...
-               $s = $url;
+               
+               # For compatibility with old buggy URLs. "+" is not valid in titles,
+               # but some URLs used it as a space replacement and they still come
+               # from some external search tools.
+               $s = str_replace( "+", " ", $url );
                
                # For links that came from outside, check for alternate/legacy
                # character encoding.
@@ -116,14 +109,20 @@ class Title {
                
                $t->mDbkeyform = str_replace( " ", "_", $s );
                if( $t->secureAndSplit() ) {
-
                        # check that lenght of title is < cur_title size
-                       $sql = "SHOW COLUMNS FROM cur LIKE \"cur_title\";";
-                       $cur_title_object = wfFetchObject(wfQuery( $sql, DB_READ ));
+                       if ($wgIsMySQL) {
+                               $sql = "SHOW COLUMNS FROM cur LIKE \"cur_title\";";
+                               $cur_title_object = wfFetchObject(wfQuery( $sql, DB_READ ));
 
-                       preg_match( "/\((.*)\)/", $cur_title_object->Type, $cur_title_size);
+                               preg_match( "/\((.*)\)/", $cur_title_object->Type, $cur_title_type);
+                               $cur_title_size=$cur_title_type[1];
+                       } else {
+                               /* midom:FIXME pg_field_type does not return varchar length
+                                  assume 255 */
+                               $cur_title_size=255;
+                       }
 
-                       if (strlen($t->mDbkeyform) > $cur_title_size[1] ) {
+                       if (strlen($t->mDbkeyform) > $cur_title_size ) {
                                return NULL;
                        }
 
@@ -189,11 +188,20 @@ class Title {
                # Missing characters:
                #  * []|# Needed for link syntax
                #  * % and + are corrupted by Apache when they appear in the path
-               # 
+               #
+               # % seems to work though
+               #
+               # The problem with % is that URLs are double-unescaped: once by Apache's 
+               # path conversion code, and again by PHP. So %253F, for example, becomes "?".
+               # Our code does not double-escape to compensate for this, indeed double escaping
+               # would break if the double-escaped title was passed in the query string
+               # rather than the path. This is a minor security issue because articles can be
+               # created such that they are hard to view or edit. -- TS
+               #
                # Theoretically 0x80-0x9F of ISO 8859-1 should be disallowed, but
                # this breaks interlanguage links
                
-               $set = " !\"$&'()*,\\-.\\/0-9:;<=>?@A-Z\\\\^_`a-z{}~\\x80-\\xFF";
+               $set = " %!\"$&'()*,\\-.\\/0-9:;=?@A-Z\\\\^_`a-z{}~\\x80-\\xFF";
                return $set;
        }
        
@@ -235,21 +243,21 @@ class Title {
        # The URL contains $1, which is replaced by the title
        function getInterwikiLink( $key )
        {       
-               global $wgMemc, $wgDBname;
-               static $title_interwiki_cache = array();
+               global $wgMemc, $wgDBname, $wgInterwikiExpiry, $wgTitleInterwikiCache;
 
                $k = "$wgDBname:interwiki:$key";
 
-               if( array_key_exists( $k, $title_interwiki_cache ) )
-                       return $title_interwiki_cache[$k]->iw_url;
+               if( array_key_exists( $k, $wgTitleInterwikiCache ) )
+                       return $wgTitleInterwikiCache[$k]->iw_url;
 
                $s = $wgMemc->get( $k ); 
-               if( $s ) { 
-                       $title_interwiki_cache[$k] = $s;
+               # Ignore old keys with no iw_local
+               if( $s && isset( $s->iw_local ) ) { 
+                       $wgTitleInterwikiCache[$k] = $s;
                        return $s->iw_url;
                }
                $dkey = wfStrencode( $key );
-               $query = "SELECT iw_url FROM interwiki WHERE iw_prefix='$dkey'";
+               $query = "SELECT iw_url,iw_local FROM interwiki WHERE iw_prefix='$dkey'";
                $res = wfQuery( $query, DB_READ, "Title::getInterwikiLink" );
                if(!$res) return "";
                
@@ -258,11 +266,24 @@ class Title {
                        $s = (object)false;
                        $s->iw_url = "";
                }
-               $wgMemc->set( $k, $s );
-               $title_interwiki_cache[$k] = $s;
+               $wgMemc->set( $k, $s, $wgInterwikiExpiry );
+               $wgTitleInterwikiCache[$k] = $s;
                return $s->iw_url;
        }
-       
+
+       function isLocal() {
+               global $wgTitleInterwikiCache, $wgDBname;
+
+               if ( $this->mInterwiki != "" ) {
+                       # Make sure key is loaded into cache
+                       $this->getInterwikiLink( $this->mInterwiki );
+                       $k = "$wgDBname:interwiki:" . $this->mInterwiki;
+                       return (bool)($wgTitleInterwikiCache[$k]->iw_local);
+               } else {
+                       return true;
+               }
+       }
+
        # Update the cur_touched field for an array of title objects
        # Inefficient unless the IDs are already loaded into the link cache
        /* static */ function touchArray( $titles, $timestamp = "" ) {
@@ -362,9 +383,6 @@ class Title {
                $n = $wgLang->getNsText( $this->mNamespace );
                if ( "" != $n ) { $n .= ":"; }
                $u = str_replace( "$1", $n . $this->mUrlform, $p );
-               if ( "" != $this->mFragment ) {
-                       $u .= "#" . wfUrlencode( $this->mFragment );
-               }
                return $u;
        }
 
@@ -480,11 +498,24 @@ class Title {
        function userCanEdit()
        {
                global $wgUser;
-
                if ( -1 == $this->mNamespace ) { return false; }
+               if ( NS_MEDIAWIKI == $this->mNamespace && !$wgUser->isSysop() ) { return false; }
                # if ( 0 == $this->getArticleID() ) { return false; }
                if ( $this->mDbkeyform == "_" ) { return false; }
-
+               # protect global styles and js
+               if ( NS_MEDIAWIKI == $this->mNamespace 
+                    && preg_match("/\\.(css|js)$/", $this->mTextform )
+                    && !$wgUser->isSysop() )
+               { return false; }
+               //if ( $this->isCssJsSubpage() and !$this->userCanEditCssJsSubpage() ) { return false; }
+               # 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
+               if( Namespace::getUser() == $this->mNamespace
+                       and preg_match("/\\.(css|js)$/", $this->mTextform )
+                       and !$wgUser->isSysop()
+                       and !preg_match("/^".preg_quote($wgUser->getName(), '/')."/", $this->mTextform) )
+               { return false; }
                $ur = $wgUser->getRights();
                foreach ( $this->getRestrictions() as $r ) {
                        if ( "" != $r && ( ! in_array( $r, $ur ) ) ) {
@@ -493,6 +524,39 @@ class Title {
                }
                return true;
        }
+       
+       function userCanRead() {
+               global $wgUser;
+               global $wgWhitelistRead;
+               
+               if( 0 != $wgUser->getID() ) return true;
+               if( !is_array( $wgWhitelistRead ) ) return true;
+               
+               $name = $this->getPrefixedText();
+               if( in_array( $name, $wgWhitelistRead ) ) return true;
+               
+               # Compatibility with old settings
+               if( $this->getNamespace() == NS_MAIN ) {
+                       if( in_array( ":" . $name, $wgWhitelistRead ) ) return true;
+               }
+               return false;
+       }
+       
+       function isCssJsSubpage() {
+               return ( Namespace::getUser() == $this->mNamespace and preg_match("/\\.(css|js)$/", $this->mTextform ) );
+       }
+       function isCssSubpage() {
+               return ( Namespace::getUser() == $this->mNamespace and preg_match("/\\.css$/", $this->mTextform ) );
+       }
+       function isJsSubpage() {
+               return ( Namespace::getUser() == $this->mNamespace and preg_match("/\\.js$/", $this->mTextform ) );
+       }
+       function userCanEditCssJsSubpage() {
+               # protect css/js subpages of user pages
+               # XXX: this might be better using restrictions
+               global $wgUser;
+               return ( $wgUser->isSysop() or preg_match("/^".preg_quote($wgUser->getName())."/", $this->mTextform) );
+       }
 
        # Accessor/initialisation for mRestrictions
        function getRestrictions()
@@ -580,7 +644,7 @@ class Title {
        #
        /* private */ function secureAndSplit()
        {
-               global $wgLang, $wgLocalInterwiki;
+               global $wgLang, $wgLocalInterwiki, $wgCapitalLinks;
                $fname = "Title::secureAndSplit";
                wfProfileIn( $fname );
                
@@ -590,6 +654,7 @@ class Title {
                # Initialisation
                if ( $imgpre === false ) {
                        $imgpre = ":" . $wgLang->getNsText( Namespace::getImage() ) . ":";
+                       # % is needed as well
                        $rxTc = "/[^" . Title::legalChars() . "]/";
                }
 
@@ -599,13 +664,8 @@ class Title {
                # Clean up whitespace
                #
                $t = preg_replace( "/[\\s_]+/", "_", $this->mDbkeyform );
-               if ( "_" == @$t{0} ) { 
-                       $t = substr( $t, 1 ); 
-               }
-               $l = strlen( $t );
-               if ( $l && ( "_" == $t{$l-1} ) ) { 
-                       $t = substr( $t, 0, $l-1 ); 
-               }
+               $t = preg_replace( '/^_*(.*?)_*$/', '$1', $t );
+
                if ( "" == $t ) {
                        wfProfileOut( $fname );
                        return false;
@@ -625,10 +685,15 @@ class Title {
                        $this->mNamespace = NS_MAIN;
                } else {
                        # Namespace or interwiki prefix
-                       if ( preg_match( "/^((?:i|x|[a-z]{2,3})(?:-[a-z0-9]+)?|[A-Za-z0-9_\\x80-\\xff]+?)_*:_*(.*)$/", $t, $m ) ) {
+                       if ( preg_match( "/^(.+?)_*:_*(.*)$/", $t, $m ) ) {
                                #$p = strtolower( $m[1] );
                                $p = $m[1];
-                               if ( $ns = $wgLang->getNsIndex( strtolower( $p ) )) {
+                               $lowerNs = strtolower( $p );
+                               if ( $ns = Namespace::getCanonicalIndex( $lowerNs ) ) {
+                                       # Canonical namespace
+                                       $t = $m[2];
+                                       $this->mNamespace = $ns;
+                               } elseif ( $ns = $wgLang->getNsIndex( $lowerNs )) {
                                        # Ordinary namespace
                                        $t = $m[2];
                                        $this->mNamespace = $ns;
@@ -668,14 +733,21 @@ class Title {
                        return false;
                }
                
-               # "." and ".." conflict with the directories of those names
-               if ( $r === "." || $r === ".." ) {
+               # "." and ".." conflict with the directories of those namesa
+               if ( strpos( $r, "." ) !== false &&
+                    ( $r === "." || $r === ".." ||
+                      strpos( $r, "./" ) === 0 ||
+                      strpos( $r, "/./" !== false ) ||
+                      strpos( $r, "/../" !== false ) ) )
+               {
                        return false;
                }
 
                # Initial capital letter
-               if( $this->mInterwiki == "") $t = $wgLang->ucfirst( $r );
-
+               if( $wgCapitalLinks && $this->mInterwiki == "") {
+                       $t = $wgLang->ucfirst( $r );
+               }
+               
                # Fill fields
                $this->mDbkeyform = $t;
                $this->mUrlform = wfUrlencode( $t );
@@ -706,9 +778,10 @@ class Title {
                $retVal = array();
                if ( wfNumRows( $res ) ) {
                        while ( $row = wfFetchObject( $res ) ) {
-                               $titleObj = Title::makeTitle( $row->cur_namespace, $row->cur_title );
-                               $wgLinkCache->addGoodLink( $row->cur_id, $titleObj->getPrefixedDBkey() );
-                               $retVal[] = $titleObj;
+                               if ( $titleObj = Title::makeTitle( $row->cur_namespace, $row->cur_title ) ) {
+                                       $wgLinkCache->addGoodLink( $row->cur_id, $titleObj->getPrefixedDBkey() );
+                                       $retVal[] = $titleObj;
+                               }
                        }
                }
                wfFreeResult( $res );
@@ -727,7 +800,7 @@ class Title {
                if ( wfNumRows( $res ) ) {
                        while ( $row = wfFetchObject( $res ) ) {
                                $titleObj = Title::makeTitle( $row->cur_namespace, $row->cur_title );
-                               $wgLinkCache->addGoodLink( $titleObj->getPrefixedDBkey(), $row->cur_id );
+                               $wgLinkCache->addGoodLink( $row->cur_id, $titleObj->getPrefixedDBkey() );
                                $retVal[] = $titleObj;
                        }
                }
@@ -750,14 +823,14 @@ class Title {
        # Returns true on success, message name on failure
        # auth indicates whether wgUser's permissions should be checked
        function moveTo( &$nt, $auth = true ) {
-               $fname = "Title::move";
-               $oldid = $this->getArticleID();
-               $newid = $nt->getArticleID();
-
                if( !$this or !$nt ) {
                        return "badtitletext";
                }
 
+               $fname = "Title::move";
+               $oldid = $this->getArticleID();
+               $newid = $nt->getArticleID();
+
                if ( strlen( $nt->getDBkey() ) < 1 ) {
                        return "articleexists";
                }
@@ -877,7 +950,7 @@ class Title {
                        $fname
                );
                
-               RecentChange::notifyMove( $now, $this, $nt, $wgUser, $comment );
+               RecentChange::notifyMoveOverRedirect( $now, $this, $nt, $wgUser, $comment );
 
                # Swap links
                
@@ -885,34 +958,51 @@ class Title {
                $linksToOld = $this->getLinksTo();
                $linksToNew = $nt->getLinksTo();
                
-               # Make function to convert Titles to IDs
-               $titleToID = create_function('$t', 'return $t->getArticleID();');
-
-               # Reassign links to old title
-               if ( count( $linksToOld ) ) {
-                       $sql = "UPDATE links SET l_to=$newid WHERE l_from IN (";
-                       $sql .= implode( ",", array_map( $titleToID, $linksToOld ) );
-                       $sql .= ")";
-                       wfQuery( $sql, DB_WRITE, $fname );
-               }
-               
-               # Reassign links to new title
-               if ( count( $linksToNew ) ) {
-                       $sql = "UPDATE links SET l_to=$oldid WHERE l_from IN (";
-                       $sql .= implode( ",", array_map( $titleToID, $linksToNew ) );
-                       $sql .= ")";
+               # Delete them all
+               $sql = "DELETE FROM links WHERE l_to=$oldid OR l_to=$newid";
+               wfQuery( $sql, DB_WRITE, $fname );
+
+               # Reinsert
+               if ( count( $linksToOld ) || count( $linksToNew )) {
+                       $sql = "INSERT INTO links (l_from,l_to) VALUES ";
+                       $first = true;
+
+                       # Insert links to old title
+                       foreach ( $linksToOld as $linkTitle ) {
+                               if ( $first ) {
+                                       $first = false;
+                               } else {
+                                       $sql .= ",";
+                               }
+                               $id = $linkTitle->getArticleID();
+                               $sql .= "($id,$newid)";
+                       }
+
+                       # Insert links to new title
+                       foreach ( $linksToNew as $linkTitle ) {
+                               if ( $first ) {
+                                       $first = false;
+                               } else {
+                                       $sql .= ",";
+                               }
+                               $id = $linkTitle->getArticleID();
+                               $sql .= "($id, $oldid)";
+                       }
+
                        wfQuery( $sql, DB_WRITE, $fname );
                }
 
-               # Note: the insert below must be after the updates above!
-
                # Now, we record the link from the redirect to the new title.
                # It should have no other outgoing links...
                $sql = "DELETE FROM links WHERE l_from={$newid}";
                wfQuery( $sql, DB_WRITE, $fname );
                $sql = "INSERT INTO links (l_from,l_to) VALUES ({$newid},{$oldid})";
                wfQuery( $sql, DB_WRITE, $fname );
-
+               
+               # Clear linkscc
+               LinkCache::linksccClearLinksTo( $oldid );
+               LinkCache::linksccClearLinksTo( $newid );
+               
                # Purge squid
                if ( $wgUseSquid ) {
                        $urls = array_merge( $nt->getSquidURLs(), $this->getSquidURLs() );
@@ -979,14 +1069,16 @@ class Title {
                        ), $fname
                );
                
-               # Miscellaneous updates
+               # Record in RC
+               RecentChange::notifyMoveToNew( $now, $this, $nt, $wgUser, $comment );
 
-               RecentChange::notifyMove( $now, $this, $nt, $wgUser, $comment );
+               # Purge squid and linkscc as per article creation
                Article::onArticleCreate( $nt );
 
                # Any text links to the old title must be reassigned to the redirect
                $sql = "UPDATE links SET l_to={$newid} WHERE l_to={$oldid}";
                wfQuery( $sql, DB_WRITE, $fname );
+               LinkCache::linksccClearLinksTo( $oldid );
 
                # Record the just-created redirect's linking to the page
                $sql = "INSERT INTO links (l_from,l_to) VALUES ({$newid},{$oldid})";
@@ -1089,5 +1181,85 @@ class Title {
                Article::onArticleCreate( $this );
                return true;
        }
+       
+       # Get categories to wich belong this title and return an array of
+       # categories names.
+       function getParentCategories( )
+       {
+               global $wgLang,$wgUser;
+               
+               #$titlekey = wfStrencode( $this->getArticleID() );
+               $titlekey = $this->getArticleId();
+               $cns = Namespace::getCategory();
+               $sk =& $wgUser->getSkin();
+               $parents = array();
+               
+               # get the parents categories of this title from the database
+               $sql = "SELECT DISTINCT cur_id FROM cur,categorylinks
+                       WHERE cl_from='$titlekey' AND cl_to=cur_title AND cur_namespace='$cns'
+                               ORDER BY cl_sortkey" ;
+               $res = wfQuery ( $sql, DB_READ ) ;
+               
+               if(wfNumRows($res) > 0) {
+                       while ( $x = wfFetchObject ( $res ) ) $data[] = $x ;
+                       wfFreeResult ( $res ) ;
+               } else {
+                       $data = '';
+               }
+               return $data;
+       }
+       
+       # will get the parents and grand-parents
+       # TODO : not sure what's happening when a loop happen like:
+       #       Encyclopedia > Astronomy > Encyclopedia
+       function getAllParentCategories(&$stack)
+       {
+               global $wgUser,$wgLang;
+               $result = '';
+               
+               # getting parents
+               $parents = $this->getParentCategories( );
+
+               if($parents == '')
+               {
+                       # The current element has no more parent so we dump the stack
+                       # and make a clean line of categories
+                       $sk =& $wgUser->getSkin() ;
+
+                       foreach ( array_reverse($stack) as $child => $parent )
+                       {
+                               # make a link of that parent
+                               $result .= $sk->makeLink($wgLang->getNSText ( Namespace::getCategory() ).":".$parent,$parent);
+                               $result .= ' &gt; ';
+                               $lastchild = $child;
+                       }
+                       # append the last child.
+                       # TODO : We should have a last child unless there is an error in the
+                       # "categorylinks" table.
+                       if(isset($lastchild)) { $result .= $lastchild; }
+                       
+                       $result .= "<br/>\n";
+                       
+                       # now we can empty the stack
+                       $stack = array();
+                       
+               } else {
+                       # look at parents of current category
+                       foreach($parents as $parent)
+                       {
+                               # create a title object for the parent
+                               $tpar = Title::newFromID($parent->cur_id);
+                               # add it to the stack
+                               $stack[$this->getText()] = $tpar->getText();
+                               # grab its parents
+                               $result .= $tpar->getAllParentCategories($stack);
+                       }
+               }
+
+               if(isset($result)) { return $result; }
+               else { return ''; };
+       }
+       
+       
 }
 ?>