Thành viên:Nguyenthephuc/Note: Kĩ thuật Job trong Mediawiki

Từ VLOS
Bước tới: chuyển hướng, tìm kiếm

CHÚ Ý: Từ mw 1.21, cách thức sử dụng lớp Job có thay đổi. Chi tiết xem: mw:Manual:Job_queue/For_developers

Kĩ thuật Job và ứng dụng[sửa]

Kĩ thuật cho phép server xử lí các thao tác theo kiểu hàng đợi việc. Server sẽ thực thi các yêu cầu khi server không quá bận. Do vậy, kĩ thuật thường dùng để thực thi các công việc có tính hàng loạt.

Ứng dụng:

Cách viết phần mở rộng Job[sửa]

Có thể chia làm 2 file: MyJob.php và MyExt.php

Nội dung file MyJob.php

class MyJob extends Job {
 
	function __construct( $title, $params, $id = 0 ) {//Job sẽ làm việc gì đó params với title
		parent::__construct( 'myJob', $title, $params, $id );//"myJob" tên Job đăng kí Hook
	}
 
	function run() {
                //do something
		return true;//trả về true nếu thành công, trả về false nếu thất bại
	}
}

Nội dung file MyExt.php

	$wgJobClasses['myJob'] = 'MyJob';//đăng ký một Job với tên là 'myJob' và class thực thi Job đó là 'MyJob'
	$wgAutoloadClasses['MyJob'] = $dir . 'MyJob.php';//nạp class
	//Triệu gọi
        //Tạo tham số đầu vào params
	$jobs = array();
		foreach ( $res as $row ) {
			$title = Title::makeTitle( $row->page_namespace, $row->page_title );
			if ( !$title ) {
				continue;
			}
 
			$jobs[] = new MyJob( $title, $params );
			# Avoid excessive memory usage
			if ( count( $jobs ) > 10000 ) {
				Job::batchInsert( $jobs );//xếp Job vào hàng đợi
				$jobs = array();
			}
		}
		Job::batchInsert( $jobs );
	}

Các ví dụ[sửa]

includes\DoubleRedirectJob.php[sửa]

/**
 * 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;
	}
}

includes\EmaillingJob.php[sửa]

/**
 * 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;
	}
 
}

includes\EnotifNotifyJob.php[sửa]

/**
 * 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;
	}
}

includes\HTMLCacheUpdate.php[sửa]

/**
 * Class to invalidate the HTML cache of all the pages linking to a given title.
 * Small numbers of links will be done immediately, large numbers are pushed onto
 * the job queue.
 *
 * This class is designed to work efficiently with small numbers of links, and
 * to work reasonably well with up to ~10^5 links. Above ~10^6 links, the memory
 * and time requirements of loading all backlinked IDs in doUpdate() might become
 * prohibitive. The requirements measured at Wikimedia are approximately:
 *
 *   memory: 48 bytes per row
 *   time: 16us per row for the query plus processing
 *
 * The reason this query is done is to support partitioning of the job
 * by backlinked ID. The memory issue could be allieviated by doing this query in
 * batches, but of course LIMIT with an offset is inefficient on the DB side.
 *
 * The class is nevertheless a vast improvement on the previous method of using
 * Image::getLinksTo() and Title::touchArray(), which uses about 2KB of memory per
 * link.
 *
 * @ingroup Cache
 */
class HTMLCacheUpdate
{
	public $mTitle, $mTable, $mPrefix, $mStart, $mEnd;
	public $mRowsPerJob, $mRowsPerQuery;
 
	function __construct( $titleTo, $table, $start = false, $end = false ) {
		global $wgUpdateRowsPerJob, $wgUpdateRowsPerQuery;
 
		$this->mTitle = $titleTo;
		$this->mTable = $table;
		$this->mStart = $start;
		$this->mEnd = $end;
		$this->mRowsPerJob = $wgUpdateRowsPerJob;
		$this->mRowsPerQuery = $wgUpdateRowsPerQuery;
		$this->mCache = $this->mTitle->getBacklinkCache();
	}
 
	public function doUpdate() {
		if ( $this->mStart || $this->mEnd ) {
			$this->doPartialUpdate();
			return;
		}
 
		# Get an estimate of the number of rows from the BacklinkCache
		$numRows = $this->mCache->getNumLinks( $this->mTable );
		if ( $numRows > $this->mRowsPerJob * 2 ) {
			# Do fast cached partition
			$this->insertJobs();
		} else {
			# Get the links from the DB
			$titleArray = $this->mCache->getLinks( $this->mTable );
			# Check if the row count estimate was correct
			if ( $titleArray->count() > $this->mRowsPerJob * 2 ) {
				# Not correct, do accurate partition
				wfDebug( __METHOD__.": row count estimate was incorrect, repartitioning\n" );
				$this->insertJobsFromTitles( $titleArray );
			} else {
				$this->invalidateTitles( $titleArray );
			}
		}
		wfRunHooks( 'HTMLCacheUpdate::doUpdate', array($this->mTitle) );
	}
 
