From: Aaron Schulz Date: Tue, 23 Oct 2012 00:23:48 +0000 (-0700) Subject: Moved core Job classes under includes/job/jobs. X-Git-Tag: 1.31.0-rc.0~21898^2 X-Git-Url: http://git.cyclocoop.org/%24image?a=commitdiff_plain;h=f555a1922f3a554c462b8090d39acc4136b65bd2;p=lhc%2Fweb%2Fwiklou.git Moved core Job classes under includes/job/jobs. Change-Id: I9257bb0fce9791190f731d72b2e882a1ae400f35 --- diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php index 1cf66348d6..d98556ee34 100644 --- a/includes/AutoLoader.php +++ b/includes/AutoLoader.php @@ -435,7 +435,6 @@ $wgAutoloadLocalClasses = array( 'GenderCache' => 'includes/cache/GenderCache.php', 'GlobalDependency' => 'includes/cache/CacheDependency.php', 'HTMLCacheUpdate' => 'includes/cache/HTMLCacheUpdate.php', - 'HTMLCacheUpdateJob' => 'includes/cache/HTMLCacheUpdate.php', 'HTMLFileCache' => 'includes/cache/HTMLFileCache.php', 'LinkBatch' => 'includes/cache/LinkBatch.php', 'LinkCache' => 'includes/cache/LinkCache.php', @@ -652,16 +651,19 @@ $wgAutoloadLocalClasses = array( 'WebInstallerPage' => 'includes/installer/WebInstallerPage.php', # includes/job - 'DoubleRedirectJob' => 'includes/job/DoubleRedirectJob.php', - 'EmaillingJob' => 'includes/job/EmaillingJob.php', - 'EnotifNotifyJob' => 'includes/job/EnotifNotifyJob.php', 'Job' => 'includes/job/Job.php', 'JobQueue' => 'includes/job/JobQueue.php', 'JobQueueDB' => 'includes/job/JobQueueDB.php', 'JobQueueGroup' => 'includes/job/JobQueueGroup.php', - 'RefreshLinksJob' => 'includes/job/RefreshLinksJob.php', - 'RefreshLinksJob2' => 'includes/job/RefreshLinksJob.php', - 'UploadFromUrlJob' => 'includes/job/UploadFromUrlJob.php', + + # includes/job/jobs + 'DoubleRedirectJob' => 'includes/job/jobs/DoubleRedirectJob.php', + 'EmaillingJob' => 'includes/job/jobs/EmaillingJob.php', + 'EnotifNotifyJob' => 'includes/job/jobs/EnotifNotifyJob.php', + 'HTMLCacheUpdateJob' => 'includes/job/jobs/HTMLCacheUpdateJob.php', + 'RefreshLinksJob' => 'includes/job/jobs/RefreshLinksJob.php', + 'RefreshLinksJob2' => 'includes/job/jobs/RefreshLinksJob.php', + 'UploadFromUrlJob' => 'includes/job/jobs/UploadFromUrlJob.php', # includes/json 'FormatJson' => 'includes/json/FormatJson.php', diff --git a/includes/cache/HTMLCacheUpdate.php b/includes/cache/HTMLCacheUpdate.php index 0a3c0023d6..51a28cad88 100644 --- a/includes/cache/HTMLCacheUpdate.php +++ b/includes/cache/HTMLCacheUpdate.php @@ -228,33 +228,3 @@ class HTMLCacheUpdate implements DeferrableUpdate { } } } - - -/** - * Job wrapper for HTMLCacheUpdate. Gets run whenever a related - * job gets called from the queue. - * - * @ingroup JobQueue - */ -class HTMLCacheUpdateJob extends Job { - var $table, $start, $end; - - /** - * Construct a job - * @param $title Title: the title linked to - * @param $params Array: job parameters (table, start and end page_ids) - * @param $id Integer: job id - */ - function __construct( $title, $params, $id = 0 ) { - parent::__construct( 'htmlCacheUpdate', $title, $params, $id ); - $this->table = $params['table']; - $this->start = $params['start']; - $this->end = $params['end']; - } - - public function run() { - $update = new HTMLCacheUpdate( $this->title, $this->table, $this->start, $this->end ); - $update->doUpdate(); - return true; - } -} diff --git a/includes/job/DoubleRedirectJob.php b/includes/job/DoubleRedirectJob.php deleted file mode 100644 index b1b96b62ab..0000000000 --- a/includes/job/DoubleRedirectJob.php +++ /dev/null @@ -1,210 +0,0 @@ -" - * @param $redirTitle Title: the title which has changed, redirects pointing to this title are fixed - * @param $destTitle bool Not used - */ - public static function fixRedirects( $reason, $redirTitle, $destTitle = false ) { - # Need to use the master to get the redirect table updated in the same transaction - $dbw = wfGetDB( DB_MASTER ); - $res = $dbw->select( - array( 'redirect', 'page' ), - array( 'page_namespace', 'page_title' ), - array( - 'page_id = rd_from', - 'rd_namespace' => $redirTitle->getNamespace(), - 'rd_title' => $redirTitle->getDBkey() - ), __METHOD__ ); - if ( !$res->numRows() ) { - return; - } - $jobs = array(); - foreach ( $res as $row ) { - $title = Title::makeTitle( $row->page_namespace, $row->page_title ); - if ( !$title ) { - continue; - } - - $jobs[] = new self( $title, array( - 'reason' => $reason, - 'redirTitle' => $redirTitle->getPrefixedDBkey() ) ); - # Avoid excessive memory usage - if ( count( $jobs ) > 10000 ) { - Job::batchInsert( $jobs ); - $jobs = array(); - } - } - Job::batchInsert( $jobs ); - } - - function __construct( $title, $params = false, $id = 0 ) { - parent::__construct( 'fixDoubleRedirect', $title, $params, $id ); - $this->reason = $params['reason']; - $this->redirTitle = Title::newFromText( $params['redirTitle'] ); - } - - /** - * @return bool - */ - function run() { - if ( !$this->redirTitle ) { - $this->setLastError( 'Invalid title' ); - return false; - } - - $targetRev = Revision::newFromTitle( $this->title, false, Revision::READ_LATEST ); - if ( !$targetRev ) { - wfDebug( __METHOD__.": target redirect already deleted, ignoring\n" ); - return true; - } - $content = $targetRev->getContent(); - $currentDest = $content->getRedirectTarget(); - if ( !$currentDest || !$currentDest->equals( $this->redirTitle ) ) { - wfDebug( __METHOD__.": Redirect has changed since the job was queued\n" ); - return true; - } - - # Check for a suppression tag (used e.g. in periodically archived discussions) - $mw = MagicWord::get( 'staticredirect' ); - if ( $content->matchMagicWord( $mw ) ) { - wfDebug( __METHOD__.": skipping: suppressed with __STATICREDIRECT__\n" ); - return true; - } - - # Find the current final destination - $newTitle = self::getFinalDestination( $this->redirTitle ); - if ( !$newTitle ) { - wfDebug( __METHOD__.": skipping: single redirect, circular redirect or invalid redirect destination\n" ); - return true; - } - if ( $newTitle->equals( $this->redirTitle ) ) { - # The redirect is already right, no need to change it - # This can happen if the page was moved back (say after vandalism) - wfDebug( __METHOD__.": skipping, already good\n" ); - } - - # Preserve fragment (bug 14904) - $newTitle = Title::makeTitle( $newTitle->getNamespace(), $newTitle->getDBkey(), - $currentDest->getFragment(), $newTitle->getInterwiki() ); - - # Fix the text - $newContent = $content->updateRedirect( $newTitle ); - - if ( $newContent->equals( $content ) ) { - $this->setLastError( 'Content unchanged???' ); - return false; - } - - # Save it - global $wgUser; - $oldUser = $wgUser; - $wgUser = $this->getUser(); - $article = WikiPage::factory( $this->title ); - $reason = wfMessage( 'double-redirect-fixed-' . $this->reason, - $this->redirTitle->getPrefixedText(), $newTitle->getPrefixedText() - )->inContentLanguage()->text(); - $article->doEditContent( $newContent, $reason, EDIT_UPDATE | EDIT_SUPPRESS_RC, false, $this->getUser() ); - $wgUser = $oldUser; - - return true; - } - - /** - * Get the final destination of a redirect - * - * @param $title Title - * - * @return bool if the specified title is not a redirect, or if it is a circular redirect - */ - public static function getFinalDestination( $title ) { - $dbw = wfGetDB( DB_MASTER ); - - $seenTitles = array(); # Circular redirect check - $dest = false; - - while ( true ) { - $titleText = $title->getPrefixedDBkey(); - if ( isset( $seenTitles[$titleText] ) ) { - wfDebug( __METHOD__, "Circular redirect detected, aborting\n" ); - return false; - } - $seenTitles[$titleText] = true; - - if ( $title->getInterwiki() ) { - // If the target is interwiki, we have to break early (bug 40352). - // Otherwise it will look up a row in the local page table - // with the namespace/page of the interwiki target which can cause - // unexpected results (e.g. X -> foo:Bar -> Bar -> .. ) - break; - } - - $row = $dbw->selectRow( - array( 'redirect', 'page' ), - array( 'rd_namespace', 'rd_title', 'rd_interwiki' ), - array( - 'rd_from=page_id', - 'page_namespace' => $title->getNamespace(), - 'page_title' => $title->getDBkey() - ), __METHOD__ ); - if ( !$row ) { - # No redirect from here, chain terminates - break; - } else { - $dest = $title = Title::makeTitle( $row->rd_namespace, $row->rd_title, '', $row->rd_interwiki ); - } - } - return $dest; - } - - /** - * Get a user object for doing edits, from a request-lifetime cache - * @return User - */ - function getUser() { - if ( !self::$user ) { - self::$user = User::newFromName( wfMessage( 'double-redirect-fixer' )->inContentLanguage()->text(), false ); - # FIXME: newFromName could return false on a badly configured wiki. - if ( !self::$user->isLoggedIn() ) { - self::$user->addToDatabase(); - } - } - return self::$user; - } -} - diff --git a/includes/job/EmaillingJob.php b/includes/job/EmaillingJob.php deleted file mode 100644 index d3599882de..0000000000 --- a/includes/job/EmaillingJob.php +++ /dev/null @@ -1,46 +0,0 @@ -params['to'], - $this->params['from'], - $this->params['subj'], - $this->params['body'], - $this->params['replyto'] - ); - return true; - } - -} diff --git a/includes/job/EnotifNotifyJob.php b/includes/job/EnotifNotifyJob.php deleted file mode 100644 index b4c925e945..0000000000 --- a/includes/job/EnotifNotifyJob.php +++ /dev/null @@ -1,57 +0,0 @@ -params['editorID'] ) && $this->params['editorID'] ) { - $editor = User::newFromId( $this->params['editorID'] ); - // B/C, only the name might be given. - } else { - # FIXME: newFromName could return false on a badly configured wiki. - $editor = User::newFromName( $this->params['editor'], false ); - } - $enotif->actuallyNotifyOnPageChange( - $editor, - $this->title, - $this->params['timestamp'], - $this->params['summary'], - $this->params['minorEdit'], - $this->params['oldid'], - $this->params['watchers'] - ); - return true; - } - -} diff --git a/includes/job/RefreshLinksJob.php b/includes/job/RefreshLinksJob.php deleted file mode 100644 index a29f29fe64..0000000000 --- a/includes/job/RefreshLinksJob.php +++ /dev/null @@ -1,202 +0,0 @@ -removeDuplicates = true; // job is expensive - } - - /** - * Run a refreshLinks job - * @return boolean success - */ - function run() { - wfProfileIn( __METHOD__ ); - - $linkCache = LinkCache::singleton(); - $linkCache->clear(); - - if ( is_null( $this->title ) ) { - $this->error = "refreshLinks: Invalid title"; - wfProfileOut( __METHOD__ ); - return false; - } - - # Wait for the DB of the current/next slave DB handle to catch up to the master. - # This way, we get the correct page_latest for templates or files that just changed - # milliseconds ago, having triggered this job to begin with. - if ( isset( $this->params['masterPos'] ) ) { - wfGetLB()->waitFor( $this->params['masterPos'] ); - } - - $revision = Revision::newFromTitle( $this->title, false, Revision::READ_NORMAL ); - if ( !$revision ) { - $this->error = 'refreshLinks: Article not found "' . - $this->title->getPrefixedDBkey() . '"'; - wfProfileOut( __METHOD__ ); - return false; // XXX: what if it was just deleted? - } - - self::runForTitleInternal( $this->title, $revision, __METHOD__ ); - - wfProfileOut( __METHOD__ ); - return true; - } - - public static function runForTitleInternal( Title $title, Revision $revision, $fname ) { - global $wgContLang; - - wfProfileIn( $fname . '-parse' ); - $options = ParserOptions::newFromUserAndLang( new User, $wgContLang ); - $content = $revision->getContent(); - $parserOutput = $content->getParserOutput( $title, $revision->getId(), $options, false ); - wfProfileOut( $fname . '-parse' ); - - wfProfileIn( $fname . '-update' ); - $updates = $content->getSecondaryDataUpdates( $title, null, false, $parserOutput ); - DataUpdate::runUpdates( $updates ); - wfProfileOut( $fname . '-update' ); - } -} - -/** - * Background job to update links for a given title. - * Newer version for high use templates. - * - * @ingroup JobQueue - */ -class RefreshLinksJob2 extends Job { - const MAX_TITLES_RUN = 10; - - function __construct( $title, $params, $id = 0 ) { - parent::__construct( 'refreshLinks2', $title, $params, $id ); - } - - /** - * Run a refreshLinks2 job - * @return boolean success - */ - function run() { - wfProfileIn( __METHOD__ ); - - $linkCache = LinkCache::singleton(); - $linkCache->clear(); - - if ( is_null( $this->title ) ) { - $this->error = "refreshLinks2: Invalid title"; - wfProfileOut( __METHOD__ ); - return false; - } elseif ( !isset( $this->params['start'] ) || !isset( $this->params['end'] ) ) { - $this->error = "refreshLinks2: Invalid params"; - wfProfileOut( __METHOD__ ); - return false; - } - - // Back compat for pre-r94435 jobs - $table = isset( $this->params['table'] ) ? $this->params['table'] : 'templatelinks'; - - // Avoid slave lag when fetching templates - if ( isset( $this->params['masterPos'] ) ) { - $masterPos = $this->params['masterPos']; - } elseif ( wfGetLB()->getServerCount() > 1 ) { - $masterPos = wfGetLB()->getMasterPos(); - } else { - $masterPos = false; - } - - $titles = $this->title->getBacklinkCache()->getLinks( - $table, $this->params['start'], $this->params['end'] ); - - if ( $titles->count() > self::MAX_TITLES_RUN ) { - # We don't want to parse too many pages per job as it can starve other jobs. - # If there are too many pages to parse, break this up into smaller jobs. By passing - # in the master position here we can cut down on the time spent waiting for slaves to - # catch up by the runners handling these jobs since time will have passed between now - # and when they pop these jobs off the queue. - $start = 0; // batch start - $end = 0; // batch end - $bsize = 0; // batch size - $first = true; // first of batch - $jobs = array(); - foreach ( $titles as $title ) { - $start = $first ? $title->getArticleId() : $start; - $end = $title->getArticleId(); - $first = false; - if ( ++$bsize >= self::MAX_TITLES_RUN ) { - $jobs[] = new RefreshLinksJob2( $this->title, array( - 'table' => $table, - 'start' => $start, - 'end' => $end, - 'masterPos' => $masterPos - ) ); - $first = true; - $start = $end = $bsize = 0; - } - } - if ( $bsize > 0 ) { // group remaining pages into a job - $jobs[] = new RefreshLinksJob2( $this->title, array( - 'table' => $table, - 'start' => $start, - 'end' => $end, - 'masterPos' => $masterPos - ) ); - } - Job::batchInsert( $jobs ); - } elseif ( php_sapi_name() != 'cli' ) { - # Not suitable for page load triggered job running! - # Gracefully switch to refreshLinks jobs if this happens. - $jobs = array(); - foreach ( $titles as $title ) { - $jobs[] = new RefreshLinksJob( $title, array( 'masterPos' => $masterPos ) ); - } - Job::batchInsert( $jobs ); - } else { - # Wait for the DB of the current/next slave DB handle to catch up to the master. - # This way, we get the correct page_latest for templates or files that just changed - # milliseconds ago, having triggered this job to begin with. - if ( $masterPos ) { - wfGetLB()->waitFor( $masterPos ); - } - # Re-parse each page that transcludes this page and update their tracking links... - foreach ( $titles as $title ) { - $revision = Revision::newFromTitle( $title, false, Revision::READ_NORMAL ); - if ( !$revision ) { - $this->error = 'refreshLinks: Article not found "' . - $title->getPrefixedDBkey() . '"'; - continue; // skip this page - } - RefreshLinksJob::runForTitleInternal( $title, $revision, __METHOD__ ); - wfWaitForSlaves(); - } - } - - wfProfileOut( __METHOD__ ); - return true; - } -} diff --git a/includes/job/UploadFromUrlJob.php b/includes/job/UploadFromUrlJob.php deleted file mode 100644 index e06f68e461..0000000000 --- a/includes/job/UploadFromUrlJob.php +++ /dev/null @@ -1,179 +0,0 @@ -upload = new UploadFromUrl(); - $this->upload->initialize( - $this->title->getText(), - $this->params['url'], - false - ); - $this->user = User::newFromName( $this->params['userName'] ); - - # Fetch the file - $status = $this->upload->fetchFile(); - if ( !$status->isOk() ) { - $this->leaveMessage( $status ); - return true; - } - - # Verify upload - $result = $this->upload->verifyUpload(); - if ( $result['status'] != UploadBase::OK ) { - $status = $this->upload->convertVerifyErrorToStatus( $result ); - $this->leaveMessage( $status ); - return true; - } - - # Check warnings - if ( !$this->params['ignoreWarnings'] ) { - $warnings = $this->upload->checkWarnings(); - if ( $warnings ) { - - # Stash the upload - $key = $this->upload->stashFile(); - - if ( $this->params['leaveMessage'] ) { - $this->user->leaveUserMessage( - wfMessage( 'upload-warning-subj' )->text(), - wfMessage( 'upload-warning-msg', - $key, - $this->params['url'] )->text() - ); - } else { - wfSetupSession( $this->params['sessionId'] ); - $this->storeResultInSession( 'Warning', - 'warnings', $warnings ); - session_write_close(); - } - - return true; - } - } - - # Perform the upload - $status = $this->upload->performUpload( - $this->params['comment'], - $this->params['pageText'], - $this->params['watch'], - $this->user - ); - $this->leaveMessage( $status ); - return true; - - } - - /** - * Leave a message on the user talk page or in the session according to - * $params['leaveMessage']. - * - * @param $status Status - */ - protected function leaveMessage( $status ) { - if ( $this->params['leaveMessage'] ) { - if ( $status->isGood() ) { - $this->user->leaveUserMessage( wfMessage( 'upload-success-subj' )->text(), - wfMessage( 'upload-success-msg', - $this->upload->getTitle()->getText(), - $this->params['url'] - )->text() ); - } else { - $this->user->leaveUserMessage( wfMessage( 'upload-failure-subj' )->text(), - wfMessage( 'upload-failure-msg', - $status->getWikiText(), - $this->params['url'] - )->text() ); - } - } else { - wfSetupSession( $this->params['sessionId'] ); - if ( $status->isOk() ) { - $this->storeResultInSession( 'Success', - 'filename', $this->upload->getLocalFile()->getName() ); - } else { - $this->storeResultInSession( 'Failure', - 'errors', $status->getErrorsArray() ); - } - session_write_close(); - } - } - - /** - * Store a result in the session data. Note that the caller is responsible - * for appropriate session_start and session_write_close calls. - * - * @param $result String: the result (Success|Warning|Failure) - * @param $dataKey String: the key of the extra data - * @param $dataValue Mixed: the extra data itself - */ - protected function storeResultInSession( $result, $dataKey, $dataValue ) { - $session =& self::getSessionData( $this->params['sessionKey'] ); - $session['result'] = $result; - $session[$dataKey] = $dataValue; - } - - /** - * Initialize the session data. Sets the intial result to queued. - */ - public function initializeSessionData() { - $session =& self::getSessionData( $this->params['sessionKey'] ); - $$session['result'] = 'Queued'; - } - - /** - * @param $key - * @return mixed - */ - public static function &getSessionData( $key ) { - if ( !isset( $_SESSION[self::SESSION_KEYNAME][$key] ) ) { - $_SESSION[self::SESSION_KEYNAME][$key] = array(); - } - return $_SESSION[self::SESSION_KEYNAME][$key]; - } -} diff --git a/includes/job/jobs/DoubleRedirectJob.php b/includes/job/jobs/DoubleRedirectJob.php new file mode 100644 index 0000000000..b1b96b62ab --- /dev/null +++ b/includes/job/jobs/DoubleRedirectJob.php @@ -0,0 +1,210 @@ +" + * @param $redirTitle Title: the title which has changed, redirects pointing to this title are fixed + * @param $destTitle bool Not used + */ + public static function fixRedirects( $reason, $redirTitle, $destTitle = false ) { + # Need to use the master to get the redirect table updated in the same transaction + $dbw = wfGetDB( DB_MASTER ); + $res = $dbw->select( + array( 'redirect', 'page' ), + array( 'page_namespace', 'page_title' ), + array( + 'page_id = rd_from', + 'rd_namespace' => $redirTitle->getNamespace(), + 'rd_title' => $redirTitle->getDBkey() + ), __METHOD__ ); + if ( !$res->numRows() ) { + return; + } + $jobs = array(); + foreach ( $res as $row ) { + $title = Title::makeTitle( $row->page_namespace, $row->page_title ); + if ( !$title ) { + continue; + } + + $jobs[] = new self( $title, array( + 'reason' => $reason, + 'redirTitle' => $redirTitle->getPrefixedDBkey() ) ); + # Avoid excessive memory usage + if ( count( $jobs ) > 10000 ) { + Job::batchInsert( $jobs ); + $jobs = array(); + } + } + Job::batchInsert( $jobs ); + } + + function __construct( $title, $params = false, $id = 0 ) { + parent::__construct( 'fixDoubleRedirect', $title, $params, $id ); + $this->reason = $params['reason']; + $this->redirTitle = Title::newFromText( $params['redirTitle'] ); + } + + /** + * @return bool + */ + function run() { + if ( !$this->redirTitle ) { + $this->setLastError( 'Invalid title' ); + return false; + } + + $targetRev = Revision::newFromTitle( $this->title, false, Revision::READ_LATEST ); + if ( !$targetRev ) { + wfDebug( __METHOD__.": target redirect already deleted, ignoring\n" ); + return true; + } + $content = $targetRev->getContent(); + $currentDest = $content->getRedirectTarget(); + if ( !$currentDest || !$currentDest->equals( $this->redirTitle ) ) { + wfDebug( __METHOD__.": Redirect has changed since the job was queued\n" ); + return true; + } + + # Check for a suppression tag (used e.g. in periodically archived discussions) + $mw = MagicWord::get( 'staticredirect' ); + if ( $content->matchMagicWord( $mw ) ) { + wfDebug( __METHOD__.": skipping: suppressed with __STATICREDIRECT__\n" ); + return true; + } + + # Find the current final destination + $newTitle = self::getFinalDestination( $this->redirTitle ); + if ( !$newTitle ) { + wfDebug( __METHOD__.": skipping: single redirect, circular redirect or invalid redirect destination\n" ); + return true; + } + if ( $newTitle->equals( $this->redirTitle ) ) { + # The redirect is already right, no need to change it + # This can happen if the page was moved back (say after vandalism) + wfDebug( __METHOD__.": skipping, already good\n" ); + } + + # Preserve fragment (bug 14904) + $newTitle = Title::makeTitle( $newTitle->getNamespace(), $newTitle->getDBkey(), + $currentDest->getFragment(), $newTitle->getInterwiki() ); + + # Fix the text + $newContent = $content->updateRedirect( $newTitle ); + + if ( $newContent->equals( $content ) ) { + $this->setLastError( 'Content unchanged???' ); + return false; + } + + # Save it + global $wgUser; + $oldUser = $wgUser; + $wgUser = $this->getUser(); + $article = WikiPage::factory( $this->title ); + $reason = wfMessage( 'double-redirect-fixed-' . $this->reason, + $this->redirTitle->getPrefixedText(), $newTitle->getPrefixedText() + )->inContentLanguage()->text(); + $article->doEditContent( $newContent, $reason, EDIT_UPDATE | EDIT_SUPPRESS_RC, false, $this->getUser() ); + $wgUser = $oldUser; + + return true; + } + + /** + * Get the final destination of a redirect + * + * @param $title Title + * + * @return bool if the specified title is not a redirect, or if it is a circular redirect + */ + public static function getFinalDestination( $title ) { + $dbw = wfGetDB( DB_MASTER ); + + $seenTitles = array(); # Circular redirect check + $dest = false; + + while ( true ) { + $titleText = $title->getPrefixedDBkey(); + if ( isset( $seenTitles[$titleText] ) ) { + wfDebug( __METHOD__, "Circular redirect detected, aborting\n" ); + return false; + } + $seenTitles[$titleText] = true; + + if ( $title->getInterwiki() ) { + // If the target is interwiki, we have to break early (bug 40352). + // Otherwise it will look up a row in the local page table + // with the namespace/page of the interwiki target which can cause + // unexpected results (e.g. X -> foo:Bar -> Bar -> .. ) + break; + } + + $row = $dbw->selectRow( + array( 'redirect', 'page' ), + array( 'rd_namespace', 'rd_title', 'rd_interwiki' ), + array( + 'rd_from=page_id', + 'page_namespace' => $title->getNamespace(), + 'page_title' => $title->getDBkey() + ), __METHOD__ ); + if ( !$row ) { + # No redirect from here, chain terminates + break; + } else { + $dest = $title = Title::makeTitle( $row->rd_namespace, $row->rd_title, '', $row->rd_interwiki ); + } + } + return $dest; + } + + /** + * Get a user object for doing edits, from a request-lifetime cache + * @return User + */ + function getUser() { + if ( !self::$user ) { + self::$user = User::newFromName( wfMessage( 'double-redirect-fixer' )->inContentLanguage()->text(), false ); + # FIXME: newFromName could return false on a badly configured wiki. + if ( !self::$user->isLoggedIn() ) { + self::$user->addToDatabase(); + } + } + return self::$user; + } +} + diff --git a/includes/job/jobs/EmaillingJob.php b/includes/job/jobs/EmaillingJob.php new file mode 100644 index 0000000000..d3599882de --- /dev/null +++ b/includes/job/jobs/EmaillingJob.php @@ -0,0 +1,46 @@ +params['to'], + $this->params['from'], + $this->params['subj'], + $this->params['body'], + $this->params['replyto'] + ); + return true; + } + +} diff --git a/includes/job/jobs/EnotifNotifyJob.php b/includes/job/jobs/EnotifNotifyJob.php new file mode 100644 index 0000000000..b4c925e945 --- /dev/null +++ b/includes/job/jobs/EnotifNotifyJob.php @@ -0,0 +1,57 @@ +params['editorID'] ) && $this->params['editorID'] ) { + $editor = User::newFromId( $this->params['editorID'] ); + // B/C, only the name might be given. + } else { + # FIXME: newFromName could return false on a badly configured wiki. + $editor = User::newFromName( $this->params['editor'], false ); + } + $enotif->actuallyNotifyOnPageChange( + $editor, + $this->title, + $this->params['timestamp'], + $this->params['summary'], + $this->params['minorEdit'], + $this->params['oldid'], + $this->params['watchers'] + ); + return true; + } + +} diff --git a/includes/job/jobs/HTMLCacheUpdateJob.php b/includes/job/jobs/HTMLCacheUpdateJob.php new file mode 100644 index 0000000000..4e6fd6cb53 --- /dev/null +++ b/includes/job/jobs/HTMLCacheUpdateJob.php @@ -0,0 +1,51 @@ +table = $params['table']; + $this->start = $params['start']; + $this->end = $params['end']; + } + + public function run() { + $update = new HTMLCacheUpdate( $this->title, $this->table, $this->start, $this->end ); + $update->doUpdate(); + return true; + } +} diff --git a/includes/job/jobs/RefreshLinksJob.php b/includes/job/jobs/RefreshLinksJob.php new file mode 100644 index 0000000000..a29f29fe64 --- /dev/null +++ b/includes/job/jobs/RefreshLinksJob.php @@ -0,0 +1,202 @@ +removeDuplicates = true; // job is expensive + } + + /** + * Run a refreshLinks job + * @return boolean success + */ + function run() { + wfProfileIn( __METHOD__ ); + + $linkCache = LinkCache::singleton(); + $linkCache->clear(); + + if ( is_null( $this->title ) ) { + $this->error = "refreshLinks: Invalid title"; + wfProfileOut( __METHOD__ ); + return false; + } + + # Wait for the DB of the current/next slave DB handle to catch up to the master. + # This way, we get the correct page_latest for templates or files that just changed + # milliseconds ago, having triggered this job to begin with. + if ( isset( $this->params['masterPos'] ) ) { + wfGetLB()->waitFor( $this->params['masterPos'] ); + } + + $revision = Revision::newFromTitle( $this->title, false, Revision::READ_NORMAL ); + if ( !$revision ) { + $this->error = 'refreshLinks: Article not found "' . + $this->title->getPrefixedDBkey() . '"'; + wfProfileOut( __METHOD__ ); + return false; // XXX: what if it was just deleted? + } + + self::runForTitleInternal( $this->title, $revision, __METHOD__ ); + + wfProfileOut( __METHOD__ ); + return true; + } + + public static function runForTitleInternal( Title $title, Revision $revision, $fname ) { + global $wgContLang; + + wfProfileIn( $fname . '-parse' ); + $options = ParserOptions::newFromUserAndLang( new User, $wgContLang ); + $content = $revision->getContent(); + $parserOutput = $content->getParserOutput( $title, $revision->getId(), $options, false ); + wfProfileOut( $fname . '-parse' ); + + wfProfileIn( $fname . '-update' ); + $updates = $content->getSecondaryDataUpdates( $title, null, false, $parserOutput ); + DataUpdate::runUpdates( $updates ); + wfProfileOut( $fname . '-update' ); + } +} + +/** + * Background job to update links for a given title. + * Newer version for high use templates. + * + * @ingroup JobQueue + */ +class RefreshLinksJob2 extends Job { + const MAX_TITLES_RUN = 10; + + function __construct( $title, $params, $id = 0 ) { + parent::__construct( 'refreshLinks2', $title, $params, $id ); + } + + /** + * Run a refreshLinks2 job + * @return boolean success + */ + function run() { + wfProfileIn( __METHOD__ ); + + $linkCache = LinkCache::singleton(); + $linkCache->clear(); + + if ( is_null( $this->title ) ) { + $this->error = "refreshLinks2: Invalid title"; + wfProfileOut( __METHOD__ ); + return false; + } elseif ( !isset( $this->params['start'] ) || !isset( $this->params['end'] ) ) { + $this->error = "refreshLinks2: Invalid params"; + wfProfileOut( __METHOD__ ); + return false; + } + + // Back compat for pre-r94435 jobs + $table = isset( $this->params['table'] ) ? $this->params['table'] : 'templatelinks'; + + // Avoid slave lag when fetching templates + if ( isset( $this->params['masterPos'] ) ) { + $masterPos = $this->params['masterPos']; + } elseif ( wfGetLB()->getServerCount() > 1 ) { + $masterPos = wfGetLB()->getMasterPos(); + } else { + $masterPos = false; + } + + $titles = $this->title->getBacklinkCache()->getLinks( + $table, $this->params['start'], $this->params['end'] ); + + if ( $titles->count() > self::MAX_TITLES_RUN ) { + # We don't want to parse too many pages per job as it can starve other jobs. + # If there are too many pages to parse, break this up into smaller jobs. By passing + # in the master position here we can cut down on the time spent waiting for slaves to + # catch up by the runners handling these jobs since time will have passed between now + # and when they pop these jobs off the queue. + $start = 0; // batch start + $end = 0; // batch end + $bsize = 0; // batch size + $first = true; // first of batch + $jobs = array(); + foreach ( $titles as $title ) { + $start = $first ? $title->getArticleId() : $start; + $end = $title->getArticleId(); + $first = false; + if ( ++$bsize >= self::MAX_TITLES_RUN ) { + $jobs[] = new RefreshLinksJob2( $this->title, array( + 'table' => $table, + 'start' => $start, + 'end' => $end, + 'masterPos' => $masterPos + ) ); + $first = true; + $start = $end = $bsize = 0; + } + } + if ( $bsize > 0 ) { // group remaining pages into a job + $jobs[] = new RefreshLinksJob2( $this->title, array( + 'table' => $table, + 'start' => $start, + 'end' => $end, + 'masterPos' => $masterPos + ) ); + } + Job::batchInsert( $jobs ); + } elseif ( php_sapi_name() != 'cli' ) { + # Not suitable for page load triggered job running! + # Gracefully switch to refreshLinks jobs if this happens. + $jobs = array(); + foreach ( $titles as $title ) { + $jobs[] = new RefreshLinksJob( $title, array( 'masterPos' => $masterPos ) ); + } + Job::batchInsert( $jobs ); + } else { + # Wait for the DB of the current/next slave DB handle to catch up to the master. + # This way, we get the correct page_latest for templates or files that just changed + # milliseconds ago, having triggered this job to begin with. + if ( $masterPos ) { + wfGetLB()->waitFor( $masterPos ); + } + # Re-parse each page that transcludes this page and update their tracking links... + foreach ( $titles as $title ) { + $revision = Revision::newFromTitle( $title, false, Revision::READ_NORMAL ); + if ( !$revision ) { + $this->error = 'refreshLinks: Article not found "' . + $title->getPrefixedDBkey() . '"'; + continue; // skip this page + } + RefreshLinksJob::runForTitleInternal( $title, $revision, __METHOD__ ); + wfWaitForSlaves(); + } + } + + wfProfileOut( __METHOD__ ); + return true; + } +} diff --git a/includes/job/jobs/UploadFromUrlJob.php b/includes/job/jobs/UploadFromUrlJob.php new file mode 100644 index 0000000000..e06f68e461 --- /dev/null +++ b/includes/job/jobs/UploadFromUrlJob.php @@ -0,0 +1,179 @@ +upload = new UploadFromUrl(); + $this->upload->initialize( + $this->title->getText(), + $this->params['url'], + false + ); + $this->user = User::newFromName( $this->params['userName'] ); + + # Fetch the file + $status = $this->upload->fetchFile(); + if ( !$status->isOk() ) { + $this->leaveMessage( $status ); + return true; + } + + # Verify upload + $result = $this->upload->verifyUpload(); + if ( $result['status'] != UploadBase::OK ) { + $status = $this->upload->convertVerifyErrorToStatus( $result ); + $this->leaveMessage( $status ); + return true; + } + + # Check warnings + if ( !$this->params['ignoreWarnings'] ) { + $warnings = $this->upload->checkWarnings(); + if ( $warnings ) { + + # Stash the upload + $key = $this->upload->stashFile(); + + if ( $this->params['leaveMessage'] ) { + $this->user->leaveUserMessage( + wfMessage( 'upload-warning-subj' )->text(), + wfMessage( 'upload-warning-msg', + $key, + $this->params['url'] )->text() + ); + } else { + wfSetupSession( $this->params['sessionId'] ); + $this->storeResultInSession( 'Warning', + 'warnings', $warnings ); + session_write_close(); + } + + return true; + } + } + + # Perform the upload + $status = $this->upload->performUpload( + $this->params['comment'], + $this->params['pageText'], + $this->params['watch'], + $this->user + ); + $this->leaveMessage( $status ); + return true; + + } + + /** + * Leave a message on the user talk page or in the session according to + * $params['leaveMessage']. + * + * @param $status Status + */ + protected function leaveMessage( $status ) { + if ( $this->params['leaveMessage'] ) { + if ( $status->isGood() ) { + $this->user->leaveUserMessage( wfMessage( 'upload-success-subj' )->text(), + wfMessage( 'upload-success-msg', + $this->upload->getTitle()->getText(), + $this->params['url'] + )->text() ); + } else { + $this->user->leaveUserMessage( wfMessage( 'upload-failure-subj' )->text(), + wfMessage( 'upload-failure-msg', + $status->getWikiText(), + $this->params['url'] + )->text() ); + } + } else { + wfSetupSession( $this->params['sessionId'] ); + if ( $status->isOk() ) { + $this->storeResultInSession( 'Success', + 'filename', $this->upload->getLocalFile()->getName() ); + } else { + $this->storeResultInSession( 'Failure', + 'errors', $status->getErrorsArray() ); + } + session_write_close(); + } + } + + /** + * Store a result in the session data. Note that the caller is responsible + * for appropriate session_start and session_write_close calls. + * + * @param $result String: the result (Success|Warning|Failure) + * @param $dataKey String: the key of the extra data + * @param $dataValue Mixed: the extra data itself + */ + protected function storeResultInSession( $result, $dataKey, $dataValue ) { + $session =& self::getSessionData( $this->params['sessionKey'] ); + $session['result'] = $result; + $session[$dataKey] = $dataValue; + } + + /** + * Initialize the session data. Sets the intial result to queued. + */ + public function initializeSessionData() { + $session =& self::getSessionData( $this->params['sessionKey'] ); + $$session['result'] = 'Queued'; + } + + /** + * @param $key + * @return mixed + */ + public static function &getSessionData( $key ) { + if ( !isset( $_SESSION[self::SESSION_KEYNAME][$key] ) ) { + $_SESSION[self::SESSION_KEYNAME][$key] = array(); + } + return $_SESSION[self::SESSION_KEYNAME][$key]; + } +}