If local language's magicwords list is incomplete, try fetching it from the English one
[lhc/web/wiklou.git] / includes / Article.php
index 29349d2..e26a8a4 100644 (file)
@@ -5,7 +5,7 @@
 # Note: edit user interface and cache support functions have been
 # moved to separate EditPage and CacheManager classes.
 
-include_once( "CacheManager.php" );
+require_once( "CacheManager.php" );
 
 class Article {
        /* private */ var $mContent, $mContentLoaded;
@@ -62,7 +62,7 @@ class Article {
        # not told otherwise, and so may cause a change to mTitle.
 
        # Return the text of this revision
-       function getContent( $noredir = false )
+       function getContent( $noredir )
        {
                global $wgRequest;
 
@@ -70,7 +70,7 @@ class Article {
                $action = $wgRequest->getText( 'action', 'view' );
                $section = $wgRequest->getText( 'section' );
 
-               $fname =  "Article::getContent"; 
+               $fname =  "Article::getContent";
                wfProfileIn( $fname );
 
                if ( 0 == $this->getID() ) {
@@ -82,34 +82,29 @@ class Article {
                        return wfMsg( "noarticletext" );
                } else {
                        $this->loadContent( $noredir );
-                                               
+
                        if(
                                # check if we're displaying a [[User talk:x.x.x.x]] anonymous talk page
                                ( $this->mTitle->getNamespace() == Namespace::getTalk( Namespace::getUser()) ) &&
                                  preg_match("/^\d{1,3}\.\d{1,3}.\d{1,3}\.\d{1,3}$/",$this->mTitle->getText()) &&
                                  $action=="view"
-                               ) 
+                               )
                                {
                                wfProfileOut( $fname );
                                return $this->mContent . "\n" .wfMsg("anontalkpagetext"); }
-                       else {                          
+                       else {
                                if($action=="edit") {
                                        if($section!="") {
-                                               if($section=="new") { 
+                                               if($section=="new") {
                                                        wfProfileOut( $fname );
-                                                       return ""; 
+                                                       return "";
                                                }
 
-                                               $secs=preg_split("/(^=+.*?=+|^<h[1-6].*?>.*?<\/h[1-6].*?>)/mi",
-                                                $this->mContent, -1,
-                                                PREG_SPLIT_DELIM_CAPTURE);
-                                               if($section==0) {
-                                                       wfProfileOut( $fname );
-                                                       return trim($secs[0]);
-                                               } else {
-                                                       wfProfileOut( $fname );
-                                                       return trim($secs[$section*2-1] . $secs[$section*2]);
-                                               }
+                                               # strip NOWIKI etc. to avoid confusion (true-parameter causes HTML
+                                               # comments to be stripped as well)
+                                               $rv=$this->getSection($this->mContent,$section);
+                                               wfProfileOut( $fname );
+                                               return $rv;
                                        }
                                }
                                wfProfileOut( $fname );
@@ -118,11 +113,75 @@ class Article {
                }
        }
        
+       # This function returns the text of a section, specified by a number ($section).
+       # A section is text under a heading like == Heading == or <h1>Heading</h1>, or 
+       # the first section before any such heading (section 0).
+       #
+       # If a section contains subsections, these are also returned.
+       #
+       function getSection($text,$section)  {
+               
+               # strip NOWIKI etc. to avoid confusion (true-parameter causes HTML
+               # comments to be stripped as well)
+               $striparray=array();
+               $parser=new Parser();
+               $parser->mOutputType=OT_WIKI;
+               $striptext=$parser->strip($text, $striparray, true);
+
+               # now that we can be sure that no pseudo-sections are in the source,
+               # split it up by section
+               $secs =
+                 preg_split(
+                 "/(^=+.*?=+|^<h[1-6].*?" . ">.*?<\/h[1-6].*?" . ">)/mi",
+                 $striptext, -1,
+                 PREG_SPLIT_DELIM_CAPTURE);
+               if($section==0) {
+                       $rv=$secs[0];
+               } else {
+                       $headline=$secs[$section*2-1];
+                       preg_match( "/^(=+).*?=+|^<h([1-6]).*?>.*?<\/h[1-6].*?>/mi",$headline,$matches);
+                       $hlevel=$matches[1];
+                       
+                       # translate wiki heading into level
+                       if(strpos($hlevel,"=")!==false) {
+                               $hlevel=strlen($hlevel);                        
+                       }
+                       
+                       $rv=$headline. $secs[$section*2];
+                       $count=$section+1;
+                       
+                       $break=false;
+                       while(!empty($secs[$count*2-1]) && !$break) {
+                       
+                               $subheadline=$secs[$count*2-1];
+                               preg_match( "/^(=+).*?=+|^<h([1-6]).*?>.*?<\/h[1-6].*?>/mi",$subheadline,$matches);
+                               $subhlevel=$matches[1];
+                               if(strpos($subhlevel,"=")!==false) {
+                                       $subhlevel=strlen($subhlevel);                                          
+                               }
+                               if($subhlevel > $hlevel) {
+                                       $rv.=$subheadline.$secs[$count*2];
+                               }
+                               if($subhlevel <= $hlevel) {
+                                       $break=true;
+                               }
+                               $count++;
+                                               
+                       }
+               }
+               # reinsert stripped tags
+               $rv=$parser->unstrip($rv,$striparray);
+               $rv=trim($rv);
+               return $rv;
+
+       }
+       
+
        # Load the revision (including cur_text) into this object
        function loadContent( $noredir = false )
        {
                global $wgOut, $wgMwRedir, $wgRequest;
-               
+
                # Query variables :P
                $oldid = $wgRequest->getVal( 'oldid' );
                $redirect = $wgRequest->getVal( 'redirect' );
@@ -131,12 +190,12 @@ class Article {
                $fname = "Article::loadContent";
                
                # Pre-fill content with error message so that if something       
-               # fails we'll have something telling us what we intended.        
+               # fails we'll have something telling us what we intended.
 
                $t = $this->mTitle->getPrefixedText();   
                if ( isset( $oldid ) ) {         
                        $oldid = IntVal( $oldid );       
-                       $t .= ",oldid={$oldid}";         
+                       $t .= ",oldid={$oldid}";
                }        
                if ( isset( $redirect ) ) {      
                        $redirect = ($redirect == "no") ? "no" : "yes";          
@@ -203,12 +262,20 @@ class Article {
                        $this->mTitle->mRestrictionsLoaded = true;
                        wfFreeResult( $res );
                } else { # oldid set, retrieve historical version
-                       $sql = "SELECT old_text,old_timestamp,old_user,old_flags FROM old " .
+                       $sql = "SELECT old_namespace,old_title,old_text,old_timestamp,old_user,old_flags FROM old " .
                          "WHERE old_id={$oldid}";
                        $res = wfQuery( $sql, DB_READ, $fname );
-                       if ( 0 == wfNumRows( $res ) ) { return; }
+                       if ( 0 == wfNumRows( $res ) ) {
+                               return;
+                       }
 
                        $s = wfFetchObject( $res );
+                       if( $this->mTitle->getNamespace() != $s->old_namespace ||
+                               $this->mTitle->getDBkey() != $s->old_title ) {
+                               $oldTitle = Title::makeTitle( $s->old_namesapce, $s->old_title );
+                               $this->mTitle = $oldTitle;
+                               $wgTitle = $oldTitle;
+                       }
                        $this->mContent = Article::getRevisionText( $s );
                        $this->mUser = $s->old_user;
                        $this->mCounter = 0;
@@ -380,6 +447,53 @@ class Article {
                return $this->mMinorEdit;
        }
 
+        function getContributors($limit = 0, $offset = 0)
+        {
+                $fname = "Article::getContributors";
+
+               # XXX: this is expensive; cache this info somewhere.
+               
+               $title = $this->mTitle;
+
+               $contribs = array();
+
+                $sql = "SELECT old.old_user, old.old_user_text, " .
+                       "  user.user_real_name, MAX(old.old_timestamp) as timestamp" .
+                       " FROM old, user " .
+                       " WHERE old.old_user = user.user_id " .
+                      " AND old.old_namespace = " . $title->getNamespace() .
+                       " AND old.old_title = \"" . $title->getDBkey() . "\"" .
+                       " AND old.old_user != 0 " .
+                       " AND old.old_user != " . $this->getUser() . 
+                       " GROUP BY old.old_user " . 
+                       " ORDER BY timestamp DESC ";
+
+                if ($limit > 0) {
+                        $sql .= " LIMIT $limit";
+                }
+
+               $res = wfQuery($sql, DB_READ, $fname);
+       
+               while ( $line = wfFetchObject( $res ) ) {
+                       $contribs[$line->old_user] = 
+                                array($line->old_user_text, $line->user_real_name);
+               }    
+
+                # Count anonymous users
+
+               $res = wfQuery("SELECT COUNT(*) AS cnt " .
+                              " FROM old " .
+                              " WHERE old_namespace = " . $title->getNamespace() .
+                              " AND old_title = '" . $title->getDBkey() . "'" .
+                               " AND old_user = 0 ", DB_READ, $fname);
+       
+               while ( $line = wfFetchObject( $res ) ) {
+                        $contribs[0] = array($line->cnt, 'Anonymous');
+               }    
+
+               return $contribs;
+       }
+    
        # This is the default action of the script: just view the page of
        # the given title.
 
@@ -420,10 +534,16 @@ class Article {
                        }
                }
 
-               $text = $this->getContent(); # May change mTitle
+               $text = $this->getContent( false ); # May change mTitle by following a redirect
+               
+               # Another whitelist check in case oldid or redirects are altering the title
+               if ( !$this->mTitle->userCanRead() ) {
+                       $wgOut->loginToUse();
+                       $wgOut->output();
+                       exit;
+               }
+               
                $wgOut->setPageTitle( $this->mTitle->getPrefixedText() );
-               $wgOut->setHTMLTitle( $this->mTitle->getPrefixedText() .
-                 " - " . wfMsg( "wikititlesuffix" ) );
 
                # We're looking at an old revision
 
@@ -440,8 +560,16 @@ class Article {
                }
 
                $wgLinkCache->preFill( $this->mTitle );
-               
-               if( $wgEnableParserCache && intval($wgUser->getOption( "stubthreshold" )) == 0 ){
+
+               # wrap user css and user js in pre and don't parse
+               # XXX: use $this->mTitle->usCssJsSubpage() when php is fixed/ a workaround is found
+               if ( 
+                       $this->mTitle->getNamespace() == Namespace::getUser() && 
+                       preg_match("/\\/[\\w]+\\.(css|js)$/", $this->mTitle->getDBkey())
+               ) {
+                       $wgOut->addWikiText( wfMsg('usercssjs'));
+                       $wgOut->addHTML( '<pre>'.htmlspecialchars($this->mContent)."\n</pre>" );
+               } else if( $wgEnableParserCache && intval($wgUser->getOption( "stubthreshold" )) == 0 && empty( $oldid ) ){
                        $wgOut->addWikiText( $text, true, $this );
                } else {
                        $wgOut->addWikiText( $text );
@@ -519,23 +647,79 @@ class Article {
        /* Side effects: loads last edit */
        function getTextOfLastEditWithSectionReplacedOrAdded($section, $text, $summary = ""){
                $this->loadLastEdit();
-               $oldtext = $this->getContent();
+               $oldtext = $this->getContent( true );           
                if ($section != "") {
                        if($section=="new") {
                                if($summary) $subject="== {$summary} ==\n\n";
                                $text=$oldtext."\n\n".$subject.$text;
                        } else {
-                               $secs=preg_split("/(^=+.*?=+|^<h[1-6].*?>.*?<\/h[1-6].*?>)/mi",
+
+                               # strip NOWIKI etc. to avoid confusion (true-parameter causes HTML
+                               # comments to be stripped as well)
+                               $striparray=array();
+                               $parser=new Parser();
+                               $parser->mOutputType=OT_WIKI;
+                               $oldtext=$parser->strip($oldtext, $striparray, true);
+
+                               # now that we can be sure that no pseudo-sections are in the source,
+                               # split it up
+                               # Unfortunately we can't simply do a preg_replace because that might
+                               # replace the wrong section, so we have to use the section counter instead
+                               $secs=preg_split("/(^=+.*?=+|^<h[1-6].*?" . ">.*?<\/h[1-6].*?" . ">)/mi",
                                  $oldtext,-1,PREG_SPLIT_DELIM_CAPTURE);
                                $secs[$section*2]=$text."\n\n"; // replace with edited
-                               if($section) { $secs[$section*2-1]=""; } // erase old headline
-                               $text=join("",$secs);           
+                               
+                               # section 0 is top (intro) section
+                               if($section!=0) { 
+                                       
+                                       # headline of old section - we need to go through this section
+                                       # to determine if there are any subsections that now need to
+                                       # be erased, as the mother section has been replaced with
+                                       # the text of all subsections.
+                                       $headline=$secs[$section*2-1];
+                                       preg_match( "/^(=+).*?=+|^<h([1-6]).*?>.*?<\/h[1-6].*?>/mi",$headline,$matches);
+                                       $hlevel=$matches[1];
+                                       
+                                       # determine headline level for wikimarkup headings
+                                       if(strpos($hlevel,"=")!==false) {
+                                               $hlevel=strlen($hlevel);                        
+                                       }
+                                       
+                                       $secs[$section*2-1]=""; // erase old headline
+                                       $count=$section+1;
+                                       $break=false;
+                                       while(!empty($secs[$count*2-1]) && !$break) {
+                       
+                                               $subheadline=$secs[$count*2-1];
+                                               preg_match(
+                                                "/^(=+).*?=+|^<h([1-6]).*?>.*?<\/h[1-6].*?>/mi",$subheadline,$matches);
+                                               $subhlevel=$matches[1];
+                                               if(strpos($subhlevel,"=")!==false) {
+                                                       $subhlevel=strlen($subhlevel);          
+                                               }
+                                               if($subhlevel > $hlevel) {
+                                                       // erase old subsections
+                                                       $secs[$count*2-1]="";
+                                                       $secs[$count*2]="";
+                                               }
+                                               if($subhlevel <= $hlevel) {
+                                                       $break=true;
+                                               }
+                                               $count++;
+                                               
+                                       }
+                                       
+                               }
+                               $text=join("",$secs);
+                               # reinsert the stuff that we stripped out earlier
+                               $text=$parser->unstrip($text,$striparray);      
                        }
+                                                               
                }
                return $text;
        }
 
-       function updateArticle( $text, $summary, $minor, $watchthis, $forceBot = false )
+       function updateArticle( $text, $summary, $minor, $watchthis, $forceBot = false, $sectionanchor = "" )
        {
                global $wgOut, $wgUser, $wgLinkCache;
                global $wgDBtransactions, $wgMwRedir;
@@ -640,14 +824,14 @@ class Article {
                        $u->doUpdate();
                }
 
-               $this->showArticle( $text, wfMsg( "updated" ) );
+               $this->showArticle( $text, wfMsg( "updated" ), $sectionanchor );
                return true;
        }
 
        # After we've either updated or inserted the article, update
        # the link tables and redirect to the new page.
 
-       function showArticle( $text, $subtitle )
+       function showArticle( $text, $subtitle , $sectionanchor = '' )
        {
                global $wgOut, $wgUser, $wgLinkCache;
                global $wgMwRedir;
@@ -666,7 +850,7 @@ class Article {
                        $r = "redirect=no";
                else
                        $r = "";
-               $wgOut->redirect( $this->mTitle->getFullURL( $r ) );
+               $wgOut->redirect( $this->mTitle->getFullURL( $r ).$sectionanchor );
        }
 
        # Add this page to my watchlist
@@ -714,7 +898,7 @@ class Article {
 
        function protect( $limit = "sysop" )
        {
-               global $wgUser, $wgOut;
+               global $wgUser, $wgOut, $wgRequest;
 
                if ( ! $wgUser->isSysop() ) {
                        $wgOut->sysopRequired();
@@ -726,20 +910,94 @@ class Article {
                }
                $id = $this->mTitle->getArticleID();
                if ( 0 == $id ) {
-                       $wgOut->fatalEror( wfMsg( "badarticleerror" ) );
+                       $wgOut->fatalError( wfMsg( "badarticleerror" ) );
                        return;
                }
+
+               $confirm = $wgRequest->getBool( 'wpConfirmProtect' ) && $wgRequest->wasPosted();
+               $reason = $wgRequest->getText( 'wpReasonProtect' );
+
+               if ( $confirm ) {
+
         $sql = "UPDATE cur SET cur_touched='" . wfTimestampNow() . "'," .
                        "cur_restrictions='{$limit}' WHERE cur_id={$id}";
                wfQuery( $sql, DB_WRITE, "Article::protect" );
 
                $log = new LogPage( wfMsg( "protectlogpage" ), wfMsg( "protectlogtext" ) );
                if ( $limit === "" ) {
-                       $log->addEntry( wfMsg( "unprotectedarticle", $this->mTitle->getPrefixedText() ), "" );          
+                               $log->addEntry( wfMsg( "unprotectedarticle", $this->mTitle->getPrefixedText() ), $reason );
                } else {
-                       $log->addEntry( wfMsg( "protectedarticle", $this->mTitle->getPrefixedText() ), "" );
+                               $log->addEntry( wfMsg( "protectedarticle", $this->mTitle->getPrefixedText() ), $reason );
                }
                $wgOut->redirect( $this->mTitle->getFullURL() );
+                       return;
+               } else {
+                       $reason = htmlspecialchars( wfMsg( "protectreason" ) );
+                       return $this->confirmProtect( "", $reason, $limit );
+               }
+       }
+
+               # Output protection confirmation dialog
+       function confirmProtect( $par, $reason, $limit = "sysop"  )
+       {
+               global $wgOut;
+
+               wfDebug( "Article::confirmProtect\n" );
+
+               $sub = htmlspecialchars( $this->mTitle->getPrefixedText() );
+               $wgOut->setRobotpolicy( "noindex,nofollow" );
+
+               $check = "";
+               $protcom = "";
+
+               if ( $limit === "" ) {
+                       $wgOut->setSubtitle( wfMsg( "unprotectsub", $sub ) );
+                       $wgOut->addWikiText( wfMsg( "confirmunprotecttext" ) );
+                       $check = htmlspecialchars( wfMsg( "confirmunprotect" ) );
+                       $protcom = htmlspecialchars( wfMsg( "unprotectcomment" ) );
+                       $formaction = $this->mTitle->escapeLocalURL( "action=unprotect" . $par );
+               } else {
+                       $wgOut->setSubtitle( wfMsg( "protectsub", $sub ) );
+                       $wgOut->addWikiText( wfMsg( "confirmprotecttext" ) );
+                       $check = htmlspecialchars( wfMsg( "confirmprotect" ) );
+                       $protcom = htmlspecialchars( wfMsg( "protectcomment" ) );
+                       $formaction = $this->mTitle->escapeLocalURL( "action=protect" . $par );
+               }
+
+               $confirm = htmlspecialchars( wfMsg( "confirm" ) );
+
+               $wgOut->addHTML( "
+<form id='protectconfirm' method='post' action=\"{$formaction}\">
+       <table border='0'>
+               <tr>
+                       <td align='right'>
+                               <label for='wpReasonProtect'>{$protcom}:</label>
+                       </td>
+                       <td align='left'>
+                               <input type='text' size='60' name='wpReasonProtect' id='wpReasonProtect' value=\"" . htmlspecialchars( $reason ) . "\" />
+                       </td>
+               </tr>
+               <tr>
+                       <td>&nbsp;</td>
+               </tr>
+               <tr>
+                       <td align='right'>
+                               <input type='checkbox' name='wpConfirmProtect' value='1' id='wpConfirmProtect' />
+                       </td>
+                       <td>
+                               <label for='wpConfirmProtect'>{$check}</label>
+                       </td>
+               </tr>
+               <tr>
+                       <td>&nbsp;</td>
+                       <td>
+                               <input type='submit' name='wpConfirmProtectB' value=\"{$confirm}\" />
+                       </td>
+               </tr>
+       </table>
+</form>\n" );
+
+               $wgOut->returnToMain( false );
        }
 
        function unprotect()
@@ -1024,6 +1282,9 @@ class Article {
                $sql = "DELETE FROM brokenlinks WHERE bl_from={$id}";
                wfQuery( $sql, DB_WRITE, $fname );
                
+               $sql = "DELETE FROM categorylinks WHERE cl_from={$id}";
+               wfQuery( $sql, DB_WRITE, $fname );
+               
                $log = new LogPage( wfMsg( "dellogpage" ), wfMsg( "dellogpagetext" ) );
                $art = $this->mTitle->getPrefixedText();
                $log->addEntry( wfMsg( "deletedarticle", $art ), $reason );
@@ -1043,7 +1304,7 @@ class Article {
                        return;
                }
                if ( wfReadOnly() ) {
-                       $wgOut->readOnlyPage( $this->getContent() );
+                       $wgOut->readOnlyPage( $this->getContent( true ) );
                        return;
                }
                
@@ -1251,6 +1512,10 @@ class Article {
                        return false;
                }
        }
+
+       function getTouched() {
+               return $this->mTouched;
+       }
        
        /* static */ function incViewCount( $id )
        {
@@ -1306,7 +1571,7 @@ class Article {
 
        # This is called on page move and undelete, as well as edit     
        /* static */ function onArticleCreate($title_obj){
-               global $wgEnablePersistentLC, $wgEnableParserCache, $wgUseSquid, $wgDeferredUpdateList;
+               global $wgEnablePersistentLC, $wgUseSquid, $wgDeferredUpdateList;
 
                $titles = $title_obj->getBrokenLinksTo();
                
@@ -1324,31 +1589,20 @@ class Article {
                if ( $wgEnablePersistentLC ) {
                        LinkCache::linksccClearBrokenLinksTo( $title_obj->getPrefixedDBkey() );
                }
-
-               # Clear parser cache (not really used)
-               if ( $wgEnableParserCache ) {
-                       OutputPage::parsercacheClearBrokenLinksTo( $title_obj->getPrefixedDBkey() );
-               }
        }
 
        /* static */ function onArticleDelete($title_obj){
-               global $wgEnablePersistentLC, $wgEnableParserCache;
+               global $wgEnablePersistentLC;
                if ( $wgEnablePersistentLC ) {
                        LinkCache::linksccClearLinksTo( $title_obj->getArticleID() );
                }
-               if ( $wgEnableParserCache ) {
-                       OutputPage::parsercacheClearLinksTo( $title_obj->getArticleID() );
-               }
        }
 
        /* static */ function onArticleEdit($title_obj){
-               global $wgEnablePersistentLC, $wgEnableParserCache;
+               global $wgEnablePersistentLC;
                if ( $wgEnablePersistentLC ) {
                        LinkCache::linksccClearPage( $title_obj->getArticleID() );
                }
-               if ( $wgEnableParserCache ) {
-                       OutputPage::parsercacheClearPage( $title_obj->getArticleID(), $title_obj->getNamespace() );
-               }
        }
 }