	/**
	 * Update some of the backlinks, defined by a page ID range
	 */
	protected function doPartialUpdate() {
		$titleArray = $this->mCache->getLinks( $this->mTable, $this->mStart, $this->mEnd );
		if ( $titleArray->count() <= $this->mRowsPerJob * 2 ) {
			# This partition is small enough, do the update
			$this->invalidateTitles( $titleArray );
		} else {
			# Partitioning was excessively inaccurate. Divide the job further.
			# This can occur when a large number of links are added in a short 
			# period of time, say by updating a heavily-used template.
			$this->insertJobsFromTitles( $titleArray );
		}
	}
 
	/**
	 * Partition the current range given by $this->mStart and $this->mEnd,
	 * using a pre-calculated title array which gives the links in that range.
	 * Queue the resulting jobs.
	 */
	protected function insertJobsFromTitles( $titleArray ) {
		# We make subpartitions in the sense that the start of the first job
		# will be the start of the parent partition, and the end of the last
		# job will be the end of the parent partition.
		$jobs = array();
		$start = $this->mStart; # start of the current job
		$numTitles = 0;
		foreach ( $titleArray as $title ) {
			$id = $title->getArticleID();
			# $numTitles is now the number of titles in the current job not 
			# including the current ID
			if ( $numTitles >= $this->mRowsPerJob ) {
				# Add a job up to but not including the current ID
				$params = array(
					'table' => $this->mTable,
					'start' => $start,
					'end' => $id - 1
				);
				$jobs[] = new HTMLCacheUpdateJob( $this->mTitle, $params );
				$start = $id;
				$numTitles = 0;
			}
			$numTitles++;
		}
		# Last job
		$params = array(
			'table' => $this->mTable,
			'start' => $start,
			'end' => $this->mEnd
		);
		$jobs[] = new HTMLCacheUpdateJob( $this->mTitle, $params );
		wfDebug( __METHOD__.": repartitioning into " . count( $jobs ) . " jobs\n" );
 
		if ( count( $jobs ) < 2 ) {
			# I don't think this is possible at present, but handling this case
			# makes the code a bit more robust against future code updates and 
			# avoids a potential infinite loop of repartitioning
			wfDebug( __METHOD__.": repartitioning failed!\n" );
			$this->invalidateTitles( $titleArray );
			return;
		}
 
		Job::batchInsert( $jobs );
	}
 
	protected function insertJobs() {
		$batches = $this->mCache->partition( $this->mTable, $this->mRowsPerJob );
		if ( !$batches ) {
			return;
		}
		$jobs = array();
		foreach ( $batches as $batch ) {
			$params = array(
				'table' => $this->mTable,
				'start' => $batch[0],
				'end' => $batch[1],
			);
			$jobs[] = new HTMLCacheUpdateJob( $this->mTitle, $params );
		}
		Job::batchInsert( $jobs );
	}
 
	/**
	 * Invalidate a range of pages, right now
	 * @deprecated
	 */
	public function invalidate( $startId = false, $endId = false ) {
		$titleArray = $this->mCache->getLinks( $this->mTable, $startId, $endId );
		$this->invalidateTitles( $titleArray );
	}
 
	/**
	 * Invalidate an array (or iterator) of Title objects, right now
	 */
	protected function invalidateTitles( $titleArray ) {
		global $wgUseFileCache, $wgUseSquid;
 
		$dbw = wfGetDB( DB_MASTER );
		$timestamp = $dbw->timestamp();
 
		# Get all IDs in this query into an array
		$ids = array();
		foreach ( $titleArray as $title ) {
			$ids[] = $title->getArticleID();
		}
 
		if ( !$ids ) {
			return;
		}
 
		# Update page_touched
		$batches = array_chunk( $ids, $this->mRowsPerQuery );
		foreach ( $batches as $batch ) {
			$dbw->update( 'page',
				array( 'page_touched' => $timestamp ),
				array( 'page_id IN (' . $dbw->makeList( $batch ) . ')' ),
				__METHOD__
			);
		}
 
		# Update squid
		if ( $wgUseSquid ) {
			$u = SquidUpdate::newFromTitles( $titleArray );
			$u->doUpdate();
		}
 
		# Update file cache
		if  ( $wgUseFileCache ) {
			foreach ( $titleArray as $title ) {
				HTMLFileCache::clearFileCache( $title );
			}
		}
	}
 
}
 
