* (bug 32381) Allow descending order for list=backlinks, list=embeddedin and list=imageusage.
* (bug 32383) Allow descending order for list=langbacklinks.
* API meta=siteinfo can now return the list of known variable IDs.
-* (bug 30836) siteinfo prop=specialpagealiases will no longer return nonexistent special pages.
* (bug 35980) list=deletedrevs now honors drdir correctly in "all" mode (mode #3).
+* (bug 29290) API avoids mangling fields in continuation parameters
+* (bug 36987) API avoids mangling fields in continuation parameters
=== Languages updated in 1.20 ===
'AbortDiffCache': Can be used to cancel the caching of a diff
&$diffEngine: DifferenceEngine object
+'AbortEmailNotification': Can be used to cancel email notifications for an edit.
+$editor: The User who made the change.
+$title: The Title of the page that was edited.
+
'AbortLogin': Return false to cancel account login.
$user: the User object being authenticated against
$password: the password being submitted, not yet checked for validity
function saveCache();
/**
- * Sets the time to live for the cache, in seconds or a unix timestamp indicating the point of expiry..
+ * Sets the time to live for the cache, in seconds or a unix timestamp
+ * indicating the point of expiry...
*
* @since 1.20
*
}
/**
- * Sets the time to live for the cache, in seconds or a unix timestamp indicating the point of expiry..
+ * Sets the time to live for the cache, in seconds or a unix timestamp
+ * indicating the point of expiry...
*
* @since 1.20
*
*/
var $sink;
+ /**
+ * Returns the export schema version.
+ * @return string
+ */
+ public static function schemaVersion() {
+ return "0.7";
+ }
+
/**
* If using WikiExporter::STREAM to stream a large amount of data,
* provide a database connection which is not managed by
class XmlDumpWriter {
/**
* Returns the export schema version.
+ * @deprecated in 1.20; use WikiExporter::schemaVersion() instead
* @return string
*/
function schemaVersion() {
- return "0.7";
+ wfDeprecated( __METHOD__, '1.20' );
+ return WikiExporter::schemaVersion();
}
/**
*/
function openStream() {
global $wgLanguageCode;
- $ver = $this->schemaVersion();
+ $ver = WikiExporter::schemaVersion();
return Xml::element( 'mediawiki', array(
'xmlns' => "http://www.mediawiki.org/xml/export-$ver/",
'xmlns:xsi' => "http://www.w3.org/2001/XMLSchema-instance",
* (non-zero is usually failure)
* @param $environ Array optional environment variables which should be
* added to the executed command environment.
+ * @param $limits Array optional array with limits(filesize, memory, time)
+ * this overwrites the global wgShellMax* limits.
* @return string collected stdout as a string (trailing newlines stripped)
*/
-function wfShellExec( $cmd, &$retval = null, $environ = array() ) {
+function wfShellExec( $cmd, &$retval = null, $environ = array(), $limits = array() ) {
global $IP, $wgMaxShellMemory, $wgMaxShellFileSize, $wgMaxShellTime;
static $disabled;
$cmd = $envcmd . $cmd;
if ( php_uname( 's' ) == 'Linux' ) {
- $time = intval( $wgMaxShellTime );
- $mem = intval( $wgMaxShellMemory );
- $filesize = intval( $wgMaxShellFileSize );
+ $time = intval ( isset($limits['time']) ? $limits['time'] : $wgMaxShellTime );
+ $mem = intval ( isset($limits['memory']) ? $limits['memory'] : $wgMaxShellMemory );
+ $filesize = intval ( isset($limits['filesize']) ? $limits['filesize'] : $wgMaxShellFileSize );
if ( $time > 0 && $mem > 0 ) {
$script = "$IP/bin/ulimit4.sh";
* http://bugs.php.net/bug.php?id=33898
*
* PHP's basename() only considers '\' a pathchar on Windows and Netware.
- * We'll consider it so always, as we don't want \s in our Unix paths either.
+ * We'll consider it so always, as we don't want '\s' in our Unix paths either.
*
* @param $path String
* @param $suffix String: to remove if present
/**
* Format a stack of error messages into a single HTML string
* @param $errors Array of message keys/values
- * @return String HTML, a <ul> list of errors
+ * @return String HTML, a "<ul>" list of errors
*/
public static function formatErrors( $errors ) {
$errorstr = '';
/**
* @param $request WebRequest
- * @return Array( <overall message>, <select value>, <text field value> )
+ * @return Array("<overall message>","<select value>","<text field value>")
*/
function loadDataFromRequest( $request ) {
if ( $request->getCheck( $this->mName ) ) {
}
$title = Title::makeTitle( $this->mAttribs['rc_namespace'], $this->mAttribs['rc_title'] );
- # @todo FIXME: This would be better as an extension hook
- $enotif = new EmailNotification();
- $status = $enotif->notifyOnPageChange( $editor, $title,
- $this->mAttribs['rc_timestamp'],
- $this->mAttribs['rc_comment'],
- $this->mAttribs['rc_minor'],
- $this->mAttribs['rc_last_oldid'] );
+ if ( wfRunHooks( 'AbortEmailNotification', array($editor, $title) ) ) {
+ # @todo FIXME: This would be better as an extension hook
+ $enotif = new EmailNotification();
+ $status = $enotif->notifyOnPageChange( $editor, $title,
+ $this->mAttribs['rc_timestamp'],
+ $this->mAttribs['rc_comment'],
+ $this->mAttribs['rc_minor'],
+ $this->mAttribs['rc_last_oldid'] );
+ }
}
}
}
/**
- * Generates a list item for a navigation, portlet, portal, sidebar... etc list
- * $key is a string, usually a key from the list you are generating this link from
- * $item is an array of list item data containing some of a specific set of keys.
+ * Generates a list item for a navigation, portlet, portal, sidebar... list
+ *
+ * @param $key string, usually a key from the list you are generating this link from.
+ * @param $item array, of list item data containing some of a specific set of keys.
* The "id" and "class" keys will be used as attributes for the list item,
* if "active" contains a value of true a "active" class will also be appended to class.
- * If you want something other than a <li> you can pass a tag name such as
+ *
+ * @param $options array
+ *
+ * If you want something other than a "<li>" you can pass a tag name such as
* "tag" => "span" in the $options array to change the tag used.
* link/content data for the list item may come in one of two forms
* A "links" key may be used, in which case it should contain an array with
- * a list of links to include inside the list item, see makeLink for the format
- * of individual links array items.
+ * a list of links to include inside the list item, see makeLink for the
+ * format of individual links array items.
+ *
* Otherwise the relevant keys from the list item $item array will be passed
* to makeLink instead. Note however that "id" and "class" are used by the
* list item directly so they will not be passed to makeLink
* If you need an id or class on a single link you should include a "links"
* array with just one link item inside of it.
* $options is also passed on to makeLink calls
+ *
* @return string
*/
function makeListItem( $key, $item, $options = array() ) {
* @return mixed array or boolean false
*/
public static function pageCountInfo( $title ) {
+ wfProfileIn( __METHOD__ );
$id = $title->getArticleID();
$dbr = wfGetDB( DB_SLAVE );
'watchlist',
'COUNT(*)',
array(
+ 'wl_namespace' => $title->getNamespace(),
'wl_title' => $title->getDBkey(),
- 'wl_namespace' => $title->getNamespace()
),
__METHOD__
);
array( 'rev_page' => $id ),
__METHOD__
);
+ $result = array( 'watchers' => $watchers, 'edits' => $edits,
+ 'authors' => $authors );
- $views = (int)$dbr->selectField(
- 'page',
- 'page_counter',
- array( 'page_id' => $id ),
- __METHOD__
- );
-
- return array( 'watchers' => $watchers, 'edits' => $edits,
- 'authors' => $authors, 'views' => $views );
+ global $wgDisableCounters;
+ if ( !$wgDisableCounters ) {
+ $views = (int)$dbr->selectField(
+ 'page',
+ 'page_counter',
+ array( 'page_id' => $id ),
+ __METHOD__
+ );
+ $result['views'] = $views;
+ }
+
+ wfProfileOut( __METHOD__ );
+ return $result;
}
}
/**
* Get the name of the module as shown in the profiler log
*
- * @param $db DatabaseBase
+ * @param $db DatabaseBase|bool
*
* @return string
*/
* automated identification of the error, e.g., 'unknown_action'
* @param $httpRespCode int HTTP response code
* @param $extradata array Data to add to the "<error>" element; array in ApiResult format
+ * @throws UsageException
*/
public function dieUsage( $description, $errorCode, $httpRespCode = 0, $extradata = null ) {
Profiler::instance()->close();
$data = array(
'Target' => $params['user'],
'Reason' => array(
- is_null( $params['reason'] ) ? '' : $params['reason'],
+ $params['reason'],
'other',
- is_null( $params['reason'] ) ? '' : $params['reason']
+ $params['reason']
),
'Expiry' => $params['expiry'] == 'never' ? 'infinite' : $params['expiry'],
'HardBlock' => !$params['anononly'],
ApiBase::PARAM_DEPRECATED => true,
),
'expiry' => 'never',
- 'reason' => null,
+ 'reason' => '',
'anononly' => false,
'nocreate' => false,
'autoblock' => false,
'token' => 'A block token previously obtained through prop=info',
'gettoken' => 'If set, a block token will be returned, and no other action will be taken',
'expiry' => 'Relative expiry time, e.g. \'5 months\' or \'2 weeks\'. If set to \'infinite\', \'indefinite\' or \'never\', the block will never expire.',
- 'reason' => 'Reason for block (optional)',
+ 'reason' => 'Reason for block',
'anononly' => 'Block anonymous users only (i.e. disable anonymous edits for this IP)',
'nocreate' => 'Prevent account creation',
'autoblock' => 'Automatically block the last used IP address, and any subsequent IP addresses they try to login from',
}
$titleObj = $pageObj->getTitle();
- $reason = ( isset( $params['reason'] ) ? $params['reason'] : null );
+ $reason = $params['reason'];
$user = $this->getUser();
if ( $titleObj->getNamespace() == NS_FILE ) {
class UsageException extends MWException {
private $mCodestr;
+
+ /**
+ * @var null|array
+ */
private $mExtraData;
+ /**
+ * @param $message string
+ * @param $codestr string
+ * @param $code int
+ * @param $extradata array|null
+ */
public function __construct( $message, $codestr, $code = 0, $extradata = null ) {
parent::__construct( $message, $code );
$this->mCodestr = $codestr;
public function execute() {
$user = $this->getUser();
$params = $this->extractRequestParams();
- if ( is_null( $params['reason'] ) ) {
- $params['reason'] = '';
- }
$this->requireOnlyOneParameter( $params, 'from', 'fromid' );
ApiBase::PARAM_REQUIRED => true
),
'token' => null,
- 'reason' => null,
+ 'reason' => '',
'movetalk' => false,
'movesubpages' => false,
'noredirect' => false,
'fromid' => "Page ID of the page you want to move. Cannot be used together with {$p}from",
'to' => 'Title you want to rename the page to',
'token' => 'A move token previously retrieved through prop=info',
- 'reason' => 'Reason for the move (optional)',
+ 'reason' => 'Reason for the move',
'movetalk' => 'Move the talk page, if it exists',
'movesubpages' => 'Move subpages, if applicable',
'noredirect' => 'Don\'t create a redirect',
/**
* Constructor
- * @param $query ApiQueryBase
+ * @param $query ApiBase
* @param $resolveRedirects bool Whether redirects should be resolved
* @param $convertTitles bool
*/
'protections' => 'Pipe-separated list of protection levels, formatted action=group (e.g. edit=sysop)',
'expiry' => array( 'Expiry timestamps. If only one timestamp is set, it\'ll be used for all protections.',
'Use \'infinite\', \'indefinite\' or \'never\', for a neverexpiring protection.' ),
- 'reason' => 'Reason for (un)protecting (optional)',
+ 'reason' => 'Reason for (un)protecting',
'cascade' => array( 'Enable cascading protection (i.e. protect pages included in this page)',
'Ignored if not all protection levels are \'sysop\' or \'protect\'' ),
'watch' => 'If set, add the page being (un)protected to your watchlist',
protected $mAllowedGenerators = array();
+ /**
+ * @param $main ApiMain
+ * @param $action string
+ */
public function __construct( $main, $action ) {
parent::__construct( $main, $action );
return null;
}
+ /**
+ * @return ApiFormatRaw|null
+ */
public function getCustomPrinter() {
// If &exportnowrap is set, use the raw formatter
if ( $this->getParameter( 'export' ) &&
$this->outputGeneralPageInfo();
// Execute all requested modules.
+ /**
+ * @var $module ApiQueryBase
+ */
foreach ( $modules as $module ) {
$params = $module->extractRequestParams();
$cacheMode = $this->mergeCacheMode(
*/
private function addCustomFldsToPageSet( $modules, $pageSet ) {
// Query all requested modules.
+ /**
+ * @var $module ApiQueryBase
+ */
foreach ( $modules as $module ) {
$module->requestExtraData( $pageSet );
}
// Show redirect information
$redirValues = array();
+ /**
+ * @var $titleTo Title
+ */
foreach ( $pageSet->getRedirectTitles() as $titleStrFrom => $titleTo ) {
$r = array(
'from' => strval( $titleStrFrom ),
$this->addFields( 'cat_title' );
$this->addWhere( 'cat_pages > 0' );
+ if ( !is_null( $params['continue'] ) ) {
+ $cont = explode( '|', $params['continue'] );
+ if ( count( $cont ) != 1 ) {
+ $this->dieUsage( "Invalid continue param. You should pass the " .
+ "original value returned by the previous query", "_badcontinue" );
+ }
+ $op = $params['dir'] == 'descending' ? '<' : '>';
+ $cont_from = $db->addQuotes( $cont[0] );
+ $this->addWhere( "cat_title $op= $cont_from" );
+ }
+
$dir = ( $params['dir'] == 'descending' ? 'older' : 'newer' );
$from = ( is_null( $params['from'] ) ? null : $this->titlePartToKey( $params['from'] ) );
$to = ( is_null( $params['to'] ) ? null : $this->titlePartToKey( $params['to'] ) );
foreach ( $res as $row ) {
if ( ++ $count > $params['limit'] ) {
// We've reached the one extra which shows that there are additional cats to be had. Stop here...
- // TODO: Security issue - if the user has no right to view next title, it will still be shown
- $this->setContinueEnumParameter( 'from', $this->keyToTitle( $row->cat_title ) );
+ $this->setContinueEnumParameter( 'continue', $row->cat_title );
break;
}
}
$fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $item );
if ( !$fit ) {
- $this->setContinueEnumParameter( 'from', $this->keyToTitle( $row->cat_title ) );
+ $this->setContinueEnumParameter( 'continue', $row->cat_title );
break;
}
}
public function getAllowedParams() {
return array(
'from' => null,
+ 'continue' => null,
'to' => null,
'prefix' => null,
'dir' => array(
public function getParamDescription() {
return array(
'from' => 'The category to start enumerating from',
+ 'continue' => 'When more results are available, use this to continue',
'to' => 'The category to stop enumerating at',
'prefix' => 'Search for all category titles that begin with this value',
'dir' => 'Direction to sort in',
return 'Enumerate all categories';
}
+ public function getPossibleErrors() {
+ return array_merge( parent::getPossibleErrors(), array(
+ array( 'code' => '_badcontinue', 'info' => 'Invalid continue param. You should pass the original value returned by the previous query' ),
+ ) );
+ }
+
public function getExamples() {
return array(
'api.php?action=query&list=allcategories&acprop=size',
$params = $this->extractRequestParams();
+ if ( !is_null( $params['continue'] ) ) {
+ $cont = explode( '|', $params['continue'] );
+ if ( count( $cont ) != 1 ) {
+ $this->dieUsage( "Invalid continue param. You should pass the " .
+ "original value returned by the previous query", "_badcontinue" );
+ }
+ $op = $params['dir'] == 'descending' ? '<' : '>';
+ $cont_from = $db->addQuotes( $cont[0] );
+ $this->addWhere( "img_name $op= $cont_from" );
+ }
+
// Image filters
$dir = ( $params['dir'] == 'descending' ? 'older' : 'newer' );
$from = ( is_null( $params['from'] ) ? null : $this->titlePartToKey( $params['from'] ) );
foreach ( $res as $row ) {
if ( ++ $count > $limit ) {
// We've reached the one extra which shows that there are additional pages to be had. Stop here...
- // TODO: Security issue - if the user has no right to view next title, it will still be shown
- $this->setContinueEnumParameter( 'from', $this->keyToTitle( $row->img_name ) );
+ $this->setContinueEnumParameter( 'continue', $row->img_name );
break;
}
$fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $info );
if ( !$fit ) {
- $this->setContinueEnumParameter( 'from', $this->keyToTitle( $row->img_name ) );
+ $this->setContinueEnumParameter( 'continue', $row->img_name );
break;
}
} else {
public function getAllowedParams() {
return array (
'from' => null,
+ 'continue' => null,
'to' => null,
'prefix' => null,
'minsize' => array(
public function getParamDescription() {
return array(
'from' => 'The image title to start enumerating from',
+ 'continue' => 'When more results are available, use this to continue',
'to' => 'The image title to stop enumerating at',
'prefix' => 'Search for all image titles that begin with this value',
'dir' => 'The direction in which to list',
);
}
- private $propertyFilter = array( 'archivename' );
+ private $propertyFilter = array( 'archivename', 'thumbmime' );
public function getResultProperties() {
return array_merge(
array( 'code' => 'mimesearchdisabled', 'info' => 'MIME search disabled in Miser Mode' ),
array( 'code' => 'invalidsha1hash', 'info' => 'The SHA1 hash provided is not valid' ),
array( 'code' => 'invalidsha1base36hash', 'info' => 'The SHA1Base36 hash provided is not valid' ),
+ array( 'code' => '_badcontinue', 'info' => 'Invalid continue param. You should pass the original value returned by the previous query' ),
) );
}
}
if ( !is_null( $params['continue'] ) ) {
$continueArr = explode( '|', $params['continue'] );
- if ( count( $continueArr ) != 2 ) {
- $this->dieUsage( 'Invalid continue parameter', 'badcontinue' );
- }
$op = $params['dir'] == 'descending' ? '<' : '>';
- $continueTitle = $db->addQuotes( $this->titleToKey( $continueArr[0] ) );
- $continueFrom = intval( $continueArr[1] );
- $this->addWhere(
- "pl_title $op $continueTitle OR " .
- "(pl_title = $continueTitle AND " .
- "pl_from $op= $continueFrom)"
- );
+ if ( $params['unique'] ) {
+ if ( count( $continueArr ) != 1 ) {
+ $this->dieUsage( 'Invalid continue parameter', 'badcontinue' );
+ }
+ $continueTitle = $db->addQuotes( $continueArr[0] );
+ $this->addWhere( "pl_title $op= $continueTitle" );
+ } else {
+ if ( count( $continueArr ) != 2 ) {
+ $this->dieUsage( 'Invalid continue parameter', 'badcontinue' );
+ }
+ $continueTitle = $db->addQuotes( $continueArr[0] );
+ $continueFrom = intval( $continueArr[1] );
+ $this->addWhere(
+ "pl_title $op $continueTitle OR " .
+ "(pl_title = $continueTitle AND " .
+ "pl_from $op= $continueFrom)"
+ );
+ }
}
$from = ( is_null( $params['from'] ) ? null : $this->titlePartToKey( $params['from'] ) );
foreach ( $res as $row ) {
if ( ++ $count > $limit ) {
// We've reached the one extra which shows that there are additional pages to be had. Stop here...
- // TODO: Security issue - if the user has no right to view next title, it will still be shown
if ( $params['unique'] ) {
- $this->setContinueEnumParameter( 'from', $this->keyToTitle( $row->pl_title ) );
+ $this->setContinueEnumParameter( 'continue', $row->pl_title );
} else {
- $this->setContinueEnumParameter( 'continue', $this->keyToTitle( $row->pl_title ) . "|" . $row->pl_from );
+ $this->setContinueEnumParameter( 'continue', $row->pl_title . "|" . $row->pl_from );
}
break;
}
$fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $vals );
if ( !$fit ) {
if ( $params['unique'] ) {
- $this->setContinueEnumParameter( 'from', $this->keyToTitle( $row->pl_title ) );
+ $this->setContinueEnumParameter( 'continue', $row->pl_title );
} else {
- $this->setContinueEnumParameter( 'continue', $this->keyToTitle( $row->pl_title ) . "|" . $row->pl_from );
+ $this->setContinueEnumParameter( 'continue', $row->pl_title . "|" . $row->pl_from );
}
break;
}
// Page filters
$this->addTables( 'page' );
+ if ( !is_null( $params['continue'] ) ) {
+ $cont = explode( '|', $params['continue'] );
+ if ( count( $cont ) != 1 ) {
+ $this->dieUsage( "Invalid continue param. You should pass the " .
+ "original value returned by the previous query", "_badcontinue" );
+ }
+ $op = $params['dir'] == 'descending' ? '<' : '>';
+ $cont_from = $db->addQuotes( $cont[0] );
+ $this->addWhere( "page_title $op= $cont_from" );
+ }
+
if ( $params['filterredir'] == 'redirects' ) {
$this->addWhereFld( 'page_is_redirect', 1 );
} elseif ( $params['filterredir'] == 'nonredirects' ) {
foreach ( $res as $row ) {
if ( ++ $count > $limit ) {
// We've reached the one extra which shows that there are additional pages to be had. Stop here...
- // TODO: Security issue - if the user has no right to view next title, it will still be shown
- $this->setContinueEnumParameter( 'from', $this->keyToTitle( $row->page_title ) );
+ $this->setContinueEnumParameter( 'continue', $row->page_title );
break;
}
);
$fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $vals );
if ( !$fit ) {
- $this->setContinueEnumParameter( 'from', $this->keyToTitle( $row->page_title ) );
+ $this->setContinueEnumParameter( 'continue', $row->page_title );
break;
}
} else {
return array(
'from' => null,
+ 'continue' => null,
'to' => null,
'prefix' => null,
'namespace' => array(
$p = $this->getModulePrefix();
return array(
'from' => 'The page title to start enumerating from',
+ 'continue' => 'When more results are available, use this to continue',
'to' => 'The page title to stop enumerating at',
'prefix' => 'Search for all page titles that begin with this value',
'namespace' => 'The namespace to enumerate',
return array_merge( parent::getPossibleErrors(), array(
array( 'code' => 'params', 'info' => 'Use "gapfilterredir=nonredirects" option instead of "redirects" when using allpages as a generator' ),
array( 'code' => 'params', 'info' => 'prlevel may not be used without prtype' ),
+ array( 'code' => '_badcontinue', 'info' => 'Invalid continue param. You should pass the original value returned by the previous query' ),
) );
}
private $mIsGenerator;
+ /**
+ * @param $query ApiBase
+ * @param $moduleName string
+ * @param $paramPrefix string
+ */
public function __construct( $query, $moduleName, $paramPrefix = '' ) {
parent::__construct( $query, $moduleName, $paramPrefix );
$this->mIsGenerator = false;
}
$op = $params['dir'] == 'descending' ? '<' : '>';
$clfrom = intval( $cont[0] );
- $clto = $this->getDB()->addQuotes( $this->titleToKey( $cont[1] ) );
+ $clto = $this->getDB()->addQuotes( $cont[1] );
$this->addWhere(
"cl_from $op $clfrom OR " .
"(cl_from = $clfrom AND " .
if ( ++$count > $params['limit'] ) {
// We've reached the one extra which shows that
// there are additional pages to be had. Stop here...
- $this->setContinueEnumParameter( 'continue', $row->cl_from .
- '|' . $this->keyToTitle( $row->cl_to ) );
+ $this->setContinueEnumParameter( 'continue', $row->cl_from . '|' . $row->cl_to );
break;
}
$fit = $this->addPageSubItem( $row->cl_from, $vals );
if ( !$fit ) {
- $this->setContinueEnumParameter( 'continue', $row->cl_from .
- '|' . $this->keyToTitle( $row->cl_to ) );
+ $this->setContinueEnumParameter( 'continue', $row->cl_from . '|' . $row->cl_to );
break;
}
}
if ( ++$count > $params['limit'] ) {
// We've reached the one extra which shows that
// there are additional pages to be had. Stop here...
- $this->setContinueEnumParameter( 'continue', $row->cl_from .
- '|' . $this->keyToTitle( $row->cl_to ) );
+ $this->setContinueEnumParameter( 'continue', $row->cl_from . '|' . $row->cl_to );
break;
}
$this->dieUsage( 'Invalid continue param. You should pass the original value returned by the previous query', 'badcontinue' );
}
$ns = intval( $cont[0] );
- $title = $db->addQuotes( $this->titleToKey( $cont[1] ) );
+ $title = $db->addQuotes( $cont[1] );
$ts = $db->addQuotes( $db->timestamp( $cont[2] ) );
$op = ( $dir == 'newer' ? '>' : '<' );
$this->addWhere( "ar_namespace $op $ns OR " .
// We've had enough
if ( $mode == 'all' || $mode == 'revs' ) {
$this->setContinueEnumParameter( 'continue', intval( $row->ar_namespace ) . '|' .
- $this->keyToTitle( $row->ar_title ) . '|' . $row->ar_timestamp );
+ $row->ar_title . '|' . $row->ar_timestamp );
} else {
$this->setContinueEnumParameter( 'start', wfTimestamp( TS_ISO_8601, $row->ar_timestamp ) );
}
if ( !$fit ) {
if ( $mode == 'all' || $mode == 'revs' ) {
$this->setContinueEnumParameter( 'continue', intval( $row->ar_namespace ) . '|' .
- $this->keyToTitle( $row->ar_title ) . '|' . $row->ar_timestamp );
+ $row->ar_title . '|' . $row->ar_timestamp );
} else {
$this->setContinueEnumParameter( 'start', wfTimestamp( TS_ISO_8601, $row->ar_timestamp ) );
}
}
$op = $params['dir'] == 'descending' ? '<' : '>';
$db = $this->getDB();
- $orig = $db->addQuotes( $this->titleTokey( $cont[0] ) );
- $dup = $db->addQuotes( $this->titleToKey( $cont[1] ) );
+ $orig = $db->addQuotes( $cont[0] );
+ $dup = $db->addQuotes( $cont[1] );
$this->addWhere(
"i1.img_name $op $orig OR " .
"(i1.img_name = $orig AND " .
if ( ++$count > $params['limit'] ) {
// We've reached the one extra which shows that
// there are additional pages to be had. Stop here...
- $this->setContinueEnumParameter( 'continue',
- $this->keyToTitle( $row->orig_name ) . '|' .
- $this->keyToTitle( $row->dup_name ) );
+ $this->setContinueEnumParameter( 'continue', $row->orig_name . '|' . $row->dup_name );
break;
}
if ( !is_null( $resultPageSet ) ) {
);
$fit = $this->addPageSubItem( $images[$row->orig_name], $r );
if ( !$fit ) {
- $this->setContinueEnumParameter( 'continue',
- $this->keyToTitle( $row->orig_name ) . '|' .
- $this->keyToTitle( $row->dup_name ) );
+ $this->setContinueEnumParameter( 'continue', $row->orig_name . '|' . $row->dup_name );
break;
}
}
$this->addFieldsIf( 'fa_metadata', $fld_metadata );
$this->addFieldsIf( 'fa_bits', $fld_bitdepth );
+ if ( !is_null( $params['continue'] ) ) {
+ $cont = explode( '|', $params['continue'] );
+ if ( count( $cont ) != 1 ) {
+ $this->dieUsage( "Invalid continue param. You should pass the " .
+ "original value returned by the previous query", "_badcontinue" );
+ }
+ $op = $params['dir'] == 'descending' ? '<' : '>';
+ $cont_from = $db->addQuotes( $cont[0] );
+ $this->addWhere( "fa_name $op= $cont_from" );
+ }
+
// Image filters
$dir = ( $params['dir'] == 'descending' ? 'older' : 'newer' );
$from = ( is_null( $params['from'] ) ? null : $this->titlePartToKey( $params['from'] ) );
+ if ( !is_null( $params['continue'] ) ) {
+ $from = $params['continue'];
+ }
$to = ( is_null( $params['to'] ) ? null : $this->titlePartToKey( $params['to'] ) );
$this->addWhereRange( 'fa_name', $dir, $from, $to );
if ( isset( $params['prefix'] ) ) {
foreach ( $res as $row ) {
if ( ++$count > $limit ) {
// We've reached the one extra which shows that there are additional pages to be had. Stop here...
- // TODO: Security issue - if the user has no right to view next title, it will still be shown
- $this->setContinueEnumParameter( 'from', $this->keyToTitle( $row->fa_name ) );
+ $this->setContinueEnumParameter( 'continue', $row->fa_name );
break;
}
$fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $file );
if ( !$fit ) {
- $this->setContinueEnumParameter( 'from', $this->keyToTitle( $row->fa_name ) );
+ $this->setContinueEnumParameter( 'continue', $row->fa_name );
break;
}
}
public function getAllowedParams() {
return array (
'from' => null,
+ 'continue' => null,
'to' => null,
'prefix' => null,
'limit' => array(
public function getParamDescription() {
return array(
'from' => 'The image title to start enumerating from',
+ 'continue' => 'When more results are available, use this to continue',
'to' => 'The image title to stop enumerating at',
'prefix' => 'Search for all image titles that begin with this value',
'dir' => 'The direction in which to list',
array( 'code' => 'hashsearchdisabled', 'info' => 'Search by hash disabled in Miser Mode' ),
array( 'code' => 'invalidsha1hash', 'info' => 'The SHA1 hash provided is not valid' ),
array( 'code' => 'invalidsha1base36hash', 'info' => 'The SHA1Base36 hash provided is not valid' ),
+ array( 'code' => '_badcontinue', 'info' => 'Invalid continue param. You should pass the original value returned by the previous query' ),
) );
}
$db = $this->getDB();
$op = $params['dir'] == 'descending' ? '<' : '>';
$prefix = $db->addQuotes( $cont[0] );
- $title = $db->addQuotes( $this->titleToKey( $cont[1] ) );
+ $title = $db->addQuotes( $cont[1] );
$from = intval( $cont[2] );
$this->addWhere(
"iwl_prefix $op $prefix OR " .
$db = $this->getDB();
$iwlfrom = intval( $cont[0] );
$iwlprefix = $db->addQuotes( $cont[1] );
- $iwltitle = $db->addQuotes( $this->titleToKey( $cont[2] ) );
+ $iwltitle = $db->addQuotes( $cont[2] );
$this->addWhere(
"iwl_from $op $iwlfrom OR " .
"(iwl_from = $iwlfrom AND " .
*
* @return array
*/
- private static function getProperties() {
+ private static function getProperties( $modulePrefix = '' ) {
return array(
'timestamp' => ' timestamp - Adds timestamp for the uploaded version',
'user' => ' user - Adds the user who uploaded the image version',
'dimensions' => ' dimensions - Alias for size', // For backwards compatibility with Allimages
'sha1' => ' sha1 - Adds SHA-1 hash for the image',
'mime' => ' mime - Adds MIME type of the image',
- 'thumbmime' => ' thumbmime - Adds MIME type of the image thumbnail (requires url)',
+ 'thumbmime' => ' thumbmime - Adds MIME type of the image thumbnail' .
+ ' (requires url and param ' . $modulePrefix . 'urlwidth)',
'mediatype' => ' mediatype - Adds the media type of the image',
'metadata' => ' metadata - Lists EXIF metadata for the version of the image',
'archivename' => ' archivename - Adds the file name of the archive version for non-latest versions',
*
* @return array
*/
- public static function getPropertyDescriptions( $filter = array() ) {
+ public static function getPropertyDescriptions( $filter = array(), $modulePrefix = '' ) {
return array_merge(
array( 'What image information to get:' ),
- array_values( array_diff_key( self::getProperties(), array_flip( $filter ) ) )
+ array_values( array_diff_key( self::getProperties( $modulePrefix ), array_flip( $filter ) ) )
);
}
public function getParamDescription() {
$p = $this->getModulePrefix();
return array(
- 'prop' => self::getPropertyDescriptions(),
+ 'prop' => self::getPropertyDescriptions( array(), $p ),
'urlwidth' => array( "If {$p}prop=url is set, a URL to an image scaled to this width will be returned.",
'Only the current version of the image can be scaled' ),
'urlheight' => "Similar to {$p}urlwidth. Cannot be used without {$p}urlwidth",
}
$op = $params['dir'] == 'descending' ? '<' : '>';
$ilfrom = intval( $cont[0] );
- $ilto = $this->getDB()->addQuotes( $this->titleToKey( $cont[1] ) );
+ $ilto = $this->getDB()->addQuotes( $cont[1] );
$this->addWhere(
"il_from $op $ilfrom OR " .
"(il_from = $ilfrom AND " .
if ( ++$count > $params['limit'] ) {
// We've reached the one extra which shows that
// there are additional pages to be had. Stop here...
- $this->setContinueEnumParameter( 'continue', $row->il_from .
- '|' . $this->keyToTitle( $row->il_to ) );
+ $this->setContinueEnumParameter( 'continue', $row->il_from . '|' . $row->il_to );
break;
}
$vals = array();
ApiQueryBase::addTitleInfo( $vals, Title::makeTitle( NS_FILE, $row->il_to ) );
$fit = $this->addPageSubItem( $row->il_from, $vals );
if ( !$fit ) {
- $this->setContinueEnumParameter( 'continue', $row->il_from .
- '|' . $this->keyToTitle( $row->il_to ) );
+ $this->setContinueEnumParameter( 'continue', $row->il_from . '|' . $row->il_to );
break;
}
}
if ( ++$count > $params['limit'] ) {
// We've reached the one extra which shows that
// there are additional pages to be had. Stop here...
- $this->setContinueEnumParameter( 'continue', $row->il_from .
- '|' . $this->keyToTitle( $row->il_to ) );
+ $this->setContinueEnumParameter( 'continue', $row->il_from . '|' . $row->il_to );
break;
}
$titles[] = Title::makeTitle( NS_FILE, $row->il_to );
global $wgDisableCounters;
$pageSet->requestField( 'page_restrictions' );
- $pageSet->requestField( 'page_is_redirect' );
+ // when resolving redirects, no page will have this field
+ if( !$pageSet->isResolvingRedirects() ) {
+ $pageSet->requestField( 'page_is_redirect' );
+ }
$pageSet->requestField( 'page_is_new' );
if ( !$wgDisableCounters ) {
$pageSet->requestField( 'page_counter' );
}
$this->pageRestrictions = $pageSet->getCustomField( 'page_restrictions' );
- $this->pageIsRedir = $pageSet->getCustomField( 'page_is_redirect' );
+ // when resolving redirects, no page will have this field
+ $this->pageIsRedir = !$pageSet->isResolvingRedirects()
+ ? $pageSet->getCustomField( 'page_is_redirect' )
+ : array();
$this->pageIsNew = $pageSet->getCustomField( 'page_is_new' );
global $wgDisableCounters;
: intval( $this->pageCounter[$pageid] );
$pageInfo['length'] = intval( $this->pageLength[$pageid] );
- if ( $this->pageIsRedir[$pageid] ) {
+ if ( isset( $this->pageIsRedir[$pageid] ) && $this->pageIsRedir[$pageid] ) {
$pageInfo['redirect'] = '';
}
if ( $this->pageIsNew[$pageid] ) {
$db = $this->getDB();
$op = $params['dir'] == 'descending' ? '<' : '>';
$prefix = $db->addQuotes( $cont[0] );
- $title = $db->addQuotes( $this->titleToKey( $cont[1] ) );
+ $title = $db->addQuotes( $cont[1] );
$from = intval( $cont[2] );
$this->addWhere(
"ll_lang $op $prefix OR " .
$op = $params['dir'] == 'descending' ? '<' : '>';
$plfrom = intval( $cont[0] );
$plns = intval( $cont[1] );
- $pltitle = $this->getDB()->addQuotes( $this->titleToKey( $cont[2] ) );
+ $pltitle = $this->getDB()->addQuotes( $cont[2] );
$this->addWhere(
"{$this->prefix}_from $op $plfrom OR " .
"({$this->prefix}_from = $plfrom AND " .
// We've reached the one extra which shows that
// there are additional pages to be had. Stop here...
$this->setContinueEnumParameter( 'continue',
- "{$row->pl_from}|{$row->pl_namespace}|" .
- $this->keyToTitle( $row->pl_title ) );
+ "{$row->pl_from}|{$row->pl_namespace}|{$row->pl_title}" );
break;
}
$vals = array();
$fit = $this->addPageSubItem( $row->pl_from, $vals );
if ( !$fit ) {
$this->setContinueEnumParameter( 'continue',
- "{$row->pl_from}|{$row->pl_namespace}|" .
- $this->keyToTitle( $row->pl_title ) );
+ "{$row->pl_from}|{$row->pl_namespace}|{$row->pl_title}" );
break;
}
}
// We've reached the one extra which shows that
// there are additional pages to be had. Stop here...
$this->setContinueEnumParameter( 'continue',
- "{$row->pl_from}|{$row->pl_namespace}|" .
- $this->keyToTitle( $row->pl_title ) );
+ "{$row->pl_from}|{$row->pl_namespace}|{$row->pl_title}" );
break;
}
$titles[] = Title::makeTitle( $row->pl_namespace, $row->pl_title );
protected function appendSpecialPageAliases( $property ) {
global $wgContLang;
$data = array();
- $aliases = $wgContLang->getSpecialPageAliases();
- foreach ( SpecialPageFactory::getList() as $specialpage => $stuff ) {
- $arr = array( 'realname' => $specialpage, 'aliases' => $aliases[$specialpage] );
+ foreach ( $wgContLang->getSpecialPageAliases() as $specialpage => $aliases ) {
+ $arr = array( 'realname' => $specialpage, 'aliases' => $aliases );
$this->getResult()->setIndexedTagName( $arr['aliases'], 'alias' );
$data[] = $arr;
}
public function getParamDescription() {
$p = $this->getModulePrefix();
return array(
- 'prop' => self::getPropertyDescriptions( $this->propertyFilter ),
+ 'prop' => self::getPropertyDescriptions( $this->propertyFilter, $p ),
'filekey' => 'Key that identifies a previous upload that was stashed temporarily.',
'sessionkey' => 'Alias for filekey, for backward compatibility.',
'urlwidth' => "If {$p}prop=url is set, a URL to an image scaled to this width will be returned.",
$this->addFieldsIf( 'page_latest', $this->fld_flags );
// $this->addFieldsIf( 'rev_text_id', $this->fld_ids ); // Should this field be exposed?
$this->addFieldsIf( 'rev_comment', $this->fld_comment || $this->fld_parsedcomment );
- $this->addFieldsIf( 'rev_len', $this->fld_size );
+ $this->addFieldsIf( 'rev_len', $this->fld_size || $this->fld_sizediff );
$this->addFieldsIf( 'rev_minor_edit', $this->fld_flags );
$this->addFieldsIf( 'rev_parent_id', $this->fld_flags || $this->fld_sizediff );
$this->addFieldsIf( 'rc_patrolled', $this->fld_patrolled );
"original value returned by the previous query", "_badcontinue" );
}
$ns = intval( $cont[0] );
- $title = $this->getDB()->addQuotes( $this->titleToKey( $cont[1] ) );
+ $title = $this->getDB()->addQuotes( $cont[1] );
$op = $params['dir'] == 'ascending' ? '>' : '<';
$this->addWhere(
"wl_namespace $op $ns OR " .
foreach ( $res as $row ) {
if ( ++$count > $params['limit'] ) {
// We've reached the one extra which shows that there are additional pages to be had. Stop here...
- $this->setContinueEnumParameter( 'continue', $row->wl_namespace . '|' .
- $this->keyToTitle( $row->wl_title ) );
+ $this->setContinueEnumParameter( 'continue', $row->wl_namespace . '|' . $row->wl_title );
break;
}
$t = Title::makeTitle( $row->wl_namespace, $row->wl_title );
}
$fit = $this->getResult()->addValue( $this->getModuleName(), null, $vals );
if ( !$fit ) {
- $this->setContinueEnumParameter( 'continue', $row->wl_namespace . '|' .
- $this->keyToTitle( $row->wl_title ) );
+ $this->setContinueEnumParameter( 'continue', $row->wl_namespace . '|' . $row->wl_title );
break;
}
} else {
// User and title already validated in call to getTokenSalt from Main
$titleObj = $this->getRbTitle();
$pageObj = WikiPage::factory( $titleObj );
- $summary = ( isset( $params['summary'] ) ? $params['summary'] : '' );
+ $summary = $params['summary'];
$details = array();
$retval = $pageObj->doRollback( $this->getRbUser(), $summary, $params['token'], $params['markbot'], $details, $this->getUser() );
ApiBase::PARAM_REQUIRED => true
),
'token' => null,
- 'summary' => null,
+ 'summary' => '',
'markbot' => false,
'watchlist' => array(
ApiBase::PARAM_DFLT => 'preferences',
'title' => 'Title of the page you want to rollback.',
'user' => 'Name of the user whose edits are to be rolled back. If set incorrectly, you\'ll get a badtoken error.',
'token' => "A rollback token previously retrieved through {$this->getModulePrefix()}prop=revisions",
- 'summary' => 'Custom edit summary. If not set, default summary will be used',
+ 'summary' => 'Custom edit summary. If empty, default summary will be used',
'markbot' => 'Mark the reverted edits and the revert as bot edits',
'watchlist' => 'Unconditionally add or remove the page from your watchlist, use preferences or do not change watch',
);
$data = array(
'Target' => is_null( $params['id'] ) ? $params['user'] : "#{$params['id']}",
- 'Reason' => is_null( $params['reason'] ) ? '' : $params['reason']
+ 'Reason' => $params['reason']
);
$block = Block::newFromTarget( $data['Target'] );
$retval = SpecialUnblock::processUnblock( $data, $this->getContext() );
ApiBase::PARAM_DFLT => false,
ApiBase::PARAM_DEPRECATED => true,
),
- 'reason' => null,
+ 'reason' => '',
);
}
'user' => "Username, IP address or IP range you want to unblock. Cannot be used together with {$p}id",
'token' => "An unblock token previously obtained through prop=info",
'gettoken' => 'If set, an unblock token will be returned, and no other action will be taken',
- 'reason' => 'Reason for unblock (optional)',
+ 'reason' => 'Reason for unblock',
);
}
return array(
'title' => 'Title of the page you want to restore',
'token' => 'An undelete token previously retrieved through list=deletedrevs',
- 'reason' => 'Reason for restoring (optional)',
+ 'reason' => 'Reason for restoring',
'timestamps' => 'Timestamps of the revisions to restore. If not set, all revisions will be restored.',
'watchlist' => 'Unconditionally add or remove the page from your watchlist, use preferences or do not change watch',
);
protected $maxCacheKeys; // integer; max entries
/**
- * @param $maxEntries integer Maximum number of entries allowed (min 1).
+ * @param $maxKeys integer Maximum number of entries allowed (min 1).
* @throws MWException When $maxCacheKeys is not an int or =< 0.
*/
public function __construct( $maxKeys ) {
*
* @return bool|null previous value of the flag
*/
- function debug( $debug = null ) {
+ public function debug( $debug = null ) {
return wfSetBit( $this->mFlags, DBO_DEBUG, $debug );
}
*
* @return null|bool The previous value of the flag
*/
- function bufferResults( $buffer = null ) {
+ public function bufferResults( $buffer = null ) {
if ( is_null( $buffer ) ) {
return !(bool)( $this->mFlags & DBO_NOBUFFER );
} else {
*
* @return bool The previous value of the flag.
*/
- function ignoreErrors( $ignoreErrors = null ) {
+ public function ignoreErrors( $ignoreErrors = null ) {
return wfSetBit( $this->mFlags, DBO_IGNORE, $ignoreErrors );
}
* @param $level int An integer (0 or 1), or omitted to leave it unchanged.
* @return int The previous value
*/
- function trxLevel( $level = null ) {
+ public function trxLevel( $level = null ) {
return wfSetVar( $this->mTrxLevel, $level );
}
* @param $count int The count to set, or omitted to leave it unchanged.
* @return int The error count
*/
- function errorCount( $count = null ) {
+ public function errorCount( $count = null ) {
return wfSetVar( $this->mErrorCount, $count );
}
* @param $prefix string The table prefix to set, or omitted to leave it unchanged.
* @return string The previous table prefix.
*/
- function tablePrefix( $prefix = null ) {
+ public function tablePrefix( $prefix = null ) {
return wfSetVar( $this->mTablePrefix, $prefix );
}
*
* @return LoadBalancer|null
*/
- function getLBInfo( $name = null ) {
+ public function getLBInfo( $name = null ) {
if ( is_null( $name ) ) {
return $this->mLBInfo;
} else {
* @param $name
* @param $value
*/
- function setLBInfo( $name, $value = null ) {
+ public function setLBInfo( $name, $value = null ) {
if ( is_null( $value ) ) {
$this->mLBInfo = $name;
} else {
*
* @param $lag int
*/
- function setFakeSlaveLag( $lag ) {
+ public function setFakeSlaveLag( $lag ) {
$this->mFakeSlaveLag = $lag;
}
*
* @param $enabled bool
*/
- function setFakeMaster( $enabled = true ) {
+ public function setFakeMaster( $enabled = true ) {
$this->mFakeMaster = $enabled;
}
*
* @return bool
*/
- function cascadingDeletes() {
+ public function cascadingDeletes() {
return false;
}
*
* @return bool
*/
- function cleanupTriggers() {
+ public function cleanupTriggers() {
return false;
}
*
* @return bool
*/
- function strictIPs() {
+ public function strictIPs() {
return false;
}
*
* @return bool
*/
- function realTimestamps() {
+ public function realTimestamps() {
return false;
}
*
* @return bool
*/
- function implicitGroupby() {
+ public function implicitGroupby() {
return true;
}
*
* @return bool
*/
- function implicitOrderby() {
+ public function implicitOrderby() {
return true;
}
*
* @return bool
*/
- function searchableIPs() {
+ public function searchableIPs() {
return false;
}
*
* @return bool
*/
- function functionalIndexes() {
+ public function functionalIndexes() {
return false;
}
* Return the last query that went through DatabaseBase::query()
* @return String
*/
- function lastQuery() {
+ public function lastQuery() {
return $this->mLastQuery;
}
*
* @return bool
*/
- function doneWrites() {
+ public function doneWrites() {
return $this->mDoneWrites;
}
* Is a connection to the database open?
* @return Boolean
*/
- function isOpen() {
+ public function isOpen() {
return $this->mOpened;
}
* and removes it in command line mode
* - DBO_PERSISTENT: use persistant database connection
*/
- function setFlag( $flag ) {
+ public function setFlag( $flag ) {
global $wgDebugDBTransactions;
$this->mFlags |= $flag;
if ( ( $flag & DBO_TRX) & $wgDebugDBTransactions ) {
*
* @param $flag: same as setFlag()'s $flag param
*/
- function clearFlag( $flag ) {
+ public function clearFlag( $flag ) {
global $wgDebugDBTransactions;
$this->mFlags &= ~$flag;
if ( ( $flag & DBO_TRX ) && $wgDebugDBTransactions ) {
* @param $flag: same as setFlag()'s $flag param
* @return Boolean
*/
- function getFlag( $flag ) {
+ public function getFlag( $flag ) {
return !!( $this->mFlags & $flag );
}
*
* @return string
*/
- function getProperty( $name ) {
+ public function getProperty( $name ) {
return $this->$name;
}
/**
* @return string
*/
- function getWikiID() {
+ public function getWikiID() {
if ( $this->mTablePrefix ) {
return "{$this->mDBname}-{$this->mTablePrefix}";
} else {
*
* @return bool
*/
- function isWriteQuery( $sql ) {
+ public function isWriteQuery( $sql ) {
return !preg_match( '/^(?:SELECT|BEGIN|ROLLBACK|COMMIT|SET|SHOW|EXPLAIN|\(SELECT)\b/i', $sql );
}
* @param $fname String
* @param $tempIgnore Boolean
*/
- function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) {
+ public function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) {
# Ignore errors during error handling to avoid infinite recursion
$ignore = $this->ignoreErrors( true );
++$this->mErrorCount;
*
* @return ResultWrapper
*/
- function execute( $prepared, $args = null ) {
+ public function execute( $prepared, $args = null ) {
if ( !is_array( $args ) ) {
# Pull the var args
$args = func_get_args();
* @param $args Array of arguments to fill it with
* @return string executable SQL
*/
- function fillPrepared( $preparedQuery, $args ) {
+ public function fillPrepared( $preparedQuery, $args ) {
reset( $args );
$this->preparedArgs =& $args;
*
* @return bool|mixed The value from the field, or false on failure.
*/
- function selectField( $table, $var, $cond = '', $fname = 'DatabaseBase::selectField',
+ public function selectField( $table, $var, $cond = '', $fname = 'DatabaseBase::selectField',
$options = array() )
{
if ( !is_array( $options ) ) {
* @return Array
* @see DatabaseBase::select()
*/
- function makeSelectOptions( $options ) {
+ public function makeSelectOptions( $options ) {
$preLimitTail = $postLimitTail = '';
$startOpts = '';
* DBQueryError exception will be thrown, except if the "ignore errors"
* option was set, in which case false will be returned.
*/
- function select( $table, $vars, $conds = '', $fname = 'DatabaseBase::select',
+ public function select( $table, $vars, $conds = '', $fname = 'DatabaseBase::select',
$options = array(), $join_conds = array() ) {
$sql = $this->selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
* @return string SQL query string.
* @see DatabaseBase::select()
*/
- function selectSQLText( $table, $vars, $conds = '', $fname = 'DatabaseBase::select', $options = array(), $join_conds = array() ) {
+ public function selectSQLText( $table, $vars, $conds = '', $fname = 'DatabaseBase::select',
+ $options = array(), $join_conds = array() )
+ {
if ( is_array( $vars ) ) {
$vars = implode( ',', $vars );
}
*
* @return object|bool
*/
- function selectRow( $table, $vars, $conds, $fname = 'DatabaseBase::selectRow',
+ public function selectRow( $table, $vars, $conds, $fname = 'DatabaseBase::selectRow',
$options = array(), $join_conds = array() )
{
$options = (array)$options;
* @param $fname String: calling function name (optional)
* @return Boolean: whether $table has filed $field
*/
- function fieldExists( $table, $field, $fname = 'DatabaseBase::fieldExists' ) {
+ public function fieldExists( $table, $field, $fname = 'DatabaseBase::fieldExists' ) {
$info = $this->fieldInfo( $table, $field );
return (bool)$info;
*
* @return bool|null
*/
- function indexExists( $table, $index, $fname = 'DatabaseBase::indexExists' ) {
+ public function indexExists( $table, $index, $fname = 'DatabaseBase::indexExists' ) {
$info = $this->indexInfo( $table, $index, $fname );
if ( is_null( $info ) ) {
return null;
*
* @return bool
*/
- function tableExists( $table, $fname = __METHOD__ ) {
+ public function tableExists( $table, $fname = __METHOD__ ) {
$table = $this->tableName( $table );
$old = $this->ignoreErrors( true );
$res = $this->query( "SELECT 1 FROM $table LIMIT 1", $fname );
* @param $index
* @return string
*/
- function fieldType( $res, $index ) {
+ public function fieldType( $res, $index ) {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
}
*
* @return bool
*/
- function indexUnique( $table, $index ) {
+ public function indexUnique( $table, $index ) {
$indexInfo = $this->indexInfo( $table, $index );
if ( !$indexInfo ) {
*
* @return bool
*/
- function insert( $table, $a, $fname = 'DatabaseBase::insert', $options = array() ) {
+ public function insert( $table, $a, $fname = 'DatabaseBase::insert', $options = array() ) {
# No rows to insert, easy just return now
if ( !count( $a ) ) {
return true;
*
* @return string
*/
- function makeList( $a, $mode = LIST_COMMA ) {
+ public function makeList( $a, $mode = LIST_COMMA ) {
if ( !is_array( $a ) ) {
throw new DBUnexpectedError( $this, 'DatabaseBase::makeList called with incorrect parameters' );
}
* @param $subKey String: field name to match the sub-level keys to (eg 'pl_title')
* @return Mixed: string SQL fragment, or false if no items in array.
*/
- function makeWhereFrom2d( $data, $baseKey, $subKey ) {
+ public function makeWhereFrom2d( $data, $baseKey, $subKey ) {
$conds = array();
foreach ( $data as $base => $sub ) {
* @param $field
* @return string
*/
- function bitNot( $field ) {
+ public function bitNot( $field ) {
return "(~$field)";
}
* @param $fieldRight
* @return string
*/
- function bitAnd( $fieldLeft, $fieldRight ) {
+ public function bitAnd( $fieldLeft, $fieldRight ) {
return "($fieldLeft & $fieldRight)";
}
* @param $fieldRight
* @return string
*/
- function bitOr( $fieldLeft, $fieldRight ) {
+ public function bitOr( $fieldLeft, $fieldRight ) {
return "($fieldLeft | $fieldRight)";
}
*
* @return bool Success or failure
*/
- function selectDB( $db ) {
+ public function selectDB( $db ) {
# Stub. Shouldn't cause serious problems if it's not overridden, but
# if your database engine supports a concept similar to MySQL's
# databases you may as well.
/**
* Get the current DB name
*/
- function getDBname() {
+ public function getDBname() {
return $this->mDBname;
}
/**
* Get the server hostname or IP address
*/
- function getServer() {
+ public function getServer() {
return $this->mServer;
}
* raw - Do not add identifier quotes to the table name
* @return String: full database name
*/
- function tableName( $name, $format = 'quoted' ) {
+ public function tableName( $name, $format = 'quoted' ) {
global $wgSharedDB, $wgSharedPrefix, $wgSharedTables;
# Skip the entire process when we have a string quoted on both ends.
# Note that we check the end so that we will still quote any use of
*
* @return string
*/
- function addQuotes( $s ) {
+ public function addQuotes( $s ) {
if ( $s === null ) {
return 'NULL';
} else {
* @since 1.16
* @return String: fully built LIKE statement
*/
- function buildLike() {
+ public function buildLike() {
$params = func_get_args();
if ( count( $params ) > 0 && is_array( $params[0] ) ) {
*
* @return LikeMatch
*/
- function anyChar() {
+ public function anyChar() {
return new LikeMatch( '_' );
}
*
* @return LikeMatch
*/
- function anyString() {
+ public function anyString() {
return new LikeMatch( '%' );
}
* @param $seqName string
* @return null
*/
- function nextSequenceValue( $seqName ) {
+ public function nextSequenceValue( $seqName ) {
return null;
}
* @param $index
* @return string
*/
- function useIndexClause( $index ) {
+ public function useIndexClause( $index ) {
return '';
}
* a field name or an array of field names
* @param $fname String: Calling function name (use __METHOD__) for logs/profiling
*/
- function replace( $table, $uniqueIndexes, $rows, $fname = 'DatabaseBase::replace' ) {
+ public function replace( $table, $uniqueIndexes, $rows, $fname = 'DatabaseBase::replace' ) {
$quotedTable = $this->tableName( $table );
if ( count( $rows ) == 0 ) {
* @param $fname String: Calling function name (use __METHOD__) for
* logs/profiling
*/
- function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds,
+ public function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds,
$fname = 'DatabaseBase::deleteJoin' )
{
if ( !$conds ) {
*
* @return int
*/
- function textFieldSize( $table, $field ) {
+ public function textFieldSize( $table, $field ) {
$table = $this->tableName( $table );
$sql = "SHOW COLUMNS FROM $table LIKE \"$field\";";
$res = $this->query( $sql, 'DatabaseBase::textFieldSize' );
* @return string Returns the text of the low priority option if it is
* supported, or a blank string otherwise
*/
- function lowPriorityOption() {
+ public function lowPriorityOption() {
return '';
}
*
* @return bool
*/
- function delete( $table, $conds, $fname = 'DatabaseBase::delete' ) {
+ public function delete( $table, $conds, $fname = 'DatabaseBase::delete' ) {
if ( !$conds ) {
throw new DBUnexpectedError( $this, 'DatabaseBase::delete() called with no conditions' );
}
*
* @return ResultWrapper
*/
- function insertSelect( $destTable, $srcTable, $varMap, $conds,
+ public function insertSelect( $destTable, $srcTable, $varMap, $conds,
$fname = 'DatabaseBase::insertSelect',
$insertOptions = array(), $selectOptions = array() )
{
*
* @return string
*/
- function limitResult( $sql, $limit, $offset = false ) {
+ public function limitResult( $sql, $limit, $offset = false ) {
if ( !is_numeric( $limit ) ) {
throw new DBUnexpectedError( $this, "Invalid non-numeric limit passed to limitResult()\n" );
}
* within the UNION construct.
* @return Boolean
*/
- function unionSupportsOrderAndLimit() {
+ public function unionSupportsOrderAndLimit() {
return true; // True for almost every DB supported
}
* @param $all Boolean: use UNION ALL
* @return String: SQL fragment
*/
- function unionQueries( $sqls, $all ) {
+ public function unionQueries( $sqls, $all ) {
$glue = $all ? ') UNION ALL (' : ') UNION (';
return '(' . implode( $glue, $sqls ) . ')';
}
* @param $falseVal String: SQL expression to return if false
* @return String: SQL fragment
*/
- function conditional( $cond, $trueVal, $falseVal ) {
+ public function conditional( $cond, $trueVal, $falseVal ) {
return " (CASE WHEN $cond THEN $trueVal ELSE $falseVal END) ";
}
*
* @return string
*/
- function strreplace( $orig, $old, $new ) {
+ public function strreplace( $orig, $old, $new ) {
return "REPLACE({$orig}, {$old}, {$new})";
}
*
* @return int
*/
- function getServerUptime() {
+ public function getServerUptime() {
return 0;
}
*
* @return bool
*/
- function wasDeadlock() {
+ public function wasDeadlock() {
return false;
}
*
* @return bool
*/
- function wasLockTimeout() {
+ public function wasLockTimeout() {
return false;
}
*
* @return bool
*/
- function wasErrorReissuable() {
+ public function wasErrorReissuable() {
return false;
}
*
* @return bool
*/
- function wasReadOnlyError() {
+ public function wasReadOnlyError() {
return false;
}
*
* @return bool
*/
- function deadlockLoop() {
-
+ public function deadlockLoop() {
$this->begin( __METHOD__ );
$args = func_get_args();
$function = array_shift( $args );
* greater than zero if we waited for some period of time, less than
* zero if we timed out.
*/
- function masterPosWait( DBMasterPos $pos, $timeout ) {
+ public function masterPosWait( DBMasterPos $pos, $timeout ) {
wfProfileIn( __METHOD__ );
if ( !is_null( $this->mFakeSlaveLag ) ) {
*
* @return DBMasterPos, or false if this is not a slave.
*/
- function getSlavePos() {
+ public function getSlavePos() {
if ( !is_null( $this->mFakeSlaveLag ) ) {
$pos = new MySQLMasterPos( 'fake', microtime( true ) - $this->mFakeSlaveLag );
wfDebug( __METHOD__ . ": fake slave pos = $pos\n" );
*
* @return DBMasterPos, or false if this is not a master
*/
- function getMasterPos() {
+ public function getMasterPos() {
if ( $this->mFakeMaster ) {
return new MySQLMasterPos( 'fake', microtime( true ) );
} else {
*
* @param $fname string
*/
- function begin( $fname = 'DatabaseBase::begin' ) {
+ public function begin( $fname = 'DatabaseBase::begin' ) {
$this->query( 'BEGIN', $fname );
$this->mTrxLevel = 1;
}
*
* @param $fname string
*/
- function commit( $fname = 'DatabaseBase::commit' ) {
+ public function commit( $fname = 'DatabaseBase::commit' ) {
if ( $this->mTrxLevel ) {
$this->query( 'COMMIT', $fname );
$this->mTrxLevel = 0;
*
* @param $fname string
*/
- function rollback( $fname = 'DatabaseBase::rollback' ) {
+ public function rollback( $fname = 'DatabaseBase::rollback' ) {
if ( $this->mTrxLevel ) {
$this->query( 'ROLLBACK', $fname, true );
$this->mTrxLevel = 0;
* @param $fname String: calling function name
* @return Boolean: true if operation was successful
*/
- function duplicateTableStructure( $oldName, $newName, $temporary = false,
+ public function duplicateTableStructure( $oldName, $newName, $temporary = false,
$fname = 'DatabaseBase::duplicateTableStructure' )
{
throw new MWException(
*
* @return string
*/
- function timestamp( $ts = 0 ) {
+ public function timestamp( $ts = 0 ) {
return wfTimestamp( TS_MW, $ts );
}
*
* @return string
*/
- function timestampOrNull( $ts = null ) {
+ public function timestampOrNull( $ts = null ) {
if ( is_null( $ts ) ) {
return null;
} else {
*
* @return bool|ResultWrapper
*/
- function resultObject( $result ) {
+ public function resultObject( $result ) {
if ( empty( $result ) ) {
return false;
} elseif ( $result instanceof ResultWrapper ) {
*
* @return bool Success or failure
*/
- function ping() {
+ public function ping() {
# Stub. Not essential to override.
return true;
}
*
* @return int Database replication lag in seconds
*/
- function getLag() {
+ public function getLag() {
return intval( $this->mFakeSlaveLag );
}
* @param $b string
* @return string
*/
- function encodeBlob( $b ) {
+ public function encodeBlob( $b ) {
return $b;
}
* @param $b string
* @return string
*/
- function decodeBlob( $b ) {
+ public function decodeBlob( $b ) {
return $b;
}
* generated dynamically using $filename
* @return bool|string
*/
- function sourceFile( $filename, $lineCallback = false, $resultCallback = false, $fname = false ) {
+ public function sourceFile(
+ $filename, $lineCallback = false, $resultCallback = false, $fname = false
+ ) {
wfSuppressWarnings();
$fp = fopen( $filename, 'r' );
wfRestoreWarnings();
*
* @param $vars bool|array mapping variable name to value.
*/
- function setSchemaVars( $vars ) {
+ public function setSchemaVars( $vars ) {
$this->mSchemaVars = $vars;
}
/**
* Return aggregated value function call
*/
- function aggregateValue ( $valuedata, $valuename = 'value' ) {
+ public function aggregateValue( $valuedata, $valuename = 'value' ) {
return $valuedata;
}
/**
* Return aggregated value function call
*/
- function aggregateValue( $valuedata, $valuename = 'value' ) {
+ public function aggregateValue( $valuedata, $valuename = 'value' ) {
return $valuedata;
}
/**
* @see FileBackendStore::__construct()
* Additional $config params include:
- * basePath : File system directory that holds containers.
- * containerPaths : Map of container names to custom file system directories.
- * This should only be used for backwards-compatibility.
- * fileMode : Octal UNIX file permissions to use on files stored.
+ * - basePath : File system directory that holds containers.
+ * - containerPaths : Map of container names to custom file system directories.
+ * This should only be used for backwards-compatibility.
+ * - fileMode : Octal UNIX file permissions to use on files stored.
*/
public function __construct( array $config ) {
parent::__construct( $config );
* This should only be called from within FileBackendGroup.
*
* $config includes:
- * 'name' : The unique name of this backend.
- * This should consist of alphanumberic, '-', and '_' characters.
- * This name should not be changed after use.
- * 'wikiId' : Prefix to container names that is unique to this wiki.
- * It should only consist of alphanumberic, '-', and '_' characters.
- * 'lockManager' : Registered name of a file lock manager to use.
- * 'fileJournal' : File journal configuration; see FileJournal::factory().
- * Journals simply log changes to files stored in the backend.
- * 'readOnly' : Write operations are disallowed if this is a non-empty string.
- * It should be an explanation for the backend being read-only.
- * 'parallelize' : When to do file operations in parallel (when possible).
- * Allowed values are "implicit", "explicit" and "off".
- * 'concurrency' : How many file operations can be done in parallel.
+ * - name : The unique name of this backend.
+ * This should consist of alphanumberic, '-', and '_' characters.
+ * This name should not be changed after use.
+ * - wikiId : Prefix to container names that is unique to this wiki.
+ * It should only consist of alphanumberic, '-', and '_' characters.
+ * - lockManager : Registered name of a file lock manager to use.
+ * - fileJournal : File journal configuration; see FileJournal::factory().
+ * Journals simply log changes to files stored in the backend.
+ * - readOnly : Write operations are disallowed if this is a non-empty string.
+ * It should be an explanation for the backend being read-only.
+ * - parallelize : When to do file operations in parallel (when possible).
+ * Allowed values are "implicit", "explicit" and "off".
+ * - concurrency : How many file operations can be done in parallel.
*
* @param $config Array
* @throws MWException
? $config['lockManager']
: LockManagerGroup::singleton()->get( $config['lockManager'] );
$this->fileJournal = isset( $config['fileJournal'] )
- ? FileJournal::factory( $config['fileJournal'], $this->name )
+ ? ( ( $config['fileJournal'] instanceof FileJournal )
+ ? $config['fileJournal']
+ : FileJournal::factory( $config['fileJournal'], $this->name ) )
: FileJournal::factory( array( 'class' => 'NullFileJournal' ), $this->name );
$this->readOnly = isset( $config['readOnly'] )
? (string)$config['readOnly']
* @endcode
*
* Boolean flags for operations (operation-specific):
- * 'ignoreMissingSource' : The operation will simply succeed and do
- * nothing if the source file does not exist.
- * 'overwrite' : Any destination file will be overwritten.
- * 'overwriteSame' : An error will not be given if a file already
- * exists at the destination that has the same
- * contents as the new contents to be written there.
+ * - ignoreMissingSource : The operation will simply succeed and do
+ * nothing if the source file does not exist.
+ * - overwrite : Any destination file will be overwritten.
+ * - overwriteSame : An error will not be given if a file already
+ * exists at the destination that has the same
+ * contents as the new contents to be written there.
*
* $opts is an associative of boolean flags, including:
- * 'force' : Operation precondition errors no longer trigger an abort.
- * Any remaining operations are still attempted. Unexpected
- * failures may still cause remaning operations to be aborted.
- * 'nonLocking' : No locks are acquired for the operations.
- * This can increase performance for non-critical writes.
- * This has no effect unless the 'force' flag is set.
- * 'allowStale' : Don't require the latest available data.
- * This can increase performance for non-critical writes.
- * This has no effect unless the 'force' flag is set.
- * 'nonJournaled' : Don't log this operation batch in the file journal.
- * This limits the ability of recovery scripts.
- * 'parallelize' : Try to do operations in parallel when possible.
+ * - force : Operation precondition errors no longer trigger an abort.
+ * Any remaining operations are still attempted. Unexpected
+ * failures may still cause remaning operations to be aborted.
+ * - nonLocking : No locks are acquired for the operations.
+ * This can increase performance for non-critical writes.
+ * This has no effect unless the 'force' flag is set.
+ * - allowStale : Don't require the latest available data.
+ * This can increase performance for non-critical writes.
+ * This has no effect unless the 'force' flag is set.
+ * - nonJournaled : Don't log this operation batch in the file journal.
+ * This limits the ability of recovery scripts.
+ * - parallelize : Try to do operations in parallel when possible.
+ * - bypassReadOnly : Allow writes in read-only mode (since 1.20).
*
* @remarks Remarks on locking:
* File system paths given to operations should refer to files that are
* will reflect each operation attempted.
*
* The status will be "OK" unless:
- * a) unexpected operation errors occurred (network partitions, disk full...)
- * b) significant operation errors occured and 'force' was not set
+ * - a) unexpected operation errors occurred (network partitions, disk full...)
+ * - b) significant operation errors occured and 'force' was not set
*
* @param $ops Array List of operations to execute in order
* @param $opts Array Batch operation options
* @return Status
*/
final public function doOperations( array $ops, array $opts = array() ) {
- if ( $this->isReadOnly() ) {
+ if ( empty( $opts['bypassReadOnly'] ) && $this->isReadOnly() ) {
return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly );
}
if ( empty( $opts['force'] ) ) { // sanity
* - move
* - delete
* - null
+ *
* a) Create a new file in storage with the contents of a string
* @code
* array(
* @endcode
*
* @par Boolean flags for operations (operation-specific):
- * 'ignoreMissingSource' : The operation will simply succeed and do
- * nothing if the source file does not exist.
+ * - ignoreMissingSource : The operation will simply succeed and do
+ * nothing if the source file does not exist.
+ *
+ * $opts is an associative of boolean flags, including:
+ * - bypassReadOnly : Allow writes in read-only mode (since 1.20)
*
* @par Return value:
* This returns a Status, which contains all warnings and fatals that occured
* considered "OK" as long as no fatal errors occured.
*
* @param $ops Array Set of operations to execute
+ * @param $opts Array Batch operation options
* @return Status
* @since 1.20
*/
- final public function doQuickOperations( array $ops ) {
- if ( $this->isReadOnly() ) {
+ final public function doQuickOperations( array $ops, array $opts = array() ) {
+ if ( empty( $opts['bypassReadOnly'] ) && $this->isReadOnly() ) {
return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly );
}
foreach ( $ops as &$op ) {
* These flags should always be set for directories that have private files.
*
* $params include:
- * dir : storage directory
- * noAccess : try to deny file access (@since 1.20)
- * noListing : try to deny file listing (@since 1.20)
+ * - dir : storage directory
+ * - noAccess : try to deny file access (since 1.20)
+ * - noListing : try to deny file listing (since 1.20)
+ * - bypassReadOnly : allow writes in read-only mode (since 1.20)
*
* @param $params Array
* @return Status
*/
final public function prepare( array $params ) {
- if ( $this->isReadOnly() ) {
+ if ( empty( $params['bypassReadOnly'] ) && $this->isReadOnly() ) {
return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly );
}
return $this->doPrepare( $params );
*
* @param $params Array
* $params include:
- * - dir : storage directory
- * - noAccess : try to deny file access
- * - noListing : try to deny file listing
+ * - dir : storage directory
+ * - noAccess : try to deny file access
+ * - noListing : try to deny file listing
+ * - bypassReadOnly : allow writes in read-only mode (since 1.20)
* @return Status
*/
final public function secure( array $params ) {
- if ( $this->isReadOnly() ) {
+ if ( empty( $params['bypassReadOnly'] ) && $this->isReadOnly() ) {
return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly );
}
return $this->doSecure( $params );
* This essentially can undo the result of secure() calls.
*
* $params include:
- * dir : storage directory
- * access : try to allow file access
- * listing : try to allow file listing
+ * - dir : storage directory
+ * - access : try to allow file access
+ * - listing : try to allow file listing
+ * - bypassReadOnly : allow writes in read-only mode (since 1.20)
*
* @param $params Array
* @return Status
* @since 1.20
*/
final public function publish( array $params ) {
- if ( $this->isReadOnly() ) {
+ if ( empty( $params['bypassReadOnly'] ) && $this->isReadOnly() ) {
return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly );
}
return $this->doPublish( $params );
*
* @param $params Array
* $params include:
- * dir : storage directory
- * recursive : recursively delete empty subdirectories first (@since 1.20)
+ * - dir : storage directory
+ * - recursive : recursively delete empty subdirectories first (since 1.20)
+ * - bypassReadOnly : allow writes in read-only mode (since 1.20)
* @return Status
*/
final public function clean( array $params ) {
- if ( $this->isReadOnly() ) {
+ if ( empty( $params['bypassReadOnly'] ) && $this->isReadOnly() ) {
return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly );
}
return $this->doClean( $params );
*
* @param $params Array
* $params include:
- * src : source storage path
- * latest : use the latest available data
+ * - src : source storage path
+ * - latest : use the latest available data
* @return bool|null Returns null on failure
*/
abstract public function fileExists( array $params );
*
* @param $params Array
* $params include:
- * src : source storage path
- * latest : use the latest available data
+ * - src : source storage path
+ * - latest : use the latest available data
* @return string|bool TS_MW timestamp or false on failure
*/
abstract public function getFileTimestamp( array $params );
*
* @param $params Array
* $params include:
- * src : source storage path
- * latest : use the latest available data
+ * - src : source storage path
+ * - latest : use the latest available data
* @return string|bool Returns false on failure
*/
abstract public function getFileContents( array $params );
*
* @param $params Array
* $params include:
- * src : source storage path
- * latest : use the latest available data
+ * - src : source storage path
+ * - latest : use the latest available data
* @return integer|bool Returns false on failure
*/
abstract public function getFileSize( array $params );
* Get quick information about a file at a storage path in the backend.
* If the file does not exist, then this returns false.
* Otherwise, the result is an associative array that includes:
- * mtime : the last-modified timestamp (TS_MW)
- * size : the file size (bytes)
+ * - mtime : the last-modified timestamp (TS_MW)
+ * - size : the file size (bytes)
* Additional values may be included for internal use only.
*
* @param $params Array
* $params include:
- * src : source storage path
- * latest : use the latest available data
+ * - src : source storage path
+ * - latest : use the latest available data
* @return Array|bool|null Returns null on failure
*/
abstract public function getFileStat( array $params );
*
* @param $params Array
* $params include:
- * src : source storage path
- * latest : use the latest available data
+ * - src : source storage path
+ * - latest : use the latest available data
* @return string|bool Hash string or false on failure
*/
abstract public function getFileSha1Base36( array $params );
*
* @param $params Array
* $params include:
- * src : source storage path
- * latest : use the latest available data
+ * - src : source storage path
+ * - latest : use the latest available data
* @return Array
*/
abstract public function getFileProps( array $params );
*
* @param $params Array
* $params include:
- * src : source storage path
- * headers : additional HTTP headers to send on success
- * latest : use the latest available data
+ * - src : source storage path
+ * - headers : additional HTTP headers to send on success
+ * - latest : use the latest available data
* @return Status
*/
abstract public function streamFile( array $params );
*
* @param $params Array
* $params include:
- * src : source storage path
- * latest : use the latest available data
+ * - src : source storage path
+ * - latest : use the latest available data
* @return FSFile|null Returns null on failure
*/
abstract public function getLocalReference( array $params );
*
* @param $params Array
* $params include:
- * src : source storage path
- * latest : use the latest available data
+ * - src : source storage path
+ * - latest : use the latest available data
* @return TempFSFile|null Returns null on failure
*/
abstract public function getLocalCopy( array $params );
*
* @param $params array
* $params include:
- * dir : storage directory
+ * - dir : storage directory
* @return bool|null Returns null on failure
* @since 1.20
*/
*
* @param $params array
* $params include:
- * dir : storage directory
- * topOnly : only return direct child dirs of the directory
+ * - dir : storage directory
+ * - topOnly : only return direct child dirs of the directory
* @return Traversable|Array|null Returns null on failure
* @since 1.20
*/
*
* @param $params array
* $params include:
- * dir : storage directory
+ * - dir : storage directory
* @return Traversable|Array|null Returns null on failure
* @since 1.20
*/
*
* @param $params array
* $params include:
- * dir : storage directory
- * topOnly : only return direct child files of the directory (@since 1.20)
+ * - dir : storage directory
+ * - topOnly : only return direct child files of the directory (since 1.20)
* @return Traversable|Array|null Returns null on failure
*/
abstract public function getFileList( array $params );
*
* @param $params array
* $params include:
- * dir : storage directory
+ * - dir : storage directory
* @return Traversable|Array|null Returns null on failure
* @since 1.20
*/
/* Possible internal backend consistency checks */
const CHECK_SIZE = 1;
const CHECK_TIME = 2;
+ const CHECK_SHA1 = 4;
/**
* Construct a proxy backend that consists of several internal backends.
+ * Locking, journaling, and read-only checks are handled by the proxy backend.
+ *
* Additional $config params include:
- * 'backends' : Array of backend config and multi-backend settings.
- * Each value is the config used in the constructor of a
- * FileBackendStore class, but with these additional settings:
- * 'class' : The name of the backend class
- * 'isMultiMaster' : This must be set for one backend.
- * 'template: : If given a backend name, this will use
- * the config of that backend as a template.
- * Values specified here take precedence.
- * 'syncChecks' : Integer bitfield of internal backend sync checks to perform.
- * Possible bits include self::CHECK_SIZE and self::CHECK_TIME.
- * The checks are done before allowing any file operations.
+ * - backends : Array of backend config and multi-backend settings.
+ * Each value is the config used in the constructor of a
+ * FileBackendStore class, but with these additional settings:
+ * - class : The name of the backend class
+ * - isMultiMaster : This must be set for one backend.
+ * - template: : If given a backend name, this will use
+ * the config of that backend as a template.
+ * Values specified here take precedence.
+ * - syncChecks : Integer bitfield of internal backend sync checks to perform.
+ * Possible bits include the FileBackendMultiWrite::CHECK_* constants.
+ * There are constants for SIZE, TIME, and SHA1.
+ * The checks are done before allowing any file operations.
* @param $config Array
+ * @throws MWException
*/
public function __construct( array $config ) {
parent::__construct( $config );
foreach ( $config['backends'] as $index => $config ) {
if ( isset( $config['template'] ) ) {
// Config is just a modified version of a registered backend's.
- // This should only be used when that config is used only be this backend.
+ // This should only be used when that config is used only by this backend.
$config = $config + FileBackendGroup::singleton()->config( $config['template'] );
}
$name = $config['name'];
throw new MWException( "Two or more backends defined with the name $name." );
}
$namesUsed[$name] = 1;
- if ( !isset( $config['class'] ) ) {
- throw new MWException( 'No class given for a backend config.' );
- }
- $class = $config['class'];
- $this->backends[$index] = new $class( $config );
+ // Alter certain sub-backend settings for sanity
+ unset( $config['readOnly'] ); // use proxy backend setting
+ unset( $config['fileJournal'] ); // use proxy backend journal
+ $config['lockManager'] = 'nullLockManager'; // lock under proxy backend
if ( !empty( $config['isMultiMaster'] ) ) {
if ( $this->masterIndex >= 0 ) {
throw new MWException( 'More than one master backend defined.' );
}
- $this->masterIndex = $index;
+ $this->masterIndex = $index; // this is the "master"
+ $config['fileJournal'] = $this->fileJournal; // log under proxy backend
}
+ // Create sub-backend object
+ if ( !isset( $config['class'] ) ) {
+ throw new MWException( 'No class given for a backend config.' );
+ }
+ $class = $config['class'];
+ $this->backends[$index] = new $class( $config );
}
if ( $this->masterIndex < 0 ) { // need backends and must have a master
throw new MWException( 'No master backend defined.' );
final protected function doOperationsInternal( array $ops, array $opts ) {
$status = Status::newGood();
- $performOps = array(); // list of FileOp objects
- $paths = array(); // storage paths read from or written to
- // Build up a list of FileOps. The list will have all the ops
- // for one backend, then all the ops for the next, and so on.
- // These batches of ops are all part of a continuous array.
- // Also build up a list of files read/changed...
- foreach ( $this->backends as $index => $backend ) {
- $backendOps = $this->substOpBatchPaths( $ops, $backend );
- // Add on the operation batch for this backend
- $performOps = array_merge( $performOps,
- $backend->getOperationsInternal( $backendOps ) );
- if ( $index == 0 ) { // first batch
- // Get the files used for these operations. Each backend has a batch of
- // the same operations, so we only need to get them from the first batch.
- $paths = $backend->getPathsToLockForOpsInternal( $performOps );
- // Get the paths under the proxy backend's name
- $paths['sh'] = $this->unsubstPaths( $paths['sh'] );
- $paths['ex'] = $this->unsubstPaths( $paths['ex'] );
- }
- }
+ $mbe = $this->backends[$this->masterIndex]; // convenience
+ // Get the paths to lock from the master backend
+ $realOps = $this->substOpBatchPaths( $ops, $mbe );
+ $paths = $mbe->getPathsToLockForOpsInternal( $mbe->getOperationsInternal( $realOps ) );
+ // Get the paths under the proxy backend's name
+ $paths['sh'] = $this->unsubstPaths( $paths['sh'] );
+ $paths['ex'] = $this->unsubstPaths( $paths['ex'] );
// Try to lock those files for the scope of this function...
if ( empty( $opts['nonLocking'] ) ) {
// Try to lock those files for the scope of this function...
return $status; // abort
}
}
-
// Clear any cache entries (after locks acquired)
$this->clearCache();
-
// Do a consistency check to see if the backends agree
- if ( count( $this->backends ) > 1 ) {
- $status->merge( $this->consistencyCheck( array_merge( $paths['sh'], $paths['ex'] ) ) );
- if ( !$status->isOK() ) {
- return $status; // abort
+ $status->merge( $this->consistencyCheck( array_merge( $paths['sh'], $paths['ex'] ) ) );
+ if ( !$status->isOK() ) {
+ return $status; // abort
+ }
+ // Actually attempt the operation batch on the master backend...
+ $masterStatus = $mbe->doOperations( $realOps, $opts );
+ $status->merge( $masterStatus );
+ // Propagate the operations to the clone backends...
+ foreach ( $this->backends as $index => $backend ) {
+ if ( $index !== $this->masterIndex ) { // not done already
+ $realOps = $this->substOpBatchPaths( $ops, $backend );
+ $status->merge( $backend->doOperations( $realOps, $opts ) );
}
}
-
- // Actually attempt the operation batch...
- $subStatus = FileOpBatch::attempt( $performOps, $opts, $this->fileJournal );
-
- $success = array();
- $failCount = 0;
- $successCount = 0;
// Make 'success', 'successCount', and 'failCount' fields reflect
// the overall operation, rather than all the batches for each backend.
// Do this by only using success values from the master backend's batch.
- $batchStart = $this->masterIndex * count( $ops );
- $batchEnd = $batchStart + count( $ops ) - 1;
- for ( $i = $batchStart; $i <= $batchEnd; $i++ ) {
- if ( !isset( $subStatus->success[$i] ) ) {
- break; // failed out before trying this op
- } elseif ( $subStatus->success[$i] ) {
- ++$successCount;
- } else {
- ++$failCount;
- }
- $success[] = $subStatus->success[$i];
- }
- $subStatus->success = $success;
- $subStatus->successCount = $successCount;
- $subStatus->failCount = $failCount;
-
- // Merge errors into status fields
- $status->merge( $subStatus );
- $status->success = $subStatus->success; // not done in merge()
+ $status->success = $masterStatus->success;
+ $status->successCount = $masterStatus->successCount;
+ $status->failCount = $masterStatus->failCount;
return $status;
}
*/
public function consistencyCheck( array $paths ) {
$status = Status::newGood();
- if ( $this->syncChecks == 0 ) {
+ if ( $this->syncChecks == 0 || count( $this->backends ) <= 1 ) {
return $status; // skip checks
}
$mBackend = $this->backends[$this->masterIndex];
foreach ( array_unique( $paths ) as $path ) {
$params = array( 'src' => $path, 'latest' => true );
+ $mParams = $this->substOpPaths( $params, $mBackend );
// Stat the file on the 'master' backend
- $mStat = $mBackend->getFileStat( $this->substOpPaths( $params, $mBackend ) );
+ $mStat = $mBackend->getFileStat( $mParams );
+ if ( $this->syncChecks & self::CHECK_SHA1 ) {
+ $mSha1 = $mBackend->getFileSha1( $mParams );
+ } else {
+ $mSha1 = false;
+ }
+ $mUsable = $mBackend->isPathUsableInternal( $mParams['src'] );
// Check of all clone backends agree with the master...
foreach ( $this->backends as $index => $cBackend ) {
if ( $index === $this->masterIndex ) {
continue; // master
}
- $cStat = $cBackend->getFileStat( $this->substOpPaths( $params, $cBackend ) );
+ $cParams = $this->substOpPaths( $params, $cBackend );
+ $cStat = $cBackend->getFileStat( $cParams );
if ( $mStat ) { // file is in master
if ( !$cStat ) { // file should exist
$status->fatal( 'backend-fail-synced', $path );
continue;
}
}
+ if ( $this->syncChecks & self::CHECK_SHA1 ) {
+ if ( $cBackend->getFileSha1( $cParams ) !== $mSha1 ) { // wrong SHA1
+ $status->fatal( 'backend-fail-synced', $path );
+ continue;
+ }
+ }
} else { // file is not in master
if ( $cStat ) { // file should not exist
$status->fatal( 'backend-fail-synced', $path );
}
}
+ if ( $mUsable !== $cBackend->isPathUsableInternal( $cParams['src'] ) ) {
+ $status->fatal( 'backend-fail-synced', $path );
+ }
}
}
* @see FileBackend::doQuickOperationsInternal()
* @return Status
*/
- public function doQuickOperationsInternal( array $ops ) {
+ protected function doQuickOperationsInternal( array $ops ) {
+ $status = Status::newGood();
// Do the operations on the master backend; setting Status fields...
$realOps = $this->substOpBatchPaths( $ops, $this->backends[$this->masterIndex] );
- $status = $this->backends[$this->masterIndex]->doQuickOperations( $realOps );
+ $masterStatus = $this->backends[$this->masterIndex]->doQuickOperations( $realOps );
+ $status->merge( $masterStatus );
// Propagate the operations to the clone backends...
foreach ( $this->backends as $index => $backend ) {
if ( $index !== $this->masterIndex ) { // not done already
$status->merge( $backend->doQuickOperations( $realOps ) );
}
}
+ // Make 'success', 'successCount', and 'failCount' fields reflect
+ // the overall operation, rather than all the batches for each backend.
+ // Do this by only using success values from the master backend's batch.
+ $status->success = $masterStatus->success;
+ $status->successCount = $masterStatus->successCount;
+ $status->failCount = $masterStatus->failCount;
return $status;
}
* Do not call this function from places outside FileBackend and FileOp.
*
* $params include:
- * content : the raw file contents
- * dst : destination storage path
- * overwrite : overwrite any file that exists at the destination
- * async : Status will be returned immediately if supported.
+ * - content : the raw file contents
+ * - dst : destination storage path
+ * - overwrite : overwrite any file that exists at the destination
+ * - async : Status will be returned immediately if supported.
* If the status is OK, then its value field will be
* set to a FileBackendStoreOpHandle object.
*
* Do not call this function from places outside FileBackend and FileOp.
*
* $params include:
- * src : source path on disk
- * dst : destination storage path
- * overwrite : overwrite any file that exists at the destination
- * async : Status will be returned immediately if supported.
+ * - src : source path on disk
+ * - dst : destination storage path
+ * - overwrite : overwrite any file that exists at the destination
+ * - async : Status will be returned immediately if supported.
* If the status is OK, then its value field will be
* set to a FileBackendStoreOpHandle object.
*
* Do not call this function from places outside FileBackend and FileOp.
*
* $params include:
- * src : source storage path
- * dst : destination storage path
- * overwrite : overwrite any file that exists at the destination
- * async : Status will be returned immediately if supported.
+ * - src : source storage path
+ * - dst : destination storage path
+ * - overwrite : overwrite any file that exists at the destination
+ * - async : Status will be returned immediately if supported.
* If the status is OK, then its value field will be
* set to a FileBackendStoreOpHandle object.
*
* Do not call this function from places outside FileBackend and FileOp.
*
* $params include:
- * src : source storage path
- * ignoreMissingSource : do nothing if the source file does not exist
- * async : Status will be returned immediately if supported.
+ * - src : source storage path
+ * - ignoreMissingSource : do nothing if the source file does not exist
+ * - async : Status will be returned immediately if supported.
* If the status is OK, then its value field will be
* set to a FileBackendStoreOpHandle object.
*
* Do not call this function from places outside FileBackend and FileOp.
*
* $params include:
- * src : source storage path
- * dst : destination storage path
- * overwrite : overwrite any file that exists at the destination
- * async : Status will be returned immediately if supported.
+ * - src : source storage path
+ * - dst : destination storage path
+ * - overwrite : overwrite any file that exists at the destination
+ * - async : Status will be returned immediately if supported.
* If the status is OK, then its value field will be
* set to a FileBackendStoreOpHandle object.
*
* @see FileBackend::doOperationsInternal()
* @return Status
*/
- protected function doOperationsInternal( array $ops, array $opts ) {
+ final protected function doOperationsInternal( array $ops, array $opts ) {
wfProfileIn( __METHOD__ );
wfProfileIn( __METHOD__ . '-' . $this->name );
$status = Status::newGood();
/**
* Store a file into the backend from a file on the file system.
* Parameters similar to FileBackendStore::storeInternal(), which include:
- * src : source path on file system
- * dst : destination storage path
- * overwrite : do nothing and pass if an identical file exists at destination
- * overwriteSame : override any existing file at destination
+ * - src : source path on file system
+ * - dst : destination storage path
+ * - overwrite : do nothing and pass if an identical file exists at destination
+ * - overwriteSame : override any existing file at destination
*/
class StoreFileOp extends FileOp {
/**
/**
* Create a file in the backend with the given content.
* Parameters similar to FileBackendStore::createInternal(), which include:
- * content : the raw file contents
- * dst : destination storage path
- * overwrite : do nothing and pass if an identical file exists at destination
- * overwriteSame : override any existing file at destination
+ * - content : the raw file contents
+ * - dst : destination storage path
+ * - overwrite : do nothing and pass if an identical file exists at destination
+ * - overwriteSame : override any existing file at destination
*/
class CreateFileOp extends FileOp {
protected function allowedParams() {
/**
* Copy a file from one storage path to another in the backend.
* Parameters similar to FileBackendStore::copyInternal(), which include:
- * src : source storage path
- * dst : destination storage path
- * overwrite : do nothing and pass if an identical file exists at destination
- * overwriteSame : override any existing file at destination
+ * - src : source storage path
+ * - dst : destination storage path
+ * - overwrite : do nothing and pass if an identical file exists at destination
+ * - overwriteSame : override any existing file at destination
*/
class CopyFileOp extends FileOp {
/**
/**
* Move a file from one storage path to another in the backend.
* Parameters similar to FileBackendStore::moveInternal(), which include:
- * src : source storage path
- * dst : destination storage path
- * overwrite : do nothing and pass if an identical file exists at destination
- * overwriteSame : override any existing file at destination
+ * - src : source storage path
+ * - dst : destination storage path
+ * - overwrite : do nothing and pass if an identical file exists at destination
+ * - overwriteSame : override any existing file at destination
*/
class MoveFileOp extends FileOp {
/**
/**
* Delete a file at the given storage path from the backend.
* Parameters similar to FileBackendStore::deleteInternal(), which include:
- * src : source storage path
- * ignoreMissingSource : don't return an error if the file does not exist
+ * - src : source storage path
+ * - ignoreMissingSource : don't return an error if the file does not exist
*/
class DeleteFileOp extends FileOp {
/**
* Callers are responsible for handling file locking.
*
* $opts is an array of options, including:
- * 'force' : Errors that would normally cause a rollback do not.
- * The remaining operations are still attempted if any fail.
- * 'allowStale' : Don't require the latest available data.
- * This can increase performance for non-critical writes.
- * This has no effect unless the 'force' flag is set.
- * 'nonJournaled' : Don't log this operation batch in the file journal.
- * 'concurrency' : Try to do this many operations in parallel when possible.
+ * - force : Errors that would normally cause a rollback do not.
+ * The remaining operations are still attempted if any fail.
+ * - allowStale : Don't require the latest available data.
+ * This can increase performance for non-critical writes.
+ * This has no effect unless the 'force' flag is set.
+ * - nonJournaled : Don't log this operation batch in the file journal.
+ * - concurrency : Try to do this many operations in parallel when possible.
*
* The resulting Status will be "OK" unless:
- * a) unexpected operation errors occurred (network partitions, disk full...)
- * b) significant operation errors occured and 'force' was not set
+ * - a) unexpected operation errors occurred (network partitions, disk full...)
+ * - b) significant operation errors occured and 'force' was not set
*
* @param $performOps Array List of FileOp operations
* @param $opts Array Batch operation options
/**
* @see FileBackendStore::__construct()
* Additional $config params include:
- * swiftAuthUrl : Swift authentication server URL
- * swiftUser : Swift user used by MediaWiki (account:username)
- * swiftKey : Swift authentication key for the above user
- * swiftAuthTTL : Swift authentication TTL (seconds)
- * swiftAnonUser : Swift user used for end-user requests (account:username).
- * If set, then views of public containers are assumed to go
- * through this user. If not set, then public containers are
- * accessible to unauthenticated requests via ".r:*" in the ACL.
- * swiftUseCDN : Whether a Cloud Files Content Delivery Network is set up
- * swiftCDNExpiry : How long (in seconds) to store content in the CDN.
- * If files may likely change, this should probably not exceed
- * a few days. For example, deletions may take this long to apply.
- * If object purging is enabled, however, this is not an issue.
- * swiftCDNPurgable : Whether object purge requests are allowed by the CDN.
- * shardViaHashLevels : Map of container names to sharding config with:
- * 'base' : base of hash characters, 16 or 36
- * 'levels' : the number of hash levels (and digits)
- * 'repeat' : hash subdirectories are prefixed with all the
- * parent hash directory names (e.g. "a/ab/abc")
+ * - swiftAuthUrl : Swift authentication server URL
+ * - swiftUser : Swift user used by MediaWiki (account:username)
+ * - swiftKey : Swift authentication key for the above user
+ * - swiftAuthTTL : Swift authentication TTL (seconds)
+ * - swiftAnonUser : Swift user used for end-user requests (account:username).
+ * If set, then views of public containers are assumed to go
+ * through this user. If not set, then public containers are
+ * accessible to unauthenticated requests via ".r:*" in the ACL.
+ * - swiftUseCDN : Whether a Cloud Files Content Delivery Network is set up
+ * - swiftCDNExpiry : How long (in seconds) to store content in the CDN.
+ * If files may likely change, this should probably not exceed
+ * a few days. For example, deletions may take this long to apply.
+ * If object purging is enabled, however, this is not an issue.
+ * - swiftCDNPurgable : Whether object purge requests are allowed by the CDN.
+ * - shardViaHashLevels : Map of container names to sharding config with:
+ * - base : base of hash characters, 16 or 36
+ * - levels : the number of hash levels (and digits)
+ * - repeat : hash subdirectories are prefixed with all the
+ * parent hash directory names (e.g. "a/ab/abc")
*/
public function __construct( array $config ) {
parent::__construct( $config );
*
* $readGrps is a list of the possible criteria for a request to have
* access to read a container. Each item is one of the following formats:
- * account:user - Grants access if the request is by the given user
- * .r:<regex> - Grants access if the request is from a referrer host that
- * matches the expression and the request is not for a listing.
- * Setting this to '*' effectively makes a container public.
- * .rlistings:<regex> - Grants access if the request is from a referrer host that
- * matches the expression and the request for a listing.
+ * - account:user : Grants access if the request is by the given user
+ * - .r:<regex> : Grants access if the request is from a referrer host that
+ * matches the expression and the request is not for a listing.
+ * Setting this to '*' effectively makes a container public.
+ * - .rlistings:<regex> : Grants access if the request is from a referrer host that
+ * matches the expression and the request for a listing.
+ *
* $writeGrps is a list of the possible criteria for a request to have
* access to write to a container. Each item is of the following format:
- * account:user - Grants access if the request is by the given user
+ * - account:user : Grants access if the request is by the given user
*
* @see http://swift.openstack.org/misc.html#acls
*
$met = ini_get( 'max_execution_time' ); // this is 0 in CLI mode
$this->lockExpiry = $met ? 2*(int)$met : 2*3600;
- $this->session = wfRandomString( 31 );
+ $this->session = wfRandomString( 32 );
}
/**
protected function acquireMutexes( MemcachedBagOStuff $memc, array $keys ) {
$lockedKeys = array();
+ // Acquire the keys in lexicographical order, to avoid deadlock problems.
+ // If P1 is waiting to acquire a key P2 has, P2 can't also be waiting for a key P1 has.
+ sort( $keys );
+
+ // Try to quickly loop to acquire the keys, but back off after a few rounds.
+ // This reduces memcached spam, especially in the rare case where a server acquires
+ // some lock keys and dies without releasing them. Lock keys expire after a few minutes.
+ $rounds = 0;
$start = microtime( true );
do {
+ if ( ( ++$rounds % 4 ) == 0 ) {
+ usleep( 1000*50 ); // 50 ms
+ }
foreach ( array_diff( $keys, $lockedKeys ) as $key ) {
if ( $memc->add( "$key:mutex", 1, 180 ) ) { // lock record
$lockedKeys[] = $key;
+ } else {
+ continue; // acquire in order
}
}
} while ( count( $lockedKeys ) < count( $keys ) && ( microtime( true ) - $start ) <= 6 );
* write your messages. This appears to work well enough. Basic formatting and
* external links work just fine.
*
- * But in case a translator decides to throw in a #ifexist or internal link or
+ * But in case a translator decides to throw in a "#ifexist" or internal link or
* whatever, this function is guarded to catch the attempted DB access and to present
* some fallback text.
*
);
if ( $this->linkFlood ) {
- $element .= Linker::userToolLinks(
+ $element .= Linker::userToolLinksRedContribs(
$user->getId(),
$user->getName(),
- true, // Red if no edits
- 0, // Flags
$user->getEditCount()
);
}
'redeye' => ( $val & bindec( '01000000' ) ) >> 6,
// 'reserved' => ($val & bindec( '10000000' )) >> 7,
);
-
+ $flashMsgs = array();
# We do not need to handle unknown values since all are used.
foreach ( $flashDecode as $subTag => $subValue ) {
# We do not need any message for zeroed values.
* @param $lang String lang code of item or false
* @param $default Boolean if it is default value.
* @param $noHtml Boolean If to avoid html (for back-compat)
- * @return language item (Note: despite how this looks,
- * this is treated as wikitext not html).
+ * @throws MWException
+ * @return string language item (Note: despite how this looks,
+ * this is treated as wikitext not html).
*/
private static function langItem( $value, $lang, $default = false, $noHtml = false ) {
if ( $lang === false && $default === false) {
* Format a number, convert numbers from fractions into floating point
* numbers, joins arrays of numbers with commas.
*
- * @private
- *
* @param $num Mixed: the value to format
- * @param $round float|int digits to round to or false.
+ * @param $round float|int|bool digits to round to or false.
* @return mixed A floating point number or whatever we were fed
*/
static function formatNum( $num, $round = false ) {
* a string, not an int.
*
* @param $val String: The 8 digit news code.
- * @return srting The human readable form
+ * @return string The human readable form
*/
static private function convertNewsCode( $val ) {
if ( !preg_match( '/^\d{8}$/D', $val ) ) {
* Format a coordinate value, convert numbers from floating point
* into degree minute second representation.
*
- * @param $coord Array: degrees, minutes and seconds
+ * @param $coord int degrees, minutes and seconds
* @param $type String: latitude or longitude (for if its a NWS or E)
* @return mixed A floating point number or whatever we were fed
*/
$nCoord = -$coord;
if ( $type === 'latitude' ) {
$ref = 'S';
- }
- elseif ( $type === 'longitude' ) {
+ } elseif ( $type === 'longitude' ) {
$ref = 'W';
}
- }
- else {
+ } else {
$nCoord = $coord;
if ( $type === 'latitude' ) {
$ref = 'N';
- }
- elseif ( $type === 'longitude' ) {
+ } elseif ( $type === 'longitude' ) {
$ref = 'E';
}
}
**/
class FormatExif {
var $meta;
- function FormatExif ( $meta ) {
+
+ /**
+ * @param $meta array
+ */
+ function FormatExif( $meta ) {
wfDeprecated(__METHOD__);
$this->meta = $meta;
}
- function getFormattedData ( ) {
+ /**
+ * @return array
+ */
+ function getFormattedData() {
return FormatMetadata::getFormattedData( $this->meta );
}
}
foreach ( $entries as $index => $entry ) {
$pdbk = $entry['pdbk'];
// we only deal with new links (in its first query)
- if ( !isset( $colours[$pdbk] ) ) {
+ if ( !isset( $colours[$pdbk] ) || $colours[$pdbk] === 'new' ) {
$title = $entry['title'];
$titleText = $title->getText();
$titlesAttrs[] = array(
}
// Now do the conversion and explode string to text of titles
- $titlesAllVariants = $wgContLang->autoConvertToAllVariants( $titlesToBeConverted );
+ $titlesAllVariants = $wgContLang->autoConvertToAllVariants( rtrim( $titlesToBeConverted, "\0" ) );
$allVariantsName = array_keys( $titlesAllVariants );
foreach ( $titlesAllVariants as &$titlesVariant ) {
$titlesVariant = explode( "\0", $titlesVariant );
$entry =& $this->internals[$ns][$index];
$pdbk = $entry['pdbk'];
- if(!isset($colours[$pdbk])){
+ if ( !isset( $colours[$pdbk] ) || $colours[$pdbk] === 'new' ) {
// found link in some of the variants, replace the link holder data
$entry['title'] = $variantTitle;
$entry['pdbk'] = $varPdbk;
/**
* Return a LIMIT clause to limit results on the query.
*
- * @param string
+ * @param $sql string
*
* @return String
*/
/**
* Callback function to output a restriction
* @param Title $row Protected title
- * @return string Formatted <li> element
+ * @return string Formatted "<li>" element
*/
public function formatRow( $row ) {
wfProfileIn( __METHOD__ );
'Alexandre Emsenhuber', 'Siebrand Mazeland', 'Chad Horohoe',
'Roan Kattouw', 'Trevor Parscal', 'Bryan Tong Minh', 'Sam Reed',
'Victor Vasiliev', 'Rotem Liss', 'Platonides', 'Antoine Musso',
+ 'Timo Tijhof',
wfMsg( 'version-poweredby-others' )
);
'versionrequiredtext' => 'Был бит менән эшләү өсөн MediaWiki-ның $1 версияһы кәрәк. [[Special:Version|Ҡулланылған версия тураһында мәғлүмәт битен]] ҡара.',
'ok' => 'Тамам',
-'pagetitle' => '{{SITENAME}} проектынан',
'retrievedfrom' => 'Сығанағы — «$1»',
'youhavenewmessages' => 'Яңы $1 бар ($2).',
'newmessageslink' => 'яңы хәбәр',
Ẓr [[Special:Version|ayyaw tasna]].',
'ok' => 'Waxxa',
-'pagetitle' => '(MediaWiki)$1 - {{SITENAME}}',
-'pagetitle-view-mainpage' => '{{SITENAME}}',
'retrievedfrom' => 'Yurrid z "$1"',
'youhavenewmessages' => 'Illa dark $1 ($2).',
'newmessageslink' => 'Tibratin timaynutin',
// Only copy the missing files over in the next loop
$srcPathsRel = array_diff( $relFilesSrc, $relFilesDst );
$this->output( count( $srcPathsRel ) . " file(s) need to be copied.\n" );
+ unset( $relFilesSrc );
+ unset( $relFilesDst );
}
$batchPaths = array();
}
$fsFiles[] = $fsFile; // keep TempFSFile objects alive as needed
// Note: prepare() is usually fast for key/value backends
- $status = $dst->prepare( array( 'dir' => dirname( $dstPath ) ) );
+ $status = $dst->prepare( array( 'dir' => dirname( $dstPath ), 'bypassReadOnly' => 1 ) );
if ( !$status->isOK() ) {
$this->error( print_r( $status->getErrorsArray(), true ) );
$this->error( "Could not copy $srcPath to $dstPath.", 1 ); // die
}
$t_start = microtime( true );
- $status = $dst->doQuickOperations( $ops );
+ $status = $dst->doQuickOperations( $ops, array( 'bypassReadOnly' => 1 ) );
if ( !$status->isOK() ) {
sleep( 10 ); // wait and retry copy again
- $status = $dst->doQuickOperations( $ops );
+ $status = $dst->doQuickOperations( $ops, array( 'bypassReadOnly' => 1 ) );
}
$ellapsed_ms = floor( ( microtime( true ) - $t_start ) * 1000 );
if ( !$status->isOK() ) {
function __construct() {
parent::__construct();
$this->addOption( 'list-file', 'A file containing a list of extension setup files, one per line.', true, true );
+ $this->addOption( 'extensions-dir', 'Path where extensions can be found.', false, true );
$this->addOption( 'output', 'Send output to this file (omit for stdout)', false, true );
$this->mDescription = 'Merge $wgExtensionMessagesFiles from various extensions to produce a ' .
'single array containing all message files.';
public function execute() {
global $mmfl;
+ # Add setup files contained in file passed to --list-file
$lines = file( $this->getOption( 'list-file' ) );
if ( $lines === false ) {
$this->error( 'Unable to open list file.' );
}
$mmfl = array( 'setupFiles' => array_map( 'trim', $lines ) );
+
+ # Now find out files in a directory
+ $hasError = false;
+ if ( $this->hasOption( 'extensions-dir' ) ) {
+ $extdir = $this->getOption( 'extensions-dir' );
+ $entries = scandir( $extdir );
+ foreach( $entries as $extname ) {
+ if ( $extname == '.' || $extname == '..' || !is_dir( "$extdir/$extname" ) ) {
+ continue;
+ }
+ $extfile = "{$extdir}/{$extname}/{$extname}.php";
+ if ( file_exists( $extfile ) ) {
+ $mmfl['setupFiles'][] = $extfile;
+ } else {
+ $hasError = true;
+ $this->error( "Extension {$extname} in {$extdir} lacks expected {$extname}.php" );
+ }
+ }
+ }
+
+ if ( $hasError ) {
+ $this->error( "Some files are missing (see above). Giving up.", 1 );
+ }
+
if ( $this->hasOption( 'output' ) ) {
$mmfl['output'] = $this->getOption( 'output' );
}
}
$fsFiles[] = $fsFile; // keep TempFSFile objects alive as needed
// Note: prepare() is usually fast for key/value backends
- $status->merge( $dst->prepare( array( 'dir' => dirname( $dPath ) ) ) );
+ $status->merge( $dst->prepare( array(
+ 'dir' => dirname( $dPath ), 'bypassReadOnly' => 1 ) ) );
if ( !$status->isOK() ) {
return $status;
}
}
$t_start = microtime( true );
- $status->merge( $dst->doOperations( $ops,
- array( 'nonLocking' => 1, 'nonJournaled' => 1 ) ) );
+ $status->merge( $dst->doQuickOperations( $ops, array( 'bypassReadOnly' => 1 ) ) );
$ellapsed_ms = floor( ( microtime( true ) - $t_start ) * 1000 );
if ( $status->isOK() && $this->getOption( 'verbose' ) ) {
$this->output( "Synchronized these file(s) [{$ellapsed_ms}ms]:\n" .
const BATCH_SIZE = 50; // Number of rows to process in one batch
const SYNC_INTERVAL = 20; // Wait for slaves after this many batches
+ var $sizeHistogram = array();
+
public function __construct() {
parent::__construct();
'categorylinks table is large. This will only update rows with that ' .
'collation, though, so it may miss out-of-date rows with a different, ' .
'even older collation.', false, true );
+ $this->addOption( 'target-collation', 'Set this to the new collation type to ' .
+ 'use instead of $wgCategoryCollation. Usually you should not use this, ' .
+ 'you should just update $wgCategoryCollation in LocalSettings.php.',
+ false, true );
+ $this->addOption( 'dry-run', 'Don\'t actually change the collations, just ' .
+ 'compile statistics.' );
+ $this->addOption( 'verbose-stats', 'Show more statistics.' );
}
public function execute() {
$dbw = $this->getDB( DB_MASTER );
$force = $this->getOption( 'force' );
+ $dryRun = $this->getOption( 'dry-run' );
+ $verboseStats = $this->getOption( 'verbose-stats' );
+ if ( $this->hasOption( 'target-collation' ) ) {
+ $collationName = $this->getOption( 'target-collation' );
+ $collation = Collation::factory( $collationName );
+ } else {
+ $collationName = $wgCategoryCollation;
+ $collation = Collation::singleton();
+ }
$options = array( 'LIMIT' => self::BATCH_SIZE, 'STRAIGHT_JOIN' );
- if ( $force ) {
+ if ( $force || $dryRun ) {
$options['ORDER BY'] = 'cl_from, cl_to';
$collationConds = array();
} else {
$collationConds['cl_collation'] = $this->getOption( 'previous-collation' );
} else {
$collationConds = array( 0 =>
- 'cl_collation != ' . $dbw->addQuotes( $wgCategoryCollation )
+ 'cl_collation != ' . $dbw->addQuotes( $collationName )
);
}
} else {
$count = $dbw->estimateRowCount(
'categorylinks',
- '',
+ '*',
$collationConds,
__METHOD__
);
);
$this->output( " processing..." );
- $dbw->begin( __METHOD__ );
+ if ( !$dryRun ) {
+ $dbw->begin( __METHOD__ );
+ }
foreach ( $res as $row ) {
$title = Title::newFromRow( $row );
if ( !$row->cl_collation ) {
} else {
$type = 'page';
}
- $dbw->update(
- 'categorylinks',
- array(
- 'cl_sortkey' => Collation::singleton()->getSortKey(
- $title->getCategorySortkey( $prefix ) ),
- 'cl_sortkey_prefix' => $prefix,
- 'cl_collation' => $wgCategoryCollation,
- 'cl_type' => $type,
- 'cl_timestamp = cl_timestamp',
- ),
- array( 'cl_from' => $row->cl_from, 'cl_to' => $row->cl_to ),
- __METHOD__
- );
+ $newSortKey = $collation->getSortKey(
+ $title->getCategorySortkey( $prefix ) );
+ if ( $verboseStats ) {
+ $this->updateSortKeySizeHistogram( $newSortKey );
+ }
+
+ if ( !$dryRun ) {
+ $dbw->update(
+ 'categorylinks',
+ array(
+ 'cl_sortkey' => $newSortKey,
+ 'cl_sortkey_prefix' => $prefix,
+ 'cl_collation' => $collationName,
+ 'cl_type' => $type,
+ 'cl_timestamp = cl_timestamp',
+ ),
+ array( 'cl_from' => $row->cl_from, 'cl_to' => $row->cl_to ),
+ __METHOD__
+ );
+ }
+ }
+ if ( !$dryRun ) {
+ $dbw->commit( __METHOD__ );
}
- $dbw->commit( __METHOD__ );
- if ( $force && $row ) {
+ if ( ( $force || $dryRun ) && $row ) {
$encFrom = $dbw->addQuotes( $row->cl_from );
$encTo = $dbw->addQuotes( $row->cl_to );
$batchConds = array(
$count += $res->numRows();
$this->output( "$count done.\n" );
- if ( ++$batchCount % self::SYNC_INTERVAL == 0 ) {
+ if ( !$dryRun && ++$batchCount % self::SYNC_INTERVAL == 0 ) {
$this->output( "Waiting for slaves ... " );
wfWaitForSlaves();
$this->output( "done\n" );
}
} while ( $res->numRows() == self::BATCH_SIZE );
+
+ $this->output( "$count rows processed\n" );
+
+ if ( $verboseStats ) {
+ $this->output( "\n" );
+ $this->showSortKeySizeHistogram();
+ }
+ }
+
+ function updateSortKeySizeHistogram( $key ) {
+ $length = strlen( $key );
+ if ( !isset( $this->sizeHistogram[$length] ) ) {
+ $this->sizeHistogram[$length] = 0;
+ }
+ $this->sizeHistogram[$length]++;
+ }
+
+ function showSortKeySizeHistogram() {
+ $maxLength = max( array_keys( $this->sizeHistogram ) );
+ if ( $maxLength == 0 ) {
+ return;
+ }
+ $numBins = 20;
+ $coarseHistogram = array_fill( 0, $numBins, 0 );
+ $coarseBoundaries = array();
+ $boundary = 0;
+ for ( $i = 0; $i < $numBins - 1; $i++ ) {
+ $boundary += $maxLength / $numBins;
+ $coarseBoundaries[$i] = round( $boundary );
+ }
+ $coarseBoundaries[$numBins - 1] = $maxLength + 1;
+ $raw = '';
+ for ( $i = 0; $i <= $maxLength; $i++ ) {
+ if ( $raw !== '' ) {
+ $raw .= ', ';
+ }
+ if ( !isset( $this->sizeHistogram[$i] ) ) {
+ $val = 0;
+ } else {
+ $val = $this->sizeHistogram[$i];
+ }
+ for ( $coarseIndex = 0; $coarseIndex < $numBins - 1; $coarseIndex++ ) {
+ if ( $coarseBoundaries[$coarseIndex] > $i ) {
+ $coarseHistogram[$coarseIndex] += $val;
+ break;
+ }
+ }
+ if ( $coarseIndex == $numBins - 1 ) {
+ $coarseHistogram[$coarseIndex] += $val;
+ }
+ $raw .= $val;
+ }
+
+ $this->output( "Sort key size histogram\nRaw data: $raw\n\n" );
+
+ $maxBinVal = max( $coarseHistogram );
+ $scale = 60 / $maxBinVal;
+ $prevBoundary = 0;
+ for ( $coarseIndex = 0; $coarseIndex < $numBins; $coarseIndex++ ) {
+ if ( !isset( $coarseHistogram[$coarseIndex] ) ) {
+ $val = 0;
+ } else {
+ $val = $coarseHistogram[$coarseIndex];
+ }
+ $boundary = $coarseBoundaries[$coarseIndex];
+ $this->output( sprintf( "%-10s %-10d |%s\n",
+ $prevBoundary . '-' . ( $boundary - 1 ) . ': ',
+ $val,
+ str_repeat( '*', $scale * $val ) ) );
+ $prevBoundary = $boundary;
+ }
}
}
public function setUp() {
global $wgLanguageCode, $wgLang, $wgContLang;
+ parent::setUp();
+
self::$oldLang = $wgLang;
self::$oldContLang = $wgContLang;
$wgContLang = $wgLang = Language::factory( $wgLanguageCode );
MessageCache::singleton()->disable();
+
}
public function tearDown() {
$wgContLang = self::$oldContLang;
$wgLanguageCode = $wgContLang->getCode();
self::$oldContLang = self::$oldLang = null;
+
+ parent::tearDown();
}
}
--- /dev/null
+<?php
+/**
+ * Test for the demo xml
+ *
+ * @group Dump
+ */
+class ExportDemoTest extends DumpTestCase {
+
+ /**
+ * @group large
+ */
+ function testExportDemo() {
+ $this->validateXmlFileAgainstXsd( "../../docs/export-demo.xml" );
+ }
+
+ /**
+ * Validates a xml file against the xsd.
+ *
+ * The validation is slow, because php has to read the xsd on each call.
+ *
+ * @param $fname string: name of file to validate
+ */
+ protected function validateXmlFileAgainstXsd( $fname ) {
+ $version = WikiExporter::schemaVersion();
+
+ $dom = new DomDocument();
+ $dom->load( $fname );
+
+ try {
+ $this->assertTrue( $dom->schemaValidate( "../../docs/export-" . $version . ".xsd" ),
+ "schemaValidate has found an error" );
+ } catch( Exception $e ) {
+ $this->fail( "xml not valid against xsd: " . $e->getMessage() );
+ }
+ }
+}
$props2 = $this->backend->getFileProps( array( 'src' => $dest ) );
$this->assertEquals( $props1, $props2,
"Source and destination have the same props ($backendName)." );
+
+ $this->assertBackendPathsConsistent( array( $dest ) );
}
public function provider_testStore() {
$props2 = $this->backend->getFileProps( array( 'src' => $dest ) );
$this->assertEquals( $props1, $props2,
"Source and destination have the same props ($backendName)." );
+
+ $this->assertBackendPathsConsistent( array( $source, $dest ) );
}
public function provider_testCopy() {
"Source file does not exist accourding to props ($backendName)." );
$this->assertEquals( true, $props2['fileExists'],
"Destination file exists accourding to props ($backendName)." );
+
+ $this->assertBackendPathsConsistent( array( $source, $dest ) );
}
public function provider_testMove() {
$props1 = $this->backend->getFileProps( array( 'src' => $source ) );
$this->assertFalse( $props1['fileExists'],
"Source file $source does not exist according to props ($backendName)." );
+
+ $this->assertBackendPathsConsistent( array( $source ) );
}
public function provider_testDelete() {
$this->backend->getFileSize( array( 'src' => $dest ) ),
"Destination file $dest has original size according to props ($backendName)." );
}
+
+ $this->assertBackendPathsConsistent( array( $dest ) );
}
/**
$this->backend->clean( array( 'dir' => "$base/$container", 'recursive' => 1 ) );
}
+ function assertBackendPathsConsistent( array $paths ) {
+ if ( $this->backend instanceof FileBackendMultiWrite ) {
+ $status = $this->backend->consistencyCheck( $paths );
+ $this->assertGoodStatus( $status, "Files synced: " . implode( ',', $paths ) );
+ }
+ }
+
function assertGoodStatus( $status, $msg ) {
$this->assertEquals( print_r( array(), 1 ), print_r( $status->errors, 1 ), $msg );
}
);
}
- /** @dataProvider provideFormattableTimes */
+ /**
+ * @dataProvider provideFormattableTimes
+ */
function testFormatTimePeriod( $seconds, $format, $expected, $desc ) {
$this->assertEquals( $expected, $this->lang->formatTimePeriod( $seconds, $format ), $desc );
}
function provideFormattableTimes() {
return array(
- array( 9.45, array(), '9.5s', 'formatTimePeriod() rounding (<10s)' ),
- array( 9.45, array( 'noabbrevs' => true ), '9.5 seconds', 'formatTimePeriod() rounding (<10s)' ),
- array( 9.95, array(), '10s', 'formatTimePeriod() rounding (<10s)' ),
- array( 9.95, array( 'noabbrevs' => true ), '10 seconds', 'formatTimePeriod() rounding (<10s)' ),
- array( 59.55, array(), '1m 0s', 'formatTimePeriod() rounding (<60s)' ),
- array( 59.55, array( 'noabbrevs' => true ), '1 minute 0 seconds', 'formatTimePeriod() rounding (<60s)' ),
- array( 119.55, array(), '2m 0s', 'formatTimePeriod() rounding (<1h)' ),
- array( 119.55, array( 'noabbrevs' => true ), '2 minutes 0 seconds', 'formatTimePeriod() rounding (<1h)' ),
- array( 3599.55, array(), '1h 0m 0s', 'formatTimePeriod() rounding (<1h)' ),
- array( 3599.55, array( 'noabbrevs' => true ), '1 hour 0 minutes 0 seconds', 'formatTimePeriod() rounding (<1h)' ),
- array( 7199.55, array(), '2h 0m 0s', 'formatTimePeriod() rounding (>=1h)' ),
- array( 7199.55, array( 'noabbrevs' => true ), '2 hours 0 minutes 0 seconds', 'formatTimePeriod() rounding (>=1h)' ),
- array( 7199.55, 'avoidseconds', '2h 0m', 'formatTimePeriod() rounding (>=1h), avoidseconds' ),
- array( 7199.55, array( 'avoid' => 'avoidseconds', 'noabbrevs' => true ), '2 hours 0 minutes', 'formatTimePeriod() rounding (>=1h), avoidseconds' ),
- array( 7199.55, 'avoidminutes', '2h 0m', 'formatTimePeriod() rounding (>=1h), avoidminutes' ),
- array( 7199.55, array( 'avoid' => 'avoidminutes', 'noabbrevs' => true ), '2 hours 0 minutes', 'formatTimePeriod() rounding (>=1h), avoidminutes' ),
- array( 172799.55, 'avoidseconds', '48h 0m', 'formatTimePeriod() rounding (=48h), avoidseconds' ),
- array( 172799.55, array( 'avoid' => 'avoidseconds', 'noabbrevs' => true ), '48 hours 0 minutes', 'formatTimePeriod() rounding (=48h), avoidseconds' ),
- array( 259199.55, 'avoidminutes', '3d 0h', 'formatTimePeriod() rounding (>48h), avoidminutes' ),
- array( 259199.55, array( 'avoid' => 'avoidminutes', 'noabbrevs' => true ), '3 days 0 hours', 'formatTimePeriod() rounding (>48h), avoidminutes' ),
- array( 176399.55, 'avoidseconds', '2d 1h 0m', 'formatTimePeriod() rounding (>48h), avoidseconds' ),
- array( 176399.55, array( 'avoid' => 'avoidseconds', 'noabbrevs' => true ), '2 days 1 hour 0 minutes', 'formatTimePeriod() rounding (>48h), avoidseconds' ),
- array( 176399.55, 'avoidminutes', '2d 1h', 'formatTimePeriod() rounding (>48h), avoidminutes' ),
- array( 176399.55, array( 'avoid' => 'avoidminutes', 'noabbrevs' => true ), '2 days 1 hour', 'formatTimePeriod() rounding (>48h), avoidminutes' ),
- array( 259199.55, 'avoidseconds', '3d 0h 0m', 'formatTimePeriod() rounding (>48h), avoidseconds' ),
- array( 259199.55, array( 'avoid' => 'avoidseconds', 'noabbrevs' => true ), '3 days 0 hours 0 minutes', 'formatTimePeriod() rounding (>48h), avoidseconds' ),
- array( 172801.55, 'avoidseconds', '2d 0h 0m', 'formatTimePeriod() rounding, (>48h), avoidseconds' ),
- array( 172801.55, array( 'avoid' => 'avoidseconds', 'noabbrevs' => true ), '2 days 0 hours 0 minutes', 'formatTimePeriod() rounding, (>48h), avoidseconds' ),
- array( 176460.55, array(), '2d 1h 1m 1s', 'formatTimePeriod() rounding, recursion, (>48h)' ),
- array( 176460.55, array( 'noabbrevs' => true ), '2 days 1 hour 1 minute 1 second', 'formatTimePeriod() rounding, recursion, (>48h)' ),
+ array(
+ 9.45,
+ array(),
+ '9.5s',
+ 'formatTimePeriod() rounding (<10s)'
+ ),
+ array(
+ 9.45,
+ array( 'noabbrevs' => true ),
+ '9.5 seconds',
+ 'formatTimePeriod() rounding (<10s)'
+ ),
+ array(
+ 9.95,
+ array(),
+ '10s',
+ 'formatTimePeriod() rounding (<10s)'
+ ),
+ array(
+ 9.95,
+ array( 'noabbrevs' => true ),
+ '10 seconds',
+ 'formatTimePeriod() rounding (<10s)'
+ ),
+ array(
+ 59.55,
+ array(),
+ '1m 0s',
+ 'formatTimePeriod() rounding (<60s)'
+ ),
+ array(
+ 59.55,
+ array( 'noabbrevs' => true ),
+ '1 minute 0 seconds',
+ 'formatTimePeriod() rounding (<60s)'
+ ),
+ array(
+ 119.55,
+ array(),
+ '2m 0s',
+ 'formatTimePeriod() rounding (<1h)'
+ ),
+ array(
+ 119.55,
+ array( 'noabbrevs' => true ),
+ '2 minutes 0 seconds',
+ 'formatTimePeriod() rounding (<1h)'
+ ),
+ array(
+ 3599.55,
+ array(),
+ '1h 0m 0s',
+ 'formatTimePeriod() rounding (<1h)'
+ ),
+ array(
+ 3599.55,
+ array( 'noabbrevs' => true ),
+ '1 hour 0 minutes 0 seconds',
+ 'formatTimePeriod() rounding (<1h)'
+ ),
+ array(
+ 7199.55,
+ array(),
+ '2h 0m 0s',
+ 'formatTimePeriod() rounding (>=1h)'
+ ),
+ array(
+ 7199.55,
+ array( 'noabbrevs' => true ),
+ '2 hours 0 minutes 0 seconds',
+ 'formatTimePeriod() rounding (>=1h)'
+ ),
+ array(
+ 7199.55,
+ 'avoidseconds',
+ '2h 0m',
+ 'formatTimePeriod() rounding (>=1h), avoidseconds'
+ ),
+ array(
+ 7199.55,
+ array( 'avoid' => 'avoidseconds', 'noabbrevs' => true ),
+ '2 hours 0 minutes',
+ 'formatTimePeriod() rounding (>=1h), avoidseconds'
+ ),
+ array(
+ 7199.55,
+ 'avoidminutes',
+ '2h 0m',
+ 'formatTimePeriod() rounding (>=1h), avoidminutes'
+ ),
+ array(
+ 7199.55,
+ array( 'avoid' => 'avoidminutes', 'noabbrevs' => true ),
+ '2 hours 0 minutes',
+ 'formatTimePeriod() rounding (>=1h), avoidminutes'
+ ),
+ array(
+ 172799.55,
+ 'avoidseconds',
+ '48h 0m',
+ 'formatTimePeriod() rounding (=48h), avoidseconds'
+ ),
+ array(
+ 172799.55,
+ array( 'avoid' => 'avoidseconds', 'noabbrevs' => true ),
+ '48 hours 0 minutes',
+ 'formatTimePeriod() rounding (=48h), avoidseconds'
+ ),
+ array(
+ 259199.55,
+ 'avoidminutes',
+ '3d 0h',
+ 'formatTimePeriod() rounding (>48h), avoidminutes'
+ ),
+ array(
+ 259199.55,
+ array( 'avoid' => 'avoidminutes', 'noabbrevs' => true ),
+ '3 days 0 hours',
+ 'formatTimePeriod() rounding (>48h), avoidminutes'
+ ),
+ array(
+ 176399.55,
+ 'avoidseconds',
+ '2d 1h 0m',
+ 'formatTimePeriod() rounding (>48h), avoidseconds'
+ ),
+ array(
+ 176399.55,
+ array( 'avoid' => 'avoidseconds', 'noabbrevs' => true ),
+ '2 days 1 hour 0 minutes',
+ 'formatTimePeriod() rounding (>48h), avoidseconds'
+ ),
+ array(
+ 176399.55,
+ 'avoidminutes',
+ '2d 1h',
+ 'formatTimePeriod() rounding (>48h), avoidminutes'
+ ),
+ array(
+ 176399.55,
+ array( 'avoid' => 'avoidminutes', 'noabbrevs' => true ),
+ '2 days 1 hour',
+ 'formatTimePeriod() rounding (>48h), avoidminutes'
+ ),
+ array(
+ 259199.55,
+ 'avoidseconds',
+ '3d 0h 0m',
+ 'formatTimePeriod() rounding (>48h), avoidseconds'
+ ),
+ array(
+ 259199.55,
+ array( 'avoid' => 'avoidseconds', 'noabbrevs' => true ),
+ '3 days 0 hours 0 minutes',
+ 'formatTimePeriod() rounding (>48h), avoidseconds'
+ ),
+ array(
+ 172801.55,
+ 'avoidseconds',
+ '2d 0h 0m',
+ 'formatTimePeriod() rounding, (>48h), avoidseconds'
+ ),
+ array(
+ 172801.55,
+ array( 'avoid' => 'avoidseconds', 'noabbrevs' => true ),
+ '2 days 0 hours 0 minutes',
+ 'formatTimePeriod() rounding, (>48h), avoidseconds'
+ ),
+ array(
+ 176460.55,
+ array(),
+ '2d 1h 1m 1s',
+ 'formatTimePeriod() rounding, recursion, (>48h)'
+ ),
+ array(
+ 176460.55,
+ array( 'noabbrevs' => true ),
+ '2 days 1 hour 1 minute 1 second',
+ 'formatTimePeriod() rounding, recursion, (>48h)'
+ ),
);
}
}
/**
- * @dataProvider provideHTMLTruncateData()
- */
+ * @dataProvider provideHTMLTruncateData()
+ */
function testTruncateHtml( $len, $ellipsis, $input, $expected ) {
// Actual HTML...
$this->assertEquals(
);
}
- /**\r
- * @dataProvider provideCheckTitleEncodingData\r
- */\r
- function testCheckTitleEncoding( $s ) {\r
- $this->assertEquals(\r
- $s,\r
- $this->lang->checkTitleEncoding($s),\r
- "checkTitleEncoding('$s')"\r
- );\r
- }\r
+ /**
+ * @dataProvider provideCheckTitleEncodingData
+ */
+ function testCheckTitleEncoding( $s ) {
+ $this->assertEquals(
+ $s,
+ $this->lang->checkTitleEncoding($s),
+ "checkTitleEncoding('$s')"
+ );
+ }
function provideCheckTitleEncodingData() {
return array (
),
// The following two data sets come from bug 36839. They fail if checkTitleEncoding uses a regexp to test for
// valid UTF-8 encoding and the pcre.recursion_limit is low (like, say, 1024). They succeed if checkTitleEncoding
- // uses mb_check_encoding for its test.
+ // uses mb_check_encoding for its test.
array(
rawurldecode(
"Acteur%7CAlbert%20Robbins%7CAnglais%7CAnn%20Donahue%7CAnthony%20E.%20Zuiker%7CCarol%20Mendelsohn%7C"
- . "Catherine%20Willows%7CDavid%20Hodges%7CDavid%20Phillips%7CGil%20Grissom%7CGreg%20Sanders%7CHodges%7C"
- . "Internet%20Movie%20Database%7CJim%20Brass%7CLady%20Heather%7C"
- . "Les%20Experts%20(s%C3%A9rie%20t%C3%A9l%C3%A9vis%C3%A9e)%7CLes%20Experts%20:%20Manhattan%7C"
- . "Les%20Experts%20:%20Miami%7CListe%20des%20personnages%20des%20Experts%7C"
- . "Liste%20des%20%C3%A9pisodes%20des%20Experts%7CMod%C3%A8le%20discussion:Palette%20Les%20Experts%7C"
- . "Nick%20Stokes%7CPersonnage%20de%20fiction%7CPersonnage%20fictif%7CPersonnage%20de%20fiction%7C"
- . "Personnages%20r%C3%A9currents%20dans%20Les%20Experts%7CRaymond%20Langston%7CRiley%20Adams%7C"
- . "Saison%201%20des%20Experts%7CSaison%2010%20des%20Experts%7CSaison%2011%20des%20Experts%7C"
- . "Saison%2012%20des%20Experts%7CSaison%202%20des%20Experts%7CSaison%203%20des%20Experts%7C"
- . "Saison%204%20des%20Experts%7CSaison%205%20des%20Experts%7CSaison%206%20des%20Experts%7C"
- . "Saison%207%20des%20Experts%7CSaison%208%20des%20Experts%7CSaison%209%20des%20Experts%7C"
- . "Sara%20Sidle%7CSofia%20Curtis%7CS%C3%A9rie%20t%C3%A9l%C3%A9vis%C3%A9e%7CWallace%20Langham%7C"
- . "Warrick%20Brown%7CWendy%20Simms%7C%C3%89tats-Unis"
+ . "Catherine%20Willows%7CDavid%20Hodges%7CDavid%20Phillips%7CGil%20Grissom%7CGreg%20Sanders%7CHodges%7C"
+ . "Internet%20Movie%20Database%7CJim%20Brass%7CLady%20Heather%7C"
+ . "Les%20Experts%20(s%C3%A9rie%20t%C3%A9l%C3%A9vis%C3%A9e)%7CLes%20Experts%20:%20Manhattan%7C"
+ . "Les%20Experts%20:%20Miami%7CListe%20des%20personnages%20des%20Experts%7C"
+ . "Liste%20des%20%C3%A9pisodes%20des%20Experts%7CMod%C3%A8le%20discussion:Palette%20Les%20Experts%7C"
+ . "Nick%20Stokes%7CPersonnage%20de%20fiction%7CPersonnage%20fictif%7CPersonnage%20de%20fiction%7C"
+ . "Personnages%20r%C3%A9currents%20dans%20Les%20Experts%7CRaymond%20Langston%7CRiley%20Adams%7C"
+ . "Saison%201%20des%20Experts%7CSaison%2010%20des%20Experts%7CSaison%2011%20des%20Experts%7C"
+ . "Saison%2012%20des%20Experts%7CSaison%202%20des%20Experts%7CSaison%203%20des%20Experts%7C"
+ . "Saison%204%20des%20Experts%7CSaison%205%20des%20Experts%7CSaison%206%20des%20Experts%7C"
+ . "Saison%207%20des%20Experts%7CSaison%208%20des%20Experts%7CSaison%209%20des%20Experts%7C"
+ . "Sara%20Sidle%7CSofia%20Curtis%7CS%C3%A9rie%20t%C3%A9l%C3%A9vis%C3%A9e%7CWallace%20Langham%7C"
+ . "Warrick%20Brown%7CWendy%20Simms%7C%C3%89tats-Unis"
),
),
array(
rawurldecode(
"Mod%C3%A8le%3AArrondissements%20homonymes%7CMod%C3%A8le%3ABandeau%20standard%20pour%20page%20d'homonymie%7C"
- . "Mod%C3%A8le%3ABatailles%20homonymes%7CMod%C3%A8le%3ACantons%20homonymes%7C"
- . "Mod%C3%A8le%3ACommunes%20fran%C3%A7aises%20homonymes%7CMod%C3%A8le%3AFilms%20homonymes%7C"
- . "Mod%C3%A8le%3AGouvernements%20homonymes%7CMod%C3%A8le%3AGuerres%20homonymes%7CMod%C3%A8le%3AHomonymie%7C"
- . "Mod%C3%A8le%3AHomonymie%20bateau%7CMod%C3%A8le%3AHomonymie%20d'%C3%A9tablissements%20scolaires%20ou"
- . "%20universitaires%7CMod%C3%A8le%3AHomonymie%20d'%C3%AEles%7CMod%C3%A8le%3AHomonymie%20de%20clubs%20sportifs%7C"
- . "Mod%C3%A8le%3AHomonymie%20de%20comt%C3%A9s%7CMod%C3%A8le%3AHomonymie%20de%20monument%7C"
- . "Mod%C3%A8le%3AHomonymie%20de%20nom%20romain%7CMod%C3%A8le%3AHomonymie%20de%20parti%20politique%7C"
- . "Mod%C3%A8le%3AHomonymie%20de%20route%7CMod%C3%A8le%3AHomonymie%20dynastique%7C"
- . "Mod%C3%A8le%3AHomonymie%20vid%C3%A9oludique%7CMod%C3%A8le%3AHomonymie%20%C3%A9difice%20religieux%7C"
- . "Mod%C3%A8le%3AInternationalisation%7CMod%C3%A8le%3AIsom%C3%A9rie%7CMod%C3%A8le%3AParonymie%7C"
- . "Mod%C3%A8le%3APatronyme%7CMod%C3%A8le%3APatronyme%20basque%7CMod%C3%A8le%3APatronyme%20italien%7C"
- . "Mod%C3%A8le%3APatronymie%7CMod%C3%A8le%3APersonnes%20homonymes%7CMod%C3%A8le%3ASaints%20homonymes%7C"
- . "Mod%C3%A8le%3ATitres%20homonymes%7CMod%C3%A8le%3AToponymie%7CMod%C3%A8le%3AUnit%C3%A9s%20homonymes%7C"
- . "Mod%C3%A8le%3AVilles%20homonymes%7CMod%C3%A8le%3A%C3%89difices%20religieux%20homonymes"
+ . "Mod%C3%A8le%3ABatailles%20homonymes%7CMod%C3%A8le%3ACantons%20homonymes%7C"
+ . "Mod%C3%A8le%3ACommunes%20fran%C3%A7aises%20homonymes%7CMod%C3%A8le%3AFilms%20homonymes%7C"
+ . "Mod%C3%A8le%3AGouvernements%20homonymes%7CMod%C3%A8le%3AGuerres%20homonymes%7CMod%C3%A8le%3AHomonymie%7C"
+ . "Mod%C3%A8le%3AHomonymie%20bateau%7CMod%C3%A8le%3AHomonymie%20d'%C3%A9tablissements%20scolaires%20ou"
+ . "%20universitaires%7CMod%C3%A8le%3AHomonymie%20d'%C3%AEles%7CMod%C3%A8le%3AHomonymie%20de%20clubs%20sportifs%7C"
+ . "Mod%C3%A8le%3AHomonymie%20de%20comt%C3%A9s%7CMod%C3%A8le%3AHomonymie%20de%20monument%7C"
+ . "Mod%C3%A8le%3AHomonymie%20de%20nom%20romain%7CMod%C3%A8le%3AHomonymie%20de%20parti%20politique%7C"
+ . "Mod%C3%A8le%3AHomonymie%20de%20route%7CMod%C3%A8le%3AHomonymie%20dynastique%7C"
+ . "Mod%C3%A8le%3AHomonymie%20vid%C3%A9oludique%7CMod%C3%A8le%3AHomonymie%20%C3%A9difice%20religieux%7C"
+ . "Mod%C3%A8le%3AInternationalisation%7CMod%C3%A8le%3AIsom%C3%A9rie%7CMod%C3%A8le%3AParonymie%7C"
+ . "Mod%C3%A8le%3APatronyme%7CMod%C3%A8le%3APatronyme%20basque%7CMod%C3%A8le%3APatronyme%20italien%7C"
+ . "Mod%C3%A8le%3APatronymie%7CMod%C3%A8le%3APersonnes%20homonymes%7CMod%C3%A8le%3ASaints%20homonymes%7C"
+ . "Mod%C3%A8le%3ATitres%20homonymes%7CMod%C3%A8le%3AToponymie%7CMod%C3%A8le%3AUnit%C3%A9s%20homonymes%7C"
+ . "Mod%C3%A8le%3AVilles%20homonymes%7CMod%C3%A8le%3A%C3%89difices%20religieux%20homonymes"
)
)
);