Add support for Number grouping(commafy) based on CLDR number grouping patterns like...
[lhc/web/wiklou.git] / includes / WikiPage.php
index 85b5fb5..b95faed 100644 (file)
@@ -79,17 +79,16 @@ class WikiPage extends Page {
        /**
         * Constructor from a page id
         *
-        * Always override this for all subclasses (until we use PHP with LSB)
-        *
         * @param $id Int article ID to load
         *
         * @return WikiPage
         */
        public static function newFromID( $id ) {
                $t = Title::newFromID( $id );
-               # @todo FIXME: Doesn't inherit right
-               return $t == null ? null : new self( $t );
-               # return $t == null ? null : new static( $t ); // PHP 5.3
+               if ( $t ) {
+                       return self::factory( $t );
+               }
+               return null;
        }
 
        /**
@@ -98,6 +97,8 @@ class WikiPage extends Page {
         * (and only when) $wgActions[$action] === true. This allows subclasses
         * to override the default behavior.
         *
+        * @todo: move this UI stuff somewhere else
+        *
         * @return Array
         */
        public function getActionOverrides() {
@@ -164,11 +165,11 @@ class WikiPage extends Page {
                $dbw = wfGetDB( DB_MASTER );
                $dbw->replace( 'redirect', array( 'rd_from' ),
                        array(
-                               'rd_from'               => $this->getId(),
-                               'rd_namespace'  => $rt->getNamespace(),
-                               'rd_title'              => $rt->getDBkey(),
-                               'rd_fragment'   => $rt->getFragment(),
-                               'rd_interwiki'  => $rt->getInterwiki(),
+                               'rd_from'      => $this->getId(),
+                               'rd_namespace' => $rt->getNamespace(),
+                               'rd_title'     => $rt->getDBkey(),
+                               'rd_fragment'  => $rt->getFragment(),
+                               'rd_interwiki' => $rt->getInterwiki(),
                        ),
                        __METHOD__
                );
@@ -370,8 +371,7 @@ class WikiPage extends Page {
                $lc = LinkCache::singleton();
 
                if ( $data ) {
-                       $lc->addGoodLinkObj( $data->page_id, $this->mTitle,
-                               $data->page_len, $data->page_is_redirect, $data->page_latest );
+                       $lc->addGoodLinkObjFromRow( $this->mTitle, $data );
 
                        $this->mTitle->loadFromRow( $data );
 
@@ -574,7 +574,7 @@ class WikiPage extends Page {
         * @return string MW timestamp of last article revision
         */
        public function getTimestamp() {
-               // Check if the field has been filled by ParserCache::get()
+               // Check if the field has been filled by WikiPage::setTimestamp()
                if ( !$this->mTimestamp ) {
                        $this->loadLastEdit();
                }
@@ -716,8 +716,7 @@ class WikiPage extends Page {
                        && $user->getStubThreshold() == 0
                        && $this->exists()
                        && empty( $oldid )
-                       && !$this->mTitle->isCssOrJsPage()
-                       && !$this->mTitle->isCssJsSubpage();
+                       && $this->mTitle->isWikitextPage();
        }
 
        /**
@@ -800,7 +799,7 @@ class WikiPage extends Page {
         *
         * @param $dbw DatabaseBase: object
         * @param $revision Revision: For ID number, and text used to set
-                                               length and redirect status fields
+        *                  length and redirect status fields
         * @param $lastRevision Integer: if given, will not overwrite the page field
         *                      when different from the currently set value.
         *                      Giving 0 indicates the new page flag should be set
@@ -1031,7 +1030,7 @@ class WikiPage extends Page {
         *          Fill in blank summaries with generated text where possible
         *
         * If neither EDIT_NEW nor EDIT_UPDATE is specified, the status of the article will be detected.
-        * If EDIT_UPDATE is specified and the article doesn't exist, the function will an
+        * If EDIT_UPDATE is specified and the article doesn't exist, the function will return an
         * edit-gone-missing error. If EDIT_NEW is specified and the article does exist, an
         * edit-already-exists error will be returned. These two conditions are also possible with
         * auto-detection due to MediaWiki's performance-optimised locking strategy.
@@ -1095,7 +1094,7 @@ class WikiPage extends Page {
 
                # Provide autosummaries if one is not provided and autosummaries are enabled.
                if ( $wgUseAutomaticEditSummaries && $flags & EDIT_AUTOSUMMARY && $summary == '' ) {
-                       $summary = $this->getAutosummary( $oldtext, $text, $flags );
+                       $summary = self::getAutosummary( $oldtext, $text, $flags );
                }
 
                $editInfo = $this->prepareTextForEdit( $text, null, $user );
@@ -1276,7 +1275,7 @@ class WikiPage extends Page {
 
                # Do updates right now unless deferral was requested
                if ( !( $flags & EDIT_DEFER_UPDATES ) ) {
-                       wfDoUpdates();
+                       DeferredUpdates::doUpdates();
                }
 
                // Return the new revision (or null) to the caller
@@ -1295,6 +1294,8 @@ class WikiPage extends Page {
        /**
         * Update the article's restriction field, and leave a log entry.
         *
+        * @todo: seperate the business/permission stuff out from backend code
+        *
         * @param $limit Array: set of restriction keys
         * @param $reason String
         * @param &$cascade Integer. Set to false if cascading protection isn't allowed.
@@ -1409,7 +1410,7 @@ class WikiPage extends Page {
 
                                        $encodedExpiry[$action] = $dbw->encodeExpiry( $expiry[$action] );
                                        if ( $restrictions != '' ) {
-                                               $protect_description .= "[$action=$restrictions] (";
+                                               $protect_description .= $wgContLang->getDirMark() . "[$action=$restrictions] (";
                                                if ( $encodedExpiry[$action] != 'infinity' ) {
                                                        $protect_description .= wfMsgForContent( 'protect-expiring',
                                                                $wgContLang->timeanddate( $expiry[$action], false, false ) ,
@@ -1605,7 +1606,7 @@ class WikiPage extends Page {
        public function doDeleteArticle(
                $reason, $suppress = false, $id = 0, $commit = true, &$error = '', User $user = null
        ) {
-               global $wgDeferredUpdateList, $wgUseTrackbacks, $wgUser;
+               global $wgUseTrackbacks, $wgEnableInterwikiTemplatesTracking, $wgGlobalDatabase, $wgUser;
                $user = is_null( $user ) ? $wgUser : $user;
 
                wfDebug( __METHOD__ . "\n" );
@@ -1621,8 +1622,9 @@ class WikiPage extends Page {
                        return false;
                }
 
-               $u = new SiteStatsUpdate( 0, 1, - (int)$this->isCountable(), -1 );
-               array_push( $wgDeferredUpdateList, $u );
+               DeferredUpdates::addUpdate(
+                       new SiteStatsUpdate( 0, 1, - (int)$this->isCountable(), -1 )
+               );
 
                // Bitfields to further suppress the content
                if ( $suppress ) {
@@ -1696,18 +1698,28 @@ class WikiPage extends Page {
                if ( !$dbw->cascadingDeletes() ) {
                        $dbw->delete( 'revision', array( 'rev_page' => $id ), __METHOD__ );
 
-                       if ( $wgUseTrackbacks )
+                       if ( $wgUseTrackbacks ) {
                                $dbw->delete( 'trackbacks', array( 'tb_page' => $id ), __METHOD__ );
+                       }
 
                        # Delete outgoing links
-                       $dbw->delete( 'pagelinks', array( 'pl_from' => $id ) );
-                       $dbw->delete( 'imagelinks', array( 'il_from' => $id ) );
-                       $dbw->delete( 'categorylinks', array( 'cl_from' => $id ) );
-                       $dbw->delete( 'templatelinks', array( 'tl_from' => $id ) );
-                       $dbw->delete( 'externallinks', array( 'el_from' => $id ) );
-                       $dbw->delete( 'langlinks', array( 'll_from' => $id ) );
-                       $dbw->delete( 'iwlinks', array( 'iwl_from' => $id ) );
-                       $dbw->delete( 'redirect', array( 'rd_from' => $id ) );
+                       $dbw->delete( 'pagelinks', array( 'pl_from' => $id ), __METHOD__ );
+                       $dbw->delete( 'imagelinks', array( 'il_from' => $id ), __METHOD__ );
+                       $dbw->delete( 'categorylinks', array( 'cl_from' => $id ), __METHOD__ );
+                       $dbw->delete( 'templatelinks', array( 'tl_from' => $id ), __METHOD__ );
+                       $dbw->delete( 'externallinks', array( 'el_from' => $id ), __METHOD__ );
+                       $dbw->delete( 'langlinks', array( 'll_from' => $id ), __METHOD__ );
+                       $dbw->delete( 'iwlinks', array( 'iwl_from' => $id ), __METHOD__ );
+                       $dbw->delete( 'redirect', array( 'rd_from' => $id ), __METHOD__ );
+                       $dbw->delete( 'page_props', array( 'pp_page' => $id ), __METHOD__ );
+
+                       if ( $wgEnableInterwikiTemplatesTracking && $wgGlobalDatabase ) {
+                               $dbw2 = wfGetDB( DB_MASTER, array(), $wgGlobalDatabase );
+                               $dbw2->delete( 'globaltemplatelinks',
+                                                       array(  'gtl_from_wiki' => wfGetID(),
+                                                                       'gtl_from_page' => $id )
+                                                       );
+                       }
                }
 
                # If using cleanup triggers, we can skip some manual deletes
@@ -1731,10 +1743,13 @@ class WikiPage extends Page {
 
                # Log the deletion, if the page was suppressed, log it at Oversight instead
                $logtype = $suppress ? 'suppress' : 'delete';
-               $log = new LogPage( $logtype );
 
-               # Make sure logging got through
-               $log->addEntry( 'delete', $this->mTitle, $reason, array() );
+               $logEntry = new ManualLogEntry( $logtype, 'delete' );
+               $logEntry->setPerformer( $user );
+               $logEntry->setTarget( $this->mTitle );
+               $logEntry->setComment( $reason );
+               $logid = $logEntry->insert();
+               $logEntry->publish( $logid );
 
                if ( $commit ) {
                        $dbw->commit();
@@ -1797,7 +1812,7 @@ class WikiPage extends Page {
         * and return value documentation
         *
         * NOTE: This function does NOT check ANY permissions, it just commits the
-        * rollback to the DB Therefore, you should only call this function direct-
+        * rollback to the DB. Therefore, you should only call this function direct-
         * ly if you want to use custom permissions checks. If you don't, use
         * doRollback() instead.
         * @param $fromP String: Name of the user whose edits to rollback.
@@ -1930,15 +1945,15 @@ class WikiPage extends Page {
         * @param $user User The relevant user
         */
        public function doViewUpdates( User $user ) {
-               global $wgDeferredUpdateList, $wgDisableCounters;
+               global $wgDisableCounters;
                if ( wfReadOnly() ) {
                        return;
                }
 
                # Don't update page view counters on views from bot users (bug 14044)
                if ( !$wgDisableCounters && !$user->isAllowed( 'bot' ) && $this->getId() ) {
-                       $wgDeferredUpdateList[] = new ViewCountUpdate( $this->getId() );
-                       $wgDeferredUpdateList[] = new SiteStatsUpdate( 1, 0, 0 );
+                       DeferredUpdates::addUpdate( new ViewCountUpdate( $this->getId() ) );
+                       DeferredUpdates::addUpdate( new SiteStatsUpdate( 1, 0, 0 ) );
                }
 
                # Update newtalk / watchlist notification status
@@ -1968,7 +1983,7 @@ class WikiPage extends Page {
                $edit->revid = $revid;
                $edit->newText = $text;
                $edit->pst = $this->preSaveTransform( $text, $user, $popts );
-               $edit->popts = $this->getParserOptions( true );
+               $edit->popts = $this->makeParserOptions( new User );
                $edit->output = $wgParser->parse( $edit->pst, $this->mTitle, $edit->popts, true, true, $revid );
                $edit->oldText = $this->getRawText();
 
@@ -1995,7 +2010,7 @@ class WikiPage extends Page {
         *   - null: don't change the article count
         */
        public function doEditUpdates( Revision $revision, User $user, array $options = array() ) {
-               global $wgDeferredUpdateList, $wgEnableParserCache;
+               global $wgEnableParserCache;
 
                wfProfileIn( __METHOD__ );
 
@@ -2063,8 +2078,8 @@ class WikiPage extends Page {
                        $total = 0;
                }
 
-               $wgDeferredUpdateList[] = new SiteStatsUpdate( 0, 1, $good, $total );
-               $wgDeferredUpdateList[] = new SearchUpdate( $id, $title, $text );
+               DeferredUpdates::addUpdate( new SiteStatsUpdate( 0, 1, $good, $total ) );
+               DeferredUpdates::addUpdate( new SearchUpdate( $id, $title, $text ) );
 
                # If this is another user's talk page, update newtalk.
                # Don't do this if $options['changed'] = false (null-edits) nor if
@@ -2225,6 +2240,9 @@ class WikiPage extends Page {
                $title->touchLinks();
                $title->purgeSquid();
                $title->deleteTitleProtection();
+
+               # Invalidate caches of distant articles which transclude this page
+               DeferredUpdates::addHTMLCacheUpdate( $title, 'globaltemplatelinks' );
        }
 
        /**
@@ -2268,6 +2286,9 @@ class WikiPage extends Page {
 
                # Image redirects
                RepoGroup::singleton()->getLocalRepo()->invalidateImageRedirect( $title );
+
+               # Invalidate caches of distant articles which transclude this page
+               DeferredUpdates::addHTMLCacheUpdate( $title, 'globaltemplatelinks' );
        }
 
        /**
@@ -2277,13 +2298,14 @@ class WikiPage extends Page {
         * @todo:  verify that $title is always a Title object (and never false or null), add Title hint to parameter $title
         */
        public static function onArticleEdit( $title ) {
-               global $wgDeferredUpdateList;
-
                // Invalidate caches of articles which include this page
-               $wgDeferredUpdateList[] = new HTMLCacheUpdate( $title, 'templatelinks' );
+               DeferredUpdates::addHTMLCacheUpdate( $title, 'templatelinks' );
+
+               // Invalidate caches of distant articles which transclude this page
+               DeferredUpdates::addHTMLCacheUpdate( $title, 'globaltemplatelinks' );
 
                // Invalidate the caches of all pages which redirect here
-               $wgDeferredUpdateList[] = new HTMLCacheUpdate( $title, 'redirect' );
+               DeferredUpdates::addHTMLCacheUpdate( $title, 'redirect' );
 
                # Purge squid for this page only
                $title->purgeSquid();
@@ -2323,6 +2345,40 @@ class WikiPage extends Page {
                return $result;
        }
 
+       /**
+        * Return a list of distant templates used by this article.
+        * Uses the globaltemplatelinks table
+        *
+        * @return Array of Title objects
+        */
+       public function getUsedDistantTemplates() {
+               global $wgGlobalDatabase;
+
+               $result = array();
+
+               if ( $wgGlobalDatabase ) {
+                       $id = $this->mTitle->getArticleID();
+
+                       if ( $id == 0 ) {
+                               return array();
+                       }
+
+                       $dbr = wfGetDB( DB_SLAVE, array(), $wgGlobalDatabase );
+                       $res = $dbr->select( 'globaltemplatelinks',
+                               array( 'gtl_to_prefix', 'gtl_to_namespace', 'gtl_to_title' ),
+                               array( 'gtl_from_wiki' => wfWikiID( ), 'gtl_from_page' => $id ),
+                               __METHOD__ );
+
+                       if ( $res !== false ) {
+                               foreach ( $res as $row ) {
+                                       $result[] = Title::makeTitle( $row->gtl_to_namespace, $row->gtl_to_title, null, $row->gtl_to_prefix );
+                               }
+                       }
+               }
+
+               return $result;
+       }
+
        /**
         * Returns a list of hidden categories this page is a member of.
         * Uses the page_props and categorylinks tables.
@@ -2409,24 +2465,107 @@ class WikiPage extends Page {
        }
 
        /**
-        * Get parser options suitable for rendering the primary article wikitext
-        * @param $canonical boolean Determines that the generated options must not depend on user preferences (see bug 14404)
-        * @return mixed ParserOptions object or boolean false
+        * Auto-generates a deletion reason
+        *
+        * @param &$hasHistory Boolean: whether the page has a history
+        * @return mixed String containing deletion reason or empty string, or boolean false
+        *    if no revision occurred
         */
-       public function getParserOptions( $canonical = false ) {
-               global $wgUser, $wgLanguageCode;
+       public function getAutoDeleteReason( &$hasHistory ) {
+               global $wgContLang;
 
-               if ( !$this->mParserOptions || $canonical ) {
-                       $user = !$canonical ? $wgUser : new User;
-                       $parserOptions = new ParserOptions( $user );
-                       $parserOptions->setTidy( true );
-                       $parserOptions->enableLimitReport();
+               $dbw = wfGetDB( DB_MASTER );
+               // Get the last revision
+               $rev = Revision::newFromTitle( $this->getTitle() );
+
+               if ( is_null( $rev ) ) {
+                       return false;
+               }
+
+               // Get the article's contents
+               $contents = $rev->getText();
+               $blank = false;
 
-                       if ( $canonical ) {
-                               $parserOptions->setUserLang( $wgLanguageCode ); # Must be set explicitely
-                               return $parserOptions;
+               // If the page is blank, use the text from the previous revision,
+               // which can only be blank if there's a move/import/protect dummy revision involved
+               if ( $contents == '' ) {
+                       $prev = $rev->getPrevious();
+
+                       if ( $prev )    {
+                               $contents = $prev->getText();
+                               $blank = true;
                        }
-                       $this->mParserOptions = $parserOptions;
+               }
+
+               // Find out if there was only one contributor
+               // Only scan the last 20 revisions
+               $res = $dbw->select( 'revision', 'rev_user_text',
+                       array( 'rev_page' => $this->getID(), $dbw->bitAnd( 'rev_deleted', Revision::DELETED_USER ) . ' = 0' ),
+                       __METHOD__,
+                       array( 'LIMIT' => 20 )
+               );
+
+               if ( $res === false ) {
+                       // This page has no revisions, which is very weird
+                       return false;
+               }
+
+               $hasHistory = ( $res->numRows() > 1 );
+               $row = $dbw->fetchObject( $res );
+
+               if ( $row ) { // $row is false if the only contributor is hidden
+                       $onlyAuthor = $row->rev_user_text;
+                       // Try to find a second contributor
+                       foreach ( $res as $row ) {
+                               if ( $row->rev_user_text != $onlyAuthor ) { // Bug 22999
+                                       $onlyAuthor = false;
+                                       break;
+                               }
+                       }
+               } else {
+                       $onlyAuthor = false;
+               }
+
+               // Generate the summary with a '$1' placeholder
+               if ( $blank ) {
+                       // The current revision is blank and the one before is also
+                       // blank. It's just not our lucky day
+                       $reason = wfMsgForContent( 'exbeforeblank', '$1' );
+               } else {
+                       if ( $onlyAuthor ) {
+                               $reason = wfMsgForContent( 'excontentauthor', '$1', $onlyAuthor );
+                       } else {
+                               $reason = wfMsgForContent( 'excontent', '$1' );
+                       }
+               }
+
+               if ( $reason == '-' ) {
+                       // Allow these UI messages to be blanked out cleanly
+                       return '';
+               }
+
+               // Replace newlines with spaces to prevent uglyness
+               $contents = preg_replace( "/[\n\r]/", ' ', $contents );
+               // Calculate the maximum amount of chars to get
+               // Max content length = max comment length - length of the comment (excl. $1)
+               $maxLength = 255 - ( strlen( $reason ) - 2 );
+               $contents = $wgContLang->truncate( $contents, $maxLength );
+               // Remove possible unfinished links
+               $contents = preg_replace( '/\[\[([^\]]*)\]?$/', '$1', $contents );
+               // Now replace the '$1' placeholder
+               $reason = str_replace( '$1', $contents, $reason );
+
+               return $reason;
+       }
+
+       /**
+        * Get parser options suitable for rendering the primary article wikitext
+        * @return mixed ParserOptions object or boolean false
+        */
+       public function getParserOptions() {
+               global $wgUser;
+               if ( !$this->mParserOptions ) {
+                       $this->mParserOptions = $this->makeParserOptions( $wgUser );
                }
                // Clone to allow modifications of the return value without affecting cache
                return clone $this->mParserOptions;
@@ -2438,9 +2577,13 @@ class WikiPage extends Page {
        * @return ParserOptions
        */
        public function makeParserOptions( User $user ) {
+               global $wgLanguageCode;
                $options = ParserOptions::newFromUser( $user );
                $options->enableLimitReport(); // show inclusion/loop reports
                $options->setTidy( true ); // fix bad HTML
+               if ( $user->isAnon() ) {
+                       $options->setUserLang( $wgLanguageCode ); # Must be set explicitily
+               }
                return $options;
        }