/**
 * 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;
	}
}

includes\RefreshLinksJob.php[sửa]

/**
 * 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;
	}
}

Tham khảo extensions\ReplaceText\ReplaceText.php[sửa]

# File: ReplaceText.php

$wgAutoloadClasses['ReplaceTextJob'] = $rtgIP . 'ReplaceTextJob.php';
$wgJobClasses['replaceText'] = 'ReplaceTextJob';
 
# File: ReplaceTextJob.php

/**
 * Background job to replace text in a given page
 * - based on /includes/RefreshLinksJob.php
 *
 * @author Yaron Koren
 * @author Ankit Garg
 */
class ReplaceTextJob extends Job {
 
	function __construct( $title, $params = '', $id = 0 ) {
		parent::__construct( 'replaceText', $title, $params, $id );
	}
 
	/**
	 * Run a replaceText job
	 * @return boolean success
	 */
	function run() {
		wfProfileIn( __METHOD__ );
 
		if ( is_null( $this->title ) ) {
			$this->error = "replaceText: Invalid title";
			wfProfileOut( __METHOD__ );
			return false;
		}
 
		if ( array_key_exists( 'move_page', $this->params ) ) {
			global $wgUser;
			$actual_user = $wgUser;
			$wgUser = User::newFromId( $this->params['user_id'] );
			$cur_page_name = $this->title->getText();
			if ( $this->params['use_regex'] ) {
				$new_page_name = preg_replace( "/".$this->params['target_str']."/U", $this->params['replacement_str'], $cur_page_name );
			} else {
				$new_page_name = str_replace( $this->params['target_str'], $this->params['replacement_str'], $cur_page_name );
			}
 
			$new_title = Title::newFromText( $new_page_name, $this->title->getNamespace() );
			$reason = $this->params['edit_summary'];
			$create_redirect = $this->params['create_redirect'];
			$this->title->moveTo( $new_title, true, $reason, $create_redirect );
			if ( $this->params['watch_page'] ) {
				$article = new Article( $new_title );
				$article->doWatch();
			}
			$wgUser = $actual_user;
		} else {
			$article = new Article( $this->title );
			if ( !$article ) {
				$this->error = 'replaceText: Article not found "' . $this->title->getPrefixedDBkey() . '"';
				wfProfileOut( __METHOD__ );
				return false;
			}
 
			wfProfileIn( __METHOD__ . '-replace' );
			$article_text = $article->fetchContent();
			$target_str = $this->params['target_str'];
			$replacement_str = $this->params['replacement_str'];
			$num_matches;
 
			if ( $this->params['use_regex'] ) {
				$new_text = preg_replace( '/'.$target_str.'/U', $replacement_str, $article_text, -1, $num_matches );
			} else {
				$new_text = str_replace( $target_str, $replacement_str, $article_text, $num_matches );
			}
 
			// if there's at least one replacement, modify the page,
			// using the passed-in edit summary
			if ( $num_matches > 0 ) {
				// change global $wgUser variable to the one
				// specified by the job only for the extent of
				// this replacement
				global $wgUser;
				$actual_user = $wgUser;
				$wgUser = User::newFromId( $this->params['user_id'] );
				$edit_summary = $this->params['edit_summary'];
				$flags = EDIT_MINOR;
				//if ( $wgUser->isAllowed( 'bot' ) )
					$flags |= EDIT_FORCE_BOT;
				$article->doEdit( $new_text, $edit_summary, $flags );
				$wgUser = $actual_user;
			}
			wfProfileOut( __METHOD__ . '-replace' );
		}
		wfProfileOut( __METHOD__ );
		return true;
	}
}
//Cách gọi Job
			$jobs = array();
			foreach ( $wgRequest->getValues() as $key => $value ) {
				if ( $value == '1' && $key !== 'replace' ) {
					if ( strpos( $key, 'move-' ) !== false ) {
						$title = Title::newFromID( substr( $key, 5 ) );
						$replacement_params['move_page'] = true;
					} else {
						$title = Title::newFromID( $key );
					}
					if ( $title !== null )
						$jobs[] = new ReplaceTextJob( $title, $replacement_params );
				}
			}
			Job::batchInsert( $jobs );
 
			$count = $wgLang->formatNum( count( $jobs ) );
			$wgOut->addWikiMsg( 'replacetext_success', "<tt><nowiki>{$this->target}</nowiki></tt>", "<tt><nowiki>{$this->replacement}</nowiki></tt>", $count );

