Move jobqueue classes to their own directory.
authorMark A. Hershberger <mah@users.mediawiki.org>
Fri, 14 May 2010 16:23:26 +0000 (16:23 +0000)
committerMark A. Hershberger <mah@users.mediawiki.org>
Fri, 14 May 2010 16:23:26 +0000 (16:23 +0000)
13 files changed:
includes/AutoLoader.php
includes/DoubleRedirectJob.php [deleted file]
includes/EmaillingJob.php [deleted file]
includes/EnotifNotifyJob.php [deleted file]
includes/JobQueue.php [deleted file]
includes/RefreshLinksJob.php [deleted file]
includes/UploadFromUrlJob.php [deleted file]
includes/job/DoubleRedirectJob.php [new file with mode: 0644]
includes/job/EmaillingJob.php [new file with mode: 0644]
includes/job/EnotifNotifyJob.php [new file with mode: 0644]
includes/job/JobQueue.php [new file with mode: 0644]
includes/job/RefreshLinksJob.php [new file with mode: 0644]
includes/job/UploadFromUrlJob.php [new file with mode: 0644]

index 03ca573..7fe3cff 100644 (file)
@@ -49,7 +49,7 @@ $wgAutoloadLocalClasses = array(
        'DiffHistoryBlob' => 'includes/HistoryBlob.php',
        'DjVuImage' => 'includes/DjVuImage.php',
        'DoubleReplacer' => 'includes/StringUtils.php',
-       'DoubleRedirectJob' => 'includes/DoubleRedirectJob.php',
+       'DoubleRedirectJob' => 'includes/job/DoubleRedirectJob.php',
        'DublinCoreRdf' => 'includes/Metadata.php',
        'Dump7ZipOutput' => 'includes/Export.php',
        'DumpBZip2Output' => 'includes/Export.php',
@@ -64,10 +64,10 @@ $wgAutoloadLocalClasses = array(
        'DumpPipeOutput' => 'includes/Export.php',
        'eAccelBagOStuff' => 'includes/BagOStuff.php',
        'EditPage' => 'includes/EditPage.php',
-       'EmaillingJob' => 'includes/EmaillingJob.php',
+       'EmaillingJob' => 'includes/job/EmaillingJob.php',
        'EmailNotification' => 'includes/UserMailer.php',
        'EnhancedChangesList' => 'includes/ChangesList.php',
-       'EnotifNotifyJob' => 'includes/EnotifNotifyJob.php',
+       'EnotifNotifyJob' => 'includes/job/EnotifNotifyJob.php',
        'ErrorPageError' => 'includes/Exception.php',
        'Exif' => 'includes/Exif.php',
        'ExplodeIterator' => 'includes/StringUtils.php',
@@ -133,7 +133,7 @@ $wgAutoloadLocalClasses = array(
        'IndexPager' => 'includes/Pager.php',
        'Interwiki' => 'includes/Interwiki.php',
        'IP' => 'includes/IP.php',
-       'Job' => 'includes/JobQueue.php',
+       'Job' => 'includes/job/JobQueue.php',
        'JSMin' => 'includes/JSMin.php',
        'LCStore_DB' => 'includes/LocalisationCache.php',
        'LCStore_CDB' => 'includes/LocalisationCache.php',
@@ -193,8 +193,8 @@ $wgAutoloadLocalClasses = array(
        'RCCacheEntry' => 'includes/ChangesList.php',
        'RdfMetaData' => 'includes/Metadata.php',
        'RecentChange' => 'includes/RecentChange.php',
-       'RefreshLinksJob' => 'includes/RefreshLinksJob.php',
-       'RefreshLinksJob2' => 'includes/RefreshLinksJob.php',
+       'RefreshLinksJob' => 'includes/job/RefreshLinksJob.php',
+       'RefreshLinksJob2' => 'includes/job/RefreshLinksJob.php',
        'RegexlikeReplacer' => 'includes/StringUtils.php',
        'ReplacementArray' => 'includes/StringUtils.php',
        'Replacer' => 'includes/StringUtils.php',
@@ -237,7 +237,7 @@ $wgAutoloadLocalClasses = array(
        'UploadFromStash' => 'includes/upload/UploadFromStash.php',
        'UploadFromFile' => 'includes/upload/UploadFromFile.php',
        'UploadFromUrl' => 'includes/upload/UploadFromUrl.php',
-       'UploadFromUrlJob' => 'includes/UploadFromUrlJob.php',
+       'UploadFromUrlJob' => 'includes/job/UploadFromUrlJob.php',
        'User' => 'includes/User.php',
        'UserArray' => 'includes/UserArray.php',
        'UserArrayFromResult' => 'includes/UserArray.php',
diff --git a/includes/DoubleRedirectJob.php b/includes/DoubleRedirectJob.php
deleted file mode 100644 (file)
index 0857408..0000000
+++ /dev/null
@@ -1,172 +0,0 @@
-<?php
-
-/**
- * Job to fix double redirects after moving a page
- *
- * @ingroup JobQueue
- */
-class DoubleRedirectJob extends Job {
-       var $reason, $redirTitle, $destTitleText;
-       static $user;
-
-       /** 
-        * Insert jobs into the job queue to fix redirects to the given title
-        * @param $reason String: the reason for the fix, see message double-redirect-fixed-<reason>
-        * @param $redirTitle Title: the title which has changed, redirects pointing to this title are fixed
-        * @param $destTitle 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'] );
-               $this->destTitleText = !empty( $params['destTitle'] ) ? $params['destTitle'] : '';
-       }
-
-       function run() {
-               if ( !$this->redirTitle ) {
-                       $this->setLastError( 'Invalid title' );
-                       return false;
-               }
-
-               $targetRev = Revision::newFromTitle( $this->title );
-               if ( !$targetRev ) {
-                       wfDebug( __METHOD__.": target redirect already deleted, ignoring\n" );
-                       return true;
-               }
-               $text = $targetRev->getText();
-               $currentDest = Title::newFromRedirect( $text );
-               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 ( $mw->match( $text ) ) {
-                       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() );
-
-               # Fix the text
-               # Remember that redirect pages can have categories, templates, etc.,
-               # so the regex has to be fairly general
-               $newText = preg_replace( '/ \[ \[  [^\]]*  \] \] /x', 
-                       '[[' . $newTitle->getFullText() . ']]',
-                       $text, 1 );
-
-               if ( $newText === $text ) {
-                       $this->setLastError( 'Text unchanged???' );
-                       return false;
-               }
-
-               # Save it
-               global $wgUser;
-               $oldUser = $wgUser;
-               $wgUser = $this->getUser();
-               $article = new Article( $this->title );
-               $reason = wfMsgForContent( 'double-redirect-fixed-' . $this->reason, 
-                       $this->redirTitle->getPrefixedText(), $newTitle->getPrefixedText() );
-               $article->doEdit( $newText, $reason, EDIT_UPDATE | EDIT_SUPPRESS_RC );
-               $wgUser = $oldUser;
-
-               return true;
-       }
-
-       /**
-        * Get the final destination of a redirect
-        * @return false 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;
-
-                       $row = $dbw->selectRow( 
-                               array( 'redirect', 'page' ),
-                               array( 'rd_namespace', 'rd_title' ),
-                               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 );
-                       }
-               }
-               return $dest;
-       }
-
-       /**
-        * Get a user object for doing edits, from a request-lifetime cache
-        */
-       function getUser() {
-               if ( !self::$user ) {
-                       self::$user = User::newFromName( wfMsgForContent( 'double-redirect-fixer' ), false );
-                       if ( !self::$user->isLoggedIn() ) {
-                               self::$user->addToDatabase();
-                       }
-               }
-               return self::$user;
-       }
-}
-
diff --git a/includes/EmaillingJob.php b/includes/EmaillingJob.php
deleted file mode 100644 (file)
index 380c898..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-<?php
-
-/**
- * Old job used for sending single notification emails;
- * kept for backwards-compatibility
- *
- * @ingroup JobQueue
- */
-class EmaillingJob extends Job {
-
-       function __construct( $title, $params, $id = 0 ) {
-               parent::__construct( 'sendMail', Title::newMainPage(), $params, $id );
-       }
-
-       function run() {
-               userMailer(
-                       $this->params['to'],
-                       $this->params['from'],
-                       $this->params['subj'],
-                       $this->params['body'],
-                       $this->params['replyto']
-               );
-               return true;
-       }
-
-}
diff --git a/includes/EnotifNotifyJob.php b/includes/EnotifNotifyJob.php
deleted file mode 100644 (file)
index f7178d0..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-<?php
-
-/**
- * Job for email notification mails
- *
- * @ingroup JobQueue
- */
-class EnotifNotifyJob extends Job {
-
-       function __construct( $title, $params, $id = 0 ) {
-               parent::__construct( 'enotifNotify', $title, $params, $id );
-       }
-
-       function run() {
-               $enotif = new EmailNotification();
-               // Get the user from ID (rename safe). Anons are 0, so defer to name.
-               if( isset($this->params['editorID']) && $this->params['editorID'] ) {
-                       $editor = User::newFromId( $this->params['editorID'] );
-               // B/C, only the name might be given.
-               } else {
-                       $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/JobQueue.php b/includes/JobQueue.php
deleted file mode 100644 (file)
index 3ae4b8e..0000000
+++ /dev/null
@@ -1,320 +0,0 @@
-<?php
-/**
- * @defgroup JobQueue JobQueue
- */
-
-if ( !defined( 'MEDIAWIKI' ) ) {
-       die( "This file is part of MediaWiki, it is not a valid entry point\n" );
-}
-
-/**
- * Class to both describe a background job and handle jobs.
- *
- * @ingroup JobQueue
- */
-abstract class Job {
-       var $command,
-               $title,
-               $params,
-               $id,
-               $removeDuplicates,
-               $error;
-
-       /*-------------------------------------------------------------------------
-        * Abstract functions
-        *------------------------------------------------------------------------*/
-
-       /**
-        * Run the job
-        * @return boolean success
-        */
-       abstract function run();
-
-       /*-------------------------------------------------------------------------
-        * Static functions
-        *------------------------------------------------------------------------*/
-
-       /**
-        * @deprecated use LinksUpdate::queueRecursiveJobs()
-        */
-       /**
-        * static function queueLinksJobs( $titles ) {}
-        */
-
-       /**
-        * Pop a job of a certain type.  This tries less hard than pop() to
-        * actually find a job; it may be adversely affected by concurrent job
-        * runners.
-        */
-       static function pop_type( $type ) {
-               wfProfilein( __METHOD__ );
-
-               $dbw = wfGetDB( DB_MASTER );
-
-               $row = $dbw->selectRow(
-                       'job',
-                       '*',
-                       array( 'job_cmd' => $type ),
-                       __METHOD__,
-                       array( 'LIMIT' => 1 )
-               );
-
-               if ( $row === false ) {
-                       wfProfileOut( __METHOD__ );
-                       return false;
-               }
-
-               /* Ensure we "own" this row */
-               $dbw->delete( 'job', array( 'job_id' => $row->job_id ), __METHOD__ );
-               $affected = $dbw->affectedRows();
-
-               if ( $affected == 0 ) {
-                       wfProfileOut( __METHOD__ );
-                       return false;
-               }
-
-               $namespace = $row->job_namespace;
-               $dbkey = $row->job_title;
-               $title = Title::makeTitleSafe( $namespace, $dbkey );
-               $job = Job::factory( $row->job_cmd, $title, Job::extractBlob( $row->job_params ),
-                       $row->job_id );
-
-               $dbw->delete( 'job', $job->insertFields(), __METHOD__ );
-               $dbw->commit();
-
-               wfProfileOut( __METHOD__ );
-               return $job;
-       }
-
-       /**
-        * Pop a job off the front of the queue
-        *
-        * @param $offset Integer: Number of jobs to skip
-        * @return Job or false if there's no jobs
-        */
-       static function pop( $offset = 0 ) {
-               wfProfileIn( __METHOD__ );
-
-               $dbr = wfGetDB( DB_SLAVE );
-
-               /* Get a job from the slave, start with an offset,
-                       scan full set afterwards, avoid hitting purged rows
-
-                       NB: If random fetch previously was used, offset
-                               will always be ahead of few entries
-               */
-
-               $row = $dbr->selectRow( 'job', '*', "job_id >= ${offset}", __METHOD__,
-                       array( 'ORDER BY' => 'job_id', 'LIMIT' => 1 ) );
-
-               // Refetching without offset is needed as some of job IDs could have had delayed commits
-               // and have lower IDs than jobs already executed, blame concurrency :)
-               //
-               if ( $row === false ) {
-                       if ( $offset != 0 ) {
-                               $row = $dbr->selectRow( 'job', '*', '', __METHOD__,
-                                       array( 'ORDER BY' => 'job_id', 'LIMIT' => 1 ) );
-                       }
-
-                       if ( $row === false ) {
-                               wfProfileOut( __METHOD__ );
-                               return false;
-                       }
-               }
-               $offset = $row->job_id;
-
-               // Try to delete it from the master
-               $dbw = wfGetDB( DB_MASTER );
-               $dbw->delete( 'job', array( 'job_id' => $row->job_id ), __METHOD__ );
-               $affected = $dbw->affectedRows();
-               $dbw->commit();
-
-               if ( !$affected ) {
-                       // Failed, someone else beat us to it
-                       // Try getting a random row
-                       $row = $dbw->selectRow( 'job', array( 'MIN(job_id) as minjob',
-                               'MAX(job_id) as maxjob' ), '1=1', __METHOD__ );
-                       if ( $row === false || is_null( $row->minjob ) || is_null( $row->maxjob ) ) {
-                               // No jobs to get
-                               wfProfileOut( __METHOD__ );
-                               return false;
-                       }
-                       // Get the random row
-                       $row = $dbw->selectRow( 'job', '*',
-                               'job_id >= ' . mt_rand( $row->minjob, $row->maxjob ), __METHOD__ );
-                       if ( $row === false ) {
-                               // Random job gone before we got the chance to select it
-                               // Give up
-                               wfProfileOut( __METHOD__ );
-                               return false;
-                       }
-                       // Delete the random row
-                       $dbw->delete( 'job', array( 'job_id' => $row->job_id ), __METHOD__ );
-                       $affected = $dbw->affectedRows();
-                       $dbw->commit();
-
-                       if ( !$affected ) {
-                               // Random job gone before we exclusively deleted it
-                               // Give up
-                               wfProfileOut( __METHOD__ );
-                               return false;
-                       }
-               }
-
-               // If execution got to here, there's a row in $row that has been deleted from the database
-               // by this thread. Hence the concurrent pop was successful.
-               $namespace = $row->job_namespace;
-               $dbkey = $row->job_title;
-               $title = Title::makeTitleSafe( $namespace, $dbkey );
-               $job = Job::factory( $row->job_cmd, $title, Job::extractBlob( $row->job_params ), $row->job_id );
-
-               // Remove any duplicates it may have later in the queue
-               // Deadlock prone section
-               $dbw->begin();
-               $dbw->delete( 'job', $job->insertFields(), __METHOD__ );
-               $dbw->commit();
-
-               wfProfileOut( __METHOD__ );
-               return $job;
-       }
-
-       /**
-        * Create the appropriate object to handle a specific job
-        *
-        * @param $command String: Job command
-        * @param $title Title: Associated title
-        * @param $params Array: Job parameters
-        * @param $id Int: Job identifier
-        * @return Job
-        */
-       static function factory( $command, $title, $params = false, $id = 0 ) {
-               global $wgJobClasses;
-               if( isset( $wgJobClasses[$command] ) ) {
-                       $class = $wgJobClasses[$command];
-                       return new $class( $title, $params, $id );
-               }
-               throw new MWException( "Invalid job command `{$command}`" );
-       }
-
-       static function makeBlob( $params ) {
-               if ( $params !== false ) {
-                       return serialize( $params );
-               } else {
-                       return '';
-               }
-       }
-
-       static function extractBlob( $blob ) {
-               if ( (string)$blob !== '' ) {
-                       return unserialize( $blob );
-               } else {
-                       return false;
-               }
-       }
-
-       /**
-        * Batch-insert a group of jobs into the queue.
-        * This will be wrapped in a transaction with a forced commit.
-        *
-        * This may add duplicate at insert time, but they will be
-        * removed later on, when the first one is popped.
-        *
-        * @param $jobs array of Job objects
-        */
-       static function batchInsert( $jobs ) {
-               if( !count( $jobs ) ) {
-                       return;
-               }
-               $dbw = wfGetDB( DB_MASTER );
-               $rows = array();
-               foreach( $jobs as $job ) {
-                       $rows[] = $job->insertFields();
-                       if ( count( $rows ) >= 50 ) {
-                               # Do a small transaction to avoid slave lag
-                               $dbw->begin();
-                               $dbw->insert( 'job', $rows, __METHOD__, 'IGNORE' );
-                               $dbw->commit();
-                               $rows = array();
-                       }
-               }
-               if ( $rows ) {
-                       $dbw->begin();
-                       $dbw->insert( 'job', $rows, __METHOD__, 'IGNORE' );
-                       $dbw->commit();
-               }
-       }
-
-       /*-------------------------------------------------------------------------
-        * Non-static functions
-        *------------------------------------------------------------------------*/
-
-       function __construct( $command, $title, $params = false, $id = 0 ) {
-               $this->command = $command;
-               $this->title = $title;
-               $this->params = $params;
-               $this->id = $id;
-
-               // A bit of premature generalisation
-               // Oh well, the whole class is premature generalisation really
-               $this->removeDuplicates = true;
-       }
-
-       /**
-        * Insert a single job into the queue.
-        * @return bool true on success
-        */
-       function insert() {
-               $fields = $this->insertFields();
-
-               $dbw = wfGetDB( DB_MASTER );
-
-               if ( $this->removeDuplicates ) {
-                       $res = $dbw->select( 'job', array( '1' ), $fields, __METHOD__ );
-                       if ( $dbw->numRows( $res ) ) {
-                               return;
-                       }
-               }
-               return $dbw->insert( 'job', $fields, __METHOD__ );
-       }
-
-       protected function insertFields() {
-               $dbw = wfGetDB( DB_MASTER );
-               return array(
-                       'job_id' => $dbw->nextSequenceValue( 'job_job_id_seq' ),
-                       'job_cmd' => $this->command,
-                       'job_namespace' => $this->title->getNamespace(),
-                       'job_title' => $this->title->getDBkey(),
-                       'job_params' => Job::makeBlob( $this->params )
-               );
-       }
-
-       function toString() {
-               $paramString = '';
-               if ( $this->params ) {
-                       foreach ( $this->params as $key => $value ) {
-                               if ( $paramString != '' ) {
-                                       $paramString .= ' ';
-                               }
-                               $paramString .= "$key=$value";
-                       }
-               }
-
-               if ( is_object( $this->title ) ) {
-                       $s = "{$this->command} " . $this->title->getPrefixedDBkey();
-                       if ( $paramString !== '' ) {
-                               $s .= ' ' . $paramString;
-                       }
-                       return $s;
-               } else {
-                       return "{$this->command} $paramString";
-               }
-       }
-
-       protected function setLastError( $error ) {
-               $this->error = $error;
-       }
-
-       function getLastError() {
-               return $this->error;
-       }
-}
diff --git a/includes/RefreshLinksJob.php b/includes/RefreshLinksJob.php
deleted file mode 100644 (file)
index aba1836..0000000
+++ /dev/null
@@ -1,120 +0,0 @@
-<?php
-
-/**
- * Background job to update links for a given title.
- *
- * @ingroup JobQueue
- */
-class RefreshLinksJob extends Job {
-
-       function __construct( $title, $params = '', $id = 0 ) {
-               parent::__construct( 'refreshLinks', $title, $params, $id );
-       }
-
-       /**
-        * Run a refreshLinks job
-        * @return boolean success
-        */
-       function run() {
-               global $wgParser;
-               wfProfileIn( __METHOD__ );
-
-               $linkCache = LinkCache::singleton();
-               $linkCache->clear();
-
-               if ( is_null( $this->title ) ) {
-                       $this->error = "refreshLinks: Invalid title";
-                       wfProfileOut( __METHOD__ );
-                       return false;
-               }
-
-               $revision = Revision::newFromTitle( $this->title );
-               if ( !$revision ) {
-                       $this->error = 'refreshLinks: Article not found "' . $this->title->getPrefixedDBkey() . '"';
-                       wfProfileOut( __METHOD__ );
-                       return false;
-               }
-
-               wfProfileIn( __METHOD__.'-parse' );
-               $options = new ParserOptions;
-               $parserOutput = $wgParser->parse( $revision->getText(), $this->title, $options, true, true, $revision->getId() );
-               wfProfileOut( __METHOD__.'-parse' );
-               wfProfileIn( __METHOD__.'-update' );
-               $update = new LinksUpdate( $this->title, $parserOutput, false );
-               $update->doUpdate();
-               wfProfileOut( __METHOD__.'-update' );
-               wfProfileOut( __METHOD__ );
-               return true;
-       }
-}
-
-/**
- * Background job to update links for a given title.
- * Newer version for high use templates.
- *
- * @ingroup JobQueue
- */
-class RefreshLinksJob2 extends Job {
-
-       function __construct( $title, $params, $id = 0 ) {
-               parent::__construct( 'refreshLinks2', $title, $params, $id );
-       }
-
-       /**
-        * Run a refreshLinks2 job
-        * @return boolean success
-        */
-       function run() {
-               global $wgParser;
-
-               wfProfileIn( __METHOD__ );
-
-               $linkCache = LinkCache::singleton();
-               $linkCache->clear();
-
-               if( is_null( $this->title ) ) {
-                       $this->error = "refreshLinks2: Invalid title";
-                       wfProfileOut( __METHOD__ );
-                       return false;
-               }
-               if( !isset($this->params['start']) || !isset($this->params['end']) ) {
-                       $this->error = "refreshLinks2: Invalid params";
-                       wfProfileOut( __METHOD__ );
-                       return false;
-               }
-               $titles = $this->title->getBacklinkCache()->getLinks( 
-                       'templatelinks', $this->params['start'], $this->params['end']);
-               
-               # Not suitable for page load triggered job running!
-               # Gracefully switch to refreshLinks jobs if this happens.
-               if( php_sapi_name() != 'cli' ) {
-                       $jobs = array();
-                       foreach ( $titles as $title ) {
-                               $jobs[] = new RefreshLinksJob( $title, '' );
-                       }
-                       Job::batchInsert( $jobs );
-                       return true;
-               }
-               # Re-parse each page that transcludes this page and update their tracking links...
-               foreach ( $titles as $title ) {
-                       $revision = Revision::newFromTitle( $title );
-                       if ( !$revision ) {
-                               $this->error = 'refreshLinks: Article not found "' . $title->getPrefixedDBkey() . '"';
-                               wfProfileOut( __METHOD__ );
-                               return false;
-                       }
-                       wfProfileIn( __METHOD__.'-parse' );
-                       $options = new ParserOptions;
-                       $parserOutput = $wgParser->parse( $revision->getText(), $title, $options, true, true, $revision->getId() );
-                       wfProfileOut( __METHOD__.'-parse' );
-                       wfProfileIn( __METHOD__.'-update' );
-                       $update = new LinksUpdate( $title, $parserOutput, false );
-                       $update->doUpdate();
-                       wfProfileOut( __METHOD__.'-update' );
-                       wfWaitForSlaves( 5 );
-               }
-               wfProfileOut( __METHOD__ );
-
-               return true;
-       }
-}
diff --git a/includes/UploadFromUrlJob.php b/includes/UploadFromUrlJob.php
deleted file mode 100644 (file)
index 7e1d64a..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-<?php
-
-/**
- * Job for email notification mails
- *
- * @ingroup JobQueue
- */
-class UploadFromUrlJob extends Job {
-
-       public function __construct( $title, $params, $id = 0 ) {
-               parent::__construct( 'uploadFromUrl', $title, $params, $id );
-       }
-
-       public function run() {
-               global $wgUser;
-
-               if ( $this->params['userID'] ) {
-                       $wgUser = User::newFromId( $this->params['userID'] );
-               } else {
-                       $wgUser = new User;
-               }
-               $wgUser->mEffectiveGroups[] = 'sysop';
-               $wgUser->mRights = null;
-
-               $upload = new UploadFromUrl();
-               $upload->initializeFromJob( $this );
-
-               return $upload->doUpload();
-       }
-}
diff --git a/includes/job/DoubleRedirectJob.php b/includes/job/DoubleRedirectJob.php
new file mode 100644 (file)
index 0000000..0857408
--- /dev/null
@@ -0,0 +1,172 @@
+<?php
+
+/**
+ * Job to fix double redirects after moving a page
+ *
+ * @ingroup JobQueue
+ */
+class DoubleRedirectJob extends Job {
+       var $reason, $redirTitle, $destTitleText;
+       static $user;
+
+       /** 
+        * Insert jobs into the job queue to fix redirects to the given title
+        * @param $reason String: the reason for the fix, see message double-redirect-fixed-<reason>
+        * @param $redirTitle Title: the title which has changed, redirects pointing to this title are fixed
+        * @param $destTitle 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'] );
+               $this->destTitleText = !empty( $params['destTitle'] ) ? $params['destTitle'] : '';
+       }
+
+       function run() {
+               if ( !$this->redirTitle ) {
+                       $this->setLastError( 'Invalid title' );
+                       return false;
+               }
+
+               $targetRev = Revision::newFromTitle( $this->title );
+               if ( !$targetRev ) {
+                       wfDebug( __METHOD__.": target redirect already deleted, ignoring\n" );
+                       return true;
+               }
+               $text = $targetRev->getText();
+               $currentDest = Title::newFromRedirect( $text );
+               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 ( $mw->match( $text ) ) {
+                       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() );
+
+               # Fix the text
+               # Remember that redirect pages can have categories, templates, etc.,
+               # so the regex has to be fairly general
+               $newText = preg_replace( '/ \[ \[  [^\]]*  \] \] /x', 
+                       '[[' . $newTitle->getFullText() . ']]',
+                       $text, 1 );
+
+               if ( $newText === $text ) {
+                       $this->setLastError( 'Text unchanged???' );
+                       return false;
+               }
+
+               # Save it
+               global $wgUser;
+               $oldUser = $wgUser;
+               $wgUser = $this->getUser();
+               $article = new Article( $this->title );
+               $reason = wfMsgForContent( 'double-redirect-fixed-' . $this->reason, 
+                       $this->redirTitle->getPrefixedText(), $newTitle->getPrefixedText() );
+               $article->doEdit( $newText, $reason, EDIT_UPDATE | EDIT_SUPPRESS_RC );
+               $wgUser = $oldUser;
+
+               return true;
+       }
+
+       /**
+        * Get the final destination of a redirect
+        * @return false 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;
+
+                       $row = $dbw->selectRow( 
+                               array( 'redirect', 'page' ),
+                               array( 'rd_namespace', 'rd_title' ),
+                               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 );
+                       }
+               }
+               return $dest;
+       }
+
+       /**
+        * Get a user object for doing edits, from a request-lifetime cache
+        */
+       function getUser() {
+               if ( !self::$user ) {
+                       self::$user = User::newFromName( wfMsgForContent( 'double-redirect-fixer' ), false );
+                       if ( !self::$user->isLoggedIn() ) {
+                               self::$user->addToDatabase();
+                       }
+               }
+               return self::$user;
+       }
+}
+
diff --git a/includes/job/EmaillingJob.php b/includes/job/EmaillingJob.php
new file mode 100644 (file)
index 0000000..380c898
--- /dev/null
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * Old job used for sending single notification emails;
+ * kept for backwards-compatibility
+ *
+ * @ingroup JobQueue
+ */
+class EmaillingJob extends Job {
+
+       function __construct( $title, $params, $id = 0 ) {
+               parent::__construct( 'sendMail', Title::newMainPage(), $params, $id );
+       }
+
+       function run() {
+               userMailer(
+                       $this->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
new file mode 100644 (file)
index 0000000..f7178d0
--- /dev/null
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * Job for email notification mails
+ *
+ * @ingroup JobQueue
+ */
+class EnotifNotifyJob extends Job {
+
+       function __construct( $title, $params, $id = 0 ) {
+               parent::__construct( 'enotifNotify', $title, $params, $id );
+       }
+
+       function run() {
+               $enotif = new EmailNotification();
+               // Get the user from ID (rename safe). Anons are 0, so defer to name.
+               if( isset($this->params['editorID']) && $this->params['editorID'] ) {
+                       $editor = User::newFromId( $this->params['editorID'] );
+               // B/C, only the name might be given.
+               } else {
+                       $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/JobQueue.php b/includes/job/JobQueue.php
new file mode 100644 (file)
index 0000000..3ae4b8e
--- /dev/null
@@ -0,0 +1,320 @@
+<?php
+/**
+ * @defgroup JobQueue JobQueue
+ */
+
+if ( !defined( 'MEDIAWIKI' ) ) {
+       die( "This file is part of MediaWiki, it is not a valid entry point\n" );
+}
+
+/**
+ * Class to both describe a background job and handle jobs.
+ *
+ * @ingroup JobQueue
+ */
+abstract class Job {
+       var $command,
+               $title,
+               $params,
+               $id,
+               $removeDuplicates,
+               $error;
+
+       /*-------------------------------------------------------------------------
+        * Abstract functions
+        *------------------------------------------------------------------------*/
+
+       /**
+        * Run the job
+        * @return boolean success
+        */
+       abstract function run();
+
+       /*-------------------------------------------------------------------------
+        * Static functions
+        *------------------------------------------------------------------------*/
+
+       /**
+        * @deprecated use LinksUpdate::queueRecursiveJobs()
+        */
+       /**
+        * static function queueLinksJobs( $titles ) {}
+        */
+
+       /**
+        * Pop a job of a certain type.  This tries less hard than pop() to
+        * actually find a job; it may be adversely affected by concurrent job
+        * runners.
+        */
+       static function pop_type( $type ) {
+               wfProfilein( __METHOD__ );
+
+               $dbw = wfGetDB( DB_MASTER );
+
+               $row = $dbw->selectRow(
+                       'job',
+                       '*',
+                       array( 'job_cmd' => $type ),
+                       __METHOD__,
+                       array( 'LIMIT' => 1 )
+               );
+
+               if ( $row === false ) {
+                       wfProfileOut( __METHOD__ );
+                       return false;
+               }
+
+               /* Ensure we "own" this row */
+               $dbw->delete( 'job', array( 'job_id' => $row->job_id ), __METHOD__ );
+               $affected = $dbw->affectedRows();
+
+               if ( $affected == 0 ) {
+                       wfProfileOut( __METHOD__ );
+                       return false;
+               }
+
+               $namespace = $row->job_namespace;
+               $dbkey = $row->job_title;
+               $title = Title::makeTitleSafe( $namespace, $dbkey );
+               $job = Job::factory( $row->job_cmd, $title, Job::extractBlob( $row->job_params ),
+                       $row->job_id );
+
+               $dbw->delete( 'job', $job->insertFields(), __METHOD__ );
+               $dbw->commit();
+
+               wfProfileOut( __METHOD__ );
+               return $job;
+       }
+
+       /**
+        * Pop a job off the front of the queue
+        *
+        * @param $offset Integer: Number of jobs to skip
+        * @return Job or false if there's no jobs
+        */
+       static function pop( $offset = 0 ) {
+               wfProfileIn( __METHOD__ );
+
+               $dbr = wfGetDB( DB_SLAVE );
+
+               /* Get a job from the slave, start with an offset,
+                       scan full set afterwards, avoid hitting purged rows
+
+                       NB: If random fetch previously was used, offset
+                               will always be ahead of few entries
+               */
+
+               $row = $dbr->selectRow( 'job', '*', "job_id >= ${offset}", __METHOD__,
+                       array( 'ORDER BY' => 'job_id', 'LIMIT' => 1 ) );
+
+               // Refetching without offset is needed as some of job IDs could have had delayed commits
+               // and have lower IDs than jobs already executed, blame concurrency :)
+               //
+               if ( $row === false ) {
+                       if ( $offset != 0 ) {
+                               $row = $dbr->selectRow( 'job', '*', '', __METHOD__,
+                                       array( 'ORDER BY' => 'job_id', 'LIMIT' => 1 ) );
+                       }
+
+                       if ( $row === false ) {
+                               wfProfileOut( __METHOD__ );
+                               return false;
+                       }
+               }
+               $offset = $row->job_id;
+
+               // Try to delete it from the master
+               $dbw = wfGetDB( DB_MASTER );
+               $dbw->delete( 'job', array( 'job_id' => $row->job_id ), __METHOD__ );
+               $affected = $dbw->affectedRows();
+               $dbw->commit();
+
+               if ( !$affected ) {
+                       // Failed, someone else beat us to it
+                       // Try getting a random row
+                       $row = $dbw->selectRow( 'job', array( 'MIN(job_id) as minjob',
+                               'MAX(job_id) as maxjob' ), '1=1', __METHOD__ );
+                       if ( $row === false || is_null( $row->minjob ) || is_null( $row->maxjob ) ) {
+                               // No jobs to get
+                               wfProfileOut( __METHOD__ );
+                               return false;
+                       }
+                       // Get the random row
+                       $row = $dbw->selectRow( 'job', '*',
+                               'job_id >= ' . mt_rand( $row->minjob, $row->maxjob ), __METHOD__ );
+                       if ( $row === false ) {
+                               // Random job gone before we got the chance to select it
+                               // Give up
+                               wfProfileOut( __METHOD__ );
+                               return false;
+                       }
+                       // Delete the random row
+                       $dbw->delete( 'job', array( 'job_id' => $row->job_id ), __METHOD__ );
+                       $affected = $dbw->affectedRows();
+                       $dbw->commit();
+
+                       if ( !$affected ) {
+                               // Random job gone before we exclusively deleted it
+                               // Give up
+                               wfProfileOut( __METHOD__ );
+                               return false;
+                       }
+               }
+
+               // If execution got to here, there's a row in $row that has been deleted from the database
+               // by this thread. Hence the concurrent pop was successful.
+               $namespace = $row->job_namespace;
+               $dbkey = $row->job_title;
+               $title = Title::makeTitleSafe( $namespace, $dbkey );
+               $job = Job::factory( $row->job_cmd, $title, Job::extractBlob( $row->job_params ), $row->job_id );
+
+               // Remove any duplicates it may have later in the queue
+               // Deadlock prone section
+               $dbw->begin();
+               $dbw->delete( 'job', $job->insertFields(), __METHOD__ );
+               $dbw->commit();
+
+               wfProfileOut( __METHOD__ );
+               return $job;
+       }
+
+       /**
+        * Create the appropriate object to handle a specific job
+        *
+        * @param $command String: Job command
+        * @param $title Title: Associated title
+        * @param $params Array: Job parameters
+        * @param $id Int: Job identifier
+        * @return Job
+        */
+       static function factory( $command, $title, $params = false, $id = 0 ) {
+               global $wgJobClasses;
+               if( isset( $wgJobClasses[$command] ) ) {
+                       $class = $wgJobClasses[$command];
+                       return new $class( $title, $params, $id );
+               }
+               throw new MWException( "Invalid job command `{$command}`" );
+       }
+
+       static function makeBlob( $params ) {
+               if ( $params !== false ) {
+                       return serialize( $params );
+               } else {
+                       return '';
+               }
+       }
+
+       static function extractBlob( $blob ) {
+               if ( (string)$blob !== '' ) {
+                       return unserialize( $blob );
+               } else {
+                       return false;
+               }
+       }
+
+       /**
+        * Batch-insert a group of jobs into the queue.
+        * This will be wrapped in a transaction with a forced commit.
+        *
+        * This may add duplicate at insert time, but they will be
+        * removed later on, when the first one is popped.
+        *
+        * @param $jobs array of Job objects
+        */
+       static function batchInsert( $jobs ) {
+               if( !count( $jobs ) ) {
+                       return;
+               }
+               $dbw = wfGetDB( DB_MASTER );
+               $rows = array();
+               foreach( $jobs as $job ) {
+                       $rows[] = $job->insertFields();
+                       if ( count( $rows ) >= 50 ) {
+                               # Do a small transaction to avoid slave lag
+                               $dbw->begin();
+                               $dbw->insert( 'job', $rows, __METHOD__, 'IGNORE' );
+                               $dbw->commit();
+                               $rows = array();
+                       }
+               }
+               if ( $rows ) {
+                       $dbw->begin();
+                       $dbw->insert( 'job', $rows, __METHOD__, 'IGNORE' );
+                       $dbw->commit();
+               }
+       }
+
+       /*-------------------------------------------------------------------------
+        * Non-static functions
+        *------------------------------------------------------------------------*/
+
+       function __construct( $command, $title, $params = false, $id = 0 ) {
+               $this->command = $command;
+               $this->title = $title;
+               $this->params = $params;
+               $this->id = $id;
+
+               // A bit of premature generalisation
+               // Oh well, the whole class is premature generalisation really
+               $this->removeDuplicates = true;
+       }
+
+       /**
+        * Insert a single job into the queue.
+        * @return bool true on success
+        */
+       function insert() {
+               $fields = $this->insertFields();
+
+               $dbw = wfGetDB( DB_MASTER );
+
+               if ( $this->removeDuplicates ) {
+                       $res = $dbw->select( 'job', array( '1' ), $fields, __METHOD__ );
+                       if ( $dbw->numRows( $res ) ) {
+                               return;
+                       }
+               }
+               return $dbw->insert( 'job', $fields, __METHOD__ );
+       }
+
+       protected function insertFields() {
+               $dbw = wfGetDB( DB_MASTER );
+               return array(
+                       'job_id' => $dbw->nextSequenceValue( 'job_job_id_seq' ),
+                       'job_cmd' => $this->command,
+                       'job_namespace' => $this->title->getNamespace(),
+                       'job_title' => $this->title->getDBkey(),
+                       'job_params' => Job::makeBlob( $this->params )
+               );
+       }
+
+       function toString() {
+               $paramString = '';
+               if ( $this->params ) {
+                       foreach ( $this->params as $key => $value ) {
+                               if ( $paramString != '' ) {
+                                       $paramString .= ' ';
+                               }
+                               $paramString .= "$key=$value";
+                       }
+               }
+
+               if ( is_object( $this->title ) ) {
+                       $s = "{$this->command} " . $this->title->getPrefixedDBkey();
+                       if ( $paramString !== '' ) {
+                               $s .= ' ' . $paramString;
+                       }
+                       return $s;
+               } else {
+                       return "{$this->command} $paramString";
+               }
+       }
+
+       protected function setLastError( $error ) {
+               $this->error = $error;
+       }
+
+       function getLastError() {
+               return $this->error;
+       }
+}
diff --git a/includes/job/RefreshLinksJob.php b/includes/job/RefreshLinksJob.php
new file mode 100644 (file)
index 0000000..aba1836
--- /dev/null
@@ -0,0 +1,120 @@
+<?php
+
+/**
+ * Background job to update links for a given title.
+ *
+ * @ingroup JobQueue
+ */
+class RefreshLinksJob extends Job {
+
+       function __construct( $title, $params = '', $id = 0 ) {
+               parent::__construct( 'refreshLinks', $title, $params, $id );
+       }
+
+       /**
+        * Run a refreshLinks job
+        * @return boolean success
+        */
+       function run() {
+               global $wgParser;
+               wfProfileIn( __METHOD__ );
+
+               $linkCache = LinkCache::singleton();
+               $linkCache->clear();
+
+               if ( is_null( $this->title ) ) {
+                       $this->error = "refreshLinks: Invalid title";
+                       wfProfileOut( __METHOD__ );
+                       return false;
+               }
+
+               $revision = Revision::newFromTitle( $this->title );
+               if ( !$revision ) {
+                       $this->error = 'refreshLinks: Article not found "' . $this->title->getPrefixedDBkey() . '"';
+                       wfProfileOut( __METHOD__ );
+                       return false;
+               }
+
+               wfProfileIn( __METHOD__.'-parse' );
+               $options = new ParserOptions;
+               $parserOutput = $wgParser->parse( $revision->getText(), $this->title, $options, true, true, $revision->getId() );
+               wfProfileOut( __METHOD__.'-parse' );
+               wfProfileIn( __METHOD__.'-update' );
+               $update = new LinksUpdate( $this->title, $parserOutput, false );
+               $update->doUpdate();
+               wfProfileOut( __METHOD__.'-update' );
+               wfProfileOut( __METHOD__ );
+               return true;
+       }
+}
+
+/**
+ * Background job to update links for a given title.
+ * Newer version for high use templates.
+ *
+ * @ingroup JobQueue
+ */
+class RefreshLinksJob2 extends Job {
+
+       function __construct( $title, $params, $id = 0 ) {
+               parent::__construct( 'refreshLinks2', $title, $params, $id );
+       }
+
+       /**
+        * Run a refreshLinks2 job
+        * @return boolean success
+        */
+       function run() {
+               global $wgParser;
+
+               wfProfileIn( __METHOD__ );
+
+               $linkCache = LinkCache::singleton();
+               $linkCache->clear();
+
+               if( is_null( $this->title ) ) {
+                       $this->error = "refreshLinks2: Invalid title";
+                       wfProfileOut( __METHOD__ );
+                       return false;
+               }
+               if( !isset($this->params['start']) || !isset($this->params['end']) ) {
+                       $this->error = "refreshLinks2: Invalid params";
+                       wfProfileOut( __METHOD__ );
+                       return false;
+               }
+               $titles = $this->title->getBacklinkCache()->getLinks( 
+                       'templatelinks', $this->params['start'], $this->params['end']);
+               
+               # Not suitable for page load triggered job running!
+               # Gracefully switch to refreshLinks jobs if this happens.
+               if( php_sapi_name() != 'cli' ) {
+                       $jobs = array();
+                       foreach ( $titles as $title ) {
+                               $jobs[] = new RefreshLinksJob( $title, '' );
+                       }
+                       Job::batchInsert( $jobs );
+                       return true;
+               }
+               # Re-parse each page that transcludes this page and update their tracking links...
+               foreach ( $titles as $title ) {
+                       $revision = Revision::newFromTitle( $title );
+                       if ( !$revision ) {
+                               $this->error = 'refreshLinks: Article not found "' . $title->getPrefixedDBkey() . '"';
+                               wfProfileOut( __METHOD__ );
+                               return false;
+                       }
+                       wfProfileIn( __METHOD__.'-parse' );
+                       $options = new ParserOptions;
+                       $parserOutput = $wgParser->parse( $revision->getText(), $title, $options, true, true, $revision->getId() );
+                       wfProfileOut( __METHOD__.'-parse' );
+                       wfProfileIn( __METHOD__.'-update' );
+                       $update = new LinksUpdate( $title, $parserOutput, false );
+                       $update->doUpdate();
+                       wfProfileOut( __METHOD__.'-update' );
+                       wfWaitForSlaves( 5 );
+               }
+               wfProfileOut( __METHOD__ );
+
+               return true;
+       }
+}
diff --git a/includes/job/UploadFromUrlJob.php b/includes/job/UploadFromUrlJob.php
new file mode 100644 (file)
index 0000000..7e1d64a
--- /dev/null
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * Job for email notification mails
+ *
+ * @ingroup JobQueue
+ */
+class UploadFromUrlJob extends Job {
+
+       public function __construct( $title, $params, $id = 0 ) {
+               parent::__construct( 'uploadFromUrl', $title, $params, $id );
+       }
+
+       public function run() {
+               global $wgUser;
+
+               if ( $this->params['userID'] ) {
+                       $wgUser = User::newFromId( $this->params['userID'] );
+               } else {
+                       $wgUser = new User;
+               }
+               $wgUser->mEffectiveGroups[] = 'sysop';
+               $wgUser->mRights = null;
+
+               $upload = new UploadFromUrl();
+               $upload->initializeFromJob( $this );
+
+               return $upload->doUpload();
+       }
+}