Tham khảo extensions\RenameUser\SpecialRenameuser.php[sửa]

# File: SpecialRenameuser.php

$wgAutoloadClasses['RenameUserJob'] = dirname(__FILE__) . '/RenameUserJob.php';
$wgJobClasses['renameUser'] = 'RenameUserJob';
 
# File: RenameUserJob.php
/**
 * Custom job to perform updates on tables in busier environments
 */
class RenameUserJob extends Job {
 
	/**
	 * Constructor
	 *
	 * @param Title $title Associated title
	 * @param array $params Job parameters
	 */
	public function __construct( $title, $params ) {
		parent::__construct( 'renameUser', $title, $params );
	}
 
	/**
	 * Execute the job
	 *
	 * @return bool
	 */
	public function run() {
		$dbw = wfGetDB( DB_MASTER );
		extract( $this->params );
		# Conditions like "*_user_text = 'x'
		$conds = array( $column => $oldname );
		# If user ID given, add that to condition to avoid rename collisions.
		if( isset($userID) ) {
			$conds[$uidColumn] = $userID;
		}
		# Bound by timestamp if given
		if( isset($timestampColumn) ) {
			$conds[] = "$timestampColumn >= '$minTimestamp'";
			$conds[] = "$timestampColumn <= '$maxTimestamp'";
		# Otherwise, bound by key (B/C)
		} else if( isset($uniqueKey) ) {
			$conds[$uniqueKey] = $keyId;
		} else {
			wfDebug( 'RenameUserJob::run - invalid job row given' ); // this shouldn't happen
			return false;
		}
		# Update a chuck of rows!
		$dbw->update( $table,
			array( $column => $newname ),
			$conds,
			__METHOD__
		);
		# Special case: revisions may be deleted while renaming...
		if( $table == 'revision' && isset($timestampColumn) ) {
			$actual = $dbw->affectedRows();
			# If some revisions were not renamed, they may have been deleted.
			# Do a pass on the archive table to get these straglers...
			if( $actual < $count ) {
				$dbw->update( 'archive',
					array( 'ar_user_text' => $newname ),
					array( 'ar_user_text' => $oldname,
						'ar_user' => $userID,
						// No user,rev_id index, so use timestamp to bound
						// the rows. This can use the user,timestamp index.
						"ar_timestamp >= '$minTimestamp'",
						"ar_timestamp <= '$maxTimestamp'"),
					__METHOD__
				);
			}
		}
		# Special case: revisions may be restored while renaming...
		if( $table == 'archive' && isset($timestampColumn) ) {
			$actual = $dbw->affectedRows();
			# If some revisions were not renamed, they may have been restored.
			# Do a pass on the revision table to get these straglers...
			if( $actual < $count ) {
				$dbw->update( 'revision',
					array( 'rev_user_text' => $newname ),
					array( 'rev_user_text' => $oldname,
						'rev_user' => $userID,
						// No user,rev_id index, so use timestamp to bound
						// the rows. This can use the user,timestamp index.
						"rev_timestamp >= '$minTimestamp'",
						"rev_timestamp <= '$maxTimestamp'"),
					__METHOD__
				);
			}
		}
		return true;
	}
 
}

Tham khảo extensions\vlos\namhoc[sửa]

file Namhoc.php

<?php
# Alert the user that this is not a valid entry point to MediaWiki if they try to access the special pages file directly.
if (!defined('MEDIAWIKI')) {
        echo <<<EOT
To install my extension, put the following line in LocalSettings.php:
require_once( "\$IP/extensions/vlos/namhoc/Namhoc.php" );
EOT;
        exit( 1 );
}
$wgExtensionCredits['specialpage'][] = array(
        'name' => 'Namhoc',
        'author' => 'Nguyen The Phuc',
        'url' => 'http://www.thuvienkhoahoc.com',
        'description' => 'Default description message',
        'descriptionmsg' => 'namhoc-desc',
        'version' => '0.0.0',
);
 
$dir = dirname(__FILE__) . '/';
 
 
$wgExtensionMessagesFiles['Namhoc'] = $dir . 'Namhoc.i18n.php'; # Location of a messages file (Tell MediaWiki to load this file)
$wgExtensionAliasesFiles['Namhoc'] = $dir . 'Namhoc.alias.php';
//Job
$wgJobClasses['insertNamhoc'] = 'NamhocJob';
$wgAutoloadClasses['SpecialNamhoc'] = $dir . 'SpecialNamhoc.php'; # Location of the SpecialNamhoc class (Tell MediaWiki to load this file)
$wgAutoloadClasses['NamhocJob'] = $dir . 'NamhocJob.php';
 
// This extension uses its own permission type, 'namhoc'
$wgAvailableRights[] = 'namhoc';
$wgGroupPermissions['sysop']['namhoc'] = true;
// maintenance, login, changes, media, users, highuse, pages, redirects, pagetools, wiki, other
// (chi tiet xem file DefaultSettings.php)
$wgSpecialPages['Namhoc'] = 'SpecialNamhoc'; # Tell MediaWiki about the new special page and its class name
$wgSpecialPageGroups['Namhoc'] = 'wiki';
 
$wgHooks['AdminLinks'][] = 'nhAddToAdminLinks';
 
// This function should really go into a "ReplaceText_body.php" file.
function nhAddToAdminLinks( &$admin_links_tree ) {
	$general_section = $admin_links_tree->getSection( wfMsg( 'adminlinks_general' ) );
        $extensions_row = $general_section->getRow( 'extensions' );
	if ( is_null( $extensions_row ) ) {
		$extensions_row = new ALRow( 'extensions' );
		$general_section->addRow( $extensions_row );
	}
	$extensions_row->addItem( ALItem::newFromSpecialPage( 'Namhoc' ) );
	return true;
}

file NamhocJob.php

<?php
class NamhocJob extends Job {
 
	function __construct( $title, $params = '', $id = 0 ) {
		parent::__construct( 'insertNamhoc', $title, $params, $id );
	}
 
	/**
	 * Run a insertNamhoc job
	 * @return boolean success
	 */
	function run() {
		wfProfileIn( __METHOD__ );
 
		if ( is_null( $this->title ) ) {
			$this->error = "insertNamhoc: Invalid title";
			wfProfileOut( __METHOD__ );
			return false;
		}
 
			$article = new Article( $this->title );
			if ( !$article ) {
				$this->error = 'insertNamhoc: Article not found "' . $this->title->getPrefixedDBkey() . '"';
				wfProfileOut( __METHOD__ );
				return false;
			}
 
			wfProfileIn( __METHOD__ . '-namhoc' );
				$article_text = $article->fetchContent();
				$more_str = $this->params['more_str'];
				$new_text = $article_text."\n$more_str";
				global $wgUser;
				$actual_user = $wgUser;
				$wgUser = User::newFromId( $this->params['user_id'] );
				$edit_summary = $this->params['edit_summary'];
				$flags = EDIT_MINOR;
				$flags |= EDIT_FORCE_BOT;
				$article->doEdit( $new_text, $edit_summary, $flags );
				$wgUser = $actual_user;
			wfProfileOut( __METHOD__ . '-namhoc' );
 
		wfProfileOut( __METHOD__ );
		return true;
	}
}

file SpecialNamhoc.php

	function fixNamhoc(){
		global $wgUser, $wgOut, $wgRequest, $wgLang;
 
		$this->cats = $wgRequest->getText( 'cats' );
		$this->more_str = $wgRequest->getText( 'more_str' );
 
		if ( $wgRequest->getCheck( 'continue' ) ) {
			if ( $this->more_str === '' ) {
				$this->showForm( 'namhoc_givetarget' );
				return;
			}elseif ($this->cats === ''){
				$this->showForm( 'namhoc_givesource' );
				return;
			}else{
				$res = $this->getPages();
				$jobs = array();
				$params = array();
				$params['user_id'] = $wgUser->getId();
				$params['more_str'] = $this->more_str;
				$params['edit_summary'] = wfMsgForContent( 'namhoc-editsummary', $this->more_str );
				$count=0;
				foreach ( $res as $row ) {
					$title = Title::makeTitle( $row->page_namespace, $row->page_title );
					if ( !$title ) {
						continue;
					}
					$jobs[] = new NamhocJob( $title, $params );
					# Avoid excessive memory usage
					if ( count( $jobs ) > 10000 ) {
						Job::batchInsert( $jobs );
						$jobs = array();
						$count = 0;
					}
				}
				Job::batchInsert( $jobs );
				$count += count( $jobs );
				$total = $wgLang->formatNum( $count );
				$wgOut->addWikiMsg( 'namhoc_success', "<tt><nowiki>{$this->more_str}</nowiki></tt>", $total );
				return;
			}
		}
		// if we're still here, show the starting form
		$this->showForm();
	}