* (bug 35685) api.php URL and other entry point URLs are now listed on
Special:Version
* Edit notices can now be translated.
+* (bug 22887) Add warning and tracking category for preprocessor errors
=== Bug fixes in 1.20 ===
* (bug 30245) Use the correct way to construct a log page title.
* @param $rows ResultWrapper object with rows in recentchanges table
* @param $lastmod Integer: timestamp of the last item in the recentchanges table (only used for the cache key)
* @param $opts FormOptions as in SpecialRecentChanges::getDefaultOptions()
- * @return null or true
+ * @return null|bool True or null
*/
public function execute( $feed, $rows, $lastmod, $opts ) {
global $wgLang, $wgRenderHashAppend;
if ( !FeedUtils::checkFeedOutput( $this->format ) ) {
- return;
+ return null;
}
$optionsHash = md5( serialize( $opts->getAllValues() ) ) . $wgRenderHashAppend;
global $wgExceptionHooks;
if ( !isset( $wgExceptionHooks ) || !is_array( $wgExceptionHooks ) ) {
- return; // Just silently ignore
+ return null; // Just silently ignore
}
if ( !array_key_exists( $name, $wgExceptionHooks ) || !is_array( $wgExceptionHooks[ $name ] ) ) {
- return;
+ return null;
}
$hooks = $wgExceptionHooks[ $name ];
return $result;
}
}
+ return null;
}
/**
$diffOnly = $wgRequest->getBool( 'diffonly', $wgUser->getOption( 'diffonly' ) );
if ( $this->getTitle()->getNamespace() != NS_FILE || ( isset( $diff ) && $diffOnly ) ) {
- return parent::view();
+ parent::view();
+ return;
}
$this->loadFile();
// mTitle is the same as the redirect target so ask Article
// to perform the redirect for us.
$wgRequest->setVal( 'diffonly', 'true' );
- return parent::view();
+ parent::view();
+ return;
} else {
// mTitle is not the same as the redirect target so it is
// probably the redirect page itself. Fake the redirect symbol
$defaultModules['user'] = 'ready';
}
} else {
- // User JS diabled
+ // User JS disabled
$defaultModules['user'] = 'missing';
}
);
}
+ /**
+ * @param $params array
+ * @return Title
+ */
+ public function getTitleOrPageId( $params ) {
+ $this->requireOnlyOneParameter( $params, 'title', 'pageid' );
+
+ $titleObj = null;
+ if ( isset( $params['title'] ) ) {
+ $titleObj = Title::newFromText( $params['title'] );
+ if ( !$titleObj ) {
+ $this->dieUsageMsg( array( 'invalidtitle', $params['title'] ) );
+ }
+ } elseif ( isset( $params['pageid'] ) ) {
+ $titleObj = Title::newFromID( $params['pageid'] );
+ if ( !$titleObj ) {
+ $this->dieUsageMsg( array( 'nosuchpageid', $params['pageid'] ) );
+ }
+ }
+ return $titleObj;
+ }
+
+ /**
+ * @return array
+ */
+ public function getTitleOrPageIdErrorMessage( ) {
+ return $this->getRequireOnlyOneParameterErrorMessages( array( 'title', 'pageid' ) );
+ }
+
/**
* Callback function used in requireOnlyOneParameter to check whether reequired parameters are set
*
public function execute() {
$params = $this->extractRequestParams();
- $this->requireOnlyOneParameter( $params, 'title', 'pageid' );
-
- if ( isset( $params['title'] ) ) {
- $titleObj = Title::newFromText( $params['title'] );
- if ( !$titleObj ) {
- $this->dieUsageMsg( array( 'invalidtitle', $params['title'] ) );
- }
- $pageObj = WikiPage::factory( $titleObj );
- $pageObj->loadPageData( 'fromdbmaster' );
- if ( !$pageObj->exists() ) {
- $this->dieUsageMsg( 'notanarticle' );
- }
- } elseif ( isset( $params['pageid'] ) ) {
- $pageObj = WikiPage::newFromID( $params['pageid'] );
- if ( !$pageObj ) {
- $this->dieUsageMsg( array( 'nosuchpageid', $params['pageid'] ) );
- }
- $titleObj = $pageObj->getTitle();
+ $titleObj = $this->getTitleOrPageId( $params );
+ $pageObj = WikiPage::factory( $titleObj );
+ $pageObj->loadPageData( 'fromdbmaster' );
+ if ( !$pageObj->exists() ) {
+ $this->dieUsageMsg( 'notanarticle' );
}
$reason = ( isset( $params['reason'] ) ? $params['reason'] : null );
public function getPossibleErrors() {
return array_merge( parent::getPossibleErrors(),
- $this->getRequireOnlyOneParameterErrorMessages( array( 'title', 'pageid' ) ),
+ $this->getTitleOrPageIdErrorMessage(),
array(
array( 'invalidtitle', 'title' ),
array( 'nosuchpageid', 'pageid' ),
$this->dieUsageMsg( 'missingtext' );
}
- $this->requireOnlyOneParameter( $params, 'title', 'pageid' );
-
- if ( isset( $params['title'] ) ) {
- $titleObj = Title::newFromText( $params['title'] );
- if ( !$titleObj || $titleObj->isExternal() ) {
- $this->dieUsageMsg( array( 'invalidtitle', $params['title'] ) );
- }
- } elseif ( isset( $params['pageid'] ) ) {
- $titleObj = Title::newFromID( $params['pageid'] );
- if ( !$titleObj ) {
- $this->dieUsageMsg( array( 'nosuchpageid', $params['pageid'] ) );
- }
+ $titleObj = $this->getTitleOrPageId( $params );
+ if ( $titleObj->isExternal() ) {
+ $this->dieUsageMsg( array( 'invalidtitle', $params['title'] ) );
}
$apiResult = $this->getResult();
global $wgMaxArticleSize;
return array_merge( parent::getPossibleErrors(),
- $this->getRequireOnlyOneParameterErrorMessages( array( 'title', 'pageid' ) ),
+ $this->getTitleOrPageIdErrorMessage(),
array(
array( 'nosuchpageid', 'pageid' ),
array( 'missingtext' ),
global $wgRestrictionLevels;
$params = $this->extractRequestParams();
- $this->requireOnlyOneParameter( $params, 'title', 'pageid' );
-
- if ( isset( $params['title'] ) ) {
- $titleObj = Title::newFromText( $params['title'] );
- if ( !$titleObj ) {
- $this->dieUsageMsg( array( 'invalidtitle', $params['title'] ) );
- }
- $pageObj = WikiPage::factory( $titleObj );
- $pageObj->loadPageData( 'fromdbmaster' );
- } elseif ( isset( $params['pageid'] ) ) {
- $pageObj = WikiPage::newFromID( $params['pageid'] );
- if ( !$pageObj ) {
- $this->dieUsageMsg( array( 'nosuchpageid', $params['pageid'] ) );
- }
- $titleObj = $pageObj->getTitle();
- }
+ $titleObj = $this->getTitleOrPageId( $params );
+ $pageObj = WikiPage::factory( $titleObj );
$errors = $titleObj->getUserPermissionsErrors( 'protect', $this->getUser() );
if ( $errors ) {
public function getPossibleErrors() {
return array_merge( parent::getPossibleErrors(),
- $this->getRequireOnlyOneParameterErrorMessages( array( 'title', 'pageid' ) ),
+ $this->getTitleOrPageIdErrorMessage(),
array(
array( 'invalidtitle', 'title' ),
array( 'nosuchpageid', 'pageid' ),
private function run( $resultPageSet = null ) {
$params = $this->extractRequestParams();
- $this->requireOnlyOneParameter( $params, 'title', 'pageid' );
-
- if ( isset( $params['title'] ) ) {
- $categoryTitle = Title::newFromText( $params['title'] );
-
- if ( is_null( $categoryTitle ) || $categoryTitle->getNamespace() != NS_CATEGORY ) {
- $this->dieUsage( 'The category name you entered is not valid', 'invalidcategory' );
- }
- } elseif( isset( $params['pageid'] ) ) {
- $categoryTitle = Title::newFromID( $params['pageid'] );
-
- if ( !$categoryTitle ) {
- $this->dieUsageMsg( array( 'nosuchpageid', $params['pageid'] ) );
- } elseif ( $categoryTitle->getNamespace() != NS_CATEGORY ) {
- $this->dieUsage( 'The category name you entered is not valid', 'invalidcategory' );
- }
+ $categoryTitle = $this->getTitleOrPageId( $params );
+ if ( $categoryTitle->getNamespace() != NS_CATEGORY ) {
+ $this->dieUsage( 'The category name you entered is not valid', 'invalidcategory' );
}
$prop = array_flip( $params['prop'] );
public function getPossibleErrors() {
return array_merge( parent::getPossibleErrors(),
- $this->getRequireOnlyOneParameterErrorMessages( array( 'title', 'pageid' ) ),
+ $this->getTitleOrPageIdErrorMessage(),
array(
array( 'code' => 'invalidcategory', 'info' => 'The category name you entered is not valid' ),
array( 'code' => 'badcontinue', 'info' => 'Invalid continue param. You should pass the original value returned by the previous query' ),
*/
private function getChunkResult(){
$result = array();
-
+
$result['result'] = 'Continue';
$request = $this->getMain()->getRequest();
$chunkPath = $request->getFileTempname( 'chunk' );
$this->mParams['offset']);
if ( !$status->isGood() ) {
$this->dieUsage( $status->getWikiText(), 'stashfailed' );
- return ;
+ return array();
}
// Check we added the last chunk:
if ( !$status->isGood() ) {
$this->dieUsage( $status->getWikiText(), 'stashfailed' );
- return ;
+ return array();
}
// We have a new filekey for the fully concatenated file.
} else {
$status = $this->doCreateInternal( $params );
$this->clearCache( array( $params['dst'] ) );
+ $this->deleteFileCache( $params['dst'] ); // persistent cache
}
wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
} else {
$status = $this->doStoreInternal( $params );
$this->clearCache( array( $params['dst'] ) );
+ $this->deleteFileCache( $params['dst'] ); // persistent cache
}
wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
wfProfileIn( __METHOD__ . '-' . $this->name );
$status = $this->doCopyInternal( $params );
$this->clearCache( array( $params['dst'] ) );
+ $this->deleteFileCache( $params['dst'] ); // persistent cache
wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
return $status;
wfProfileIn( __METHOD__ . '-' . $this->name );
$status = $this->doDeleteInternal( $params );
$this->clearCache( array( $params['src'] ) );
+ $this->deleteFileCache( $params['src'] ); // persistent cache
wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
return $status;
wfProfileIn( __METHOD__ . '-' . $this->name );
$status = $this->doMoveInternal( $params );
$this->clearCache( array( $params['src'], $params['dst'] ) );
+ $this->deleteFileCache( $params['src'] ); // persistent cache
+ $this->deleteFileCache( $params['dst'] ); // persistent cache
wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
return $status;
wfProfileOut( __METHOD__ );
return false; // invalid storage path
}
- $latest = !empty( $params['latest'] );
+ $latest = !empty( $params['latest'] ); // use latest data?
+ if ( !isset( $this->cache[$path]['stat'] ) ) {
+ $this->primeFileCache( array( $path ) ); // check persistent cache
+ }
if ( isset( $this->cache[$path]['stat'] ) ) {
// If we want the latest data, check that this cached
// value was in fact fetched with the latest available data.
wfProfileOut( __METHOD__ . '-miss-' . $this->name );
wfProfileOut( __METHOD__ . '-miss' );
if ( is_array( $stat ) ) { // don't cache negatives
+ $stat['latest'] = $latest;
$this->trimCache(); // limit memory
$this->cache[$path]['stat'] = $stat;
- $this->cache[$path]['stat']['latest'] = $latest;
+ $this->setFileCache( $path, $stat ); // update persistent cache
}
wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
// Clear any file cache entries (after locks acquired)
$this->clearCache();
- // Load from the persistent container cache
+ // Load from the persistent file and container caches
+ $this->primeFileCache( $performOps );
$this->primeContainerCache( $performOps );
// Actually attempt the operation batch...
* @return void
*/
final protected function setContainerCache( $container, $val ) {
- $this->memCache->set( $this->containerCacheKey( $container ), $val, 7*86400 );
+ $this->memCache->set( $this->containerCacheKey( $container ), $val, 14*86400 );
}
/**
* @return void
*/
final protected function deleteContainerCache( $container ) {
- $this->memCache->delete( $this->containerCacheKey( $container ) );
+ for ( $attempts=1; $attempts <= 3; $attempts++ ) {
+ if ( $this->memCache->delete( $this->containerCacheKey( $container ) ) ) {
+ return; // done!
+ }
+ }
+ trigger_error( "Unable to delete stat cache for container $container." );
}
/**
* Do a batch lookup from cache for container stats for all containers
* used in a list of container names, storage paths, or FileOp objects.
*
- * @param $items Array List of storage paths or FileOps
+ * @param $items Array
* @return void
*/
final protected function primeContainerCache( array $items ) {
+ wfProfileIn( __METHOD__ );
+ wfProfileIn( __METHOD__ . '-' . $this->name );
$paths = array(); // list of storage paths
$contNames = array(); // (cache key => resolved container name)
// Get all the paths/containers from the items...
// Populate the container process cache for the backend...
$this->doPrimeContainerCache( array_filter( $contInfo, 'is_array' ) );
+ wfProfileOut( __METHOD__ . '-' . $this->name );
+ wfProfileOut( __METHOD__ );
}
/**
* @return void
*/
protected function doPrimeContainerCache( array $containerInfo ) {}
+
+ /**
+ * Get the cache key for a file path
+ *
+ * @param $path Storage path
+ * @return string
+ */
+ private function fileCacheKey( $path ) {
+ return wfMemcKey( 'backend', $this->getName(), 'file', sha1( $path ) );
+ }
+
+ /**
+ * Set the cached stat info for a file path
+ *
+ * @param $path Storage path
+ * @param $val mixed Information to cache
+ * @return void
+ */
+ final protected function setFileCache( $path, $val ) {
+ $this->memCache->set( $this->fileCacheKey( $path ), $val, 7*86400 );
+ }
+
+ /**
+ * Delete the cached stat info for a file path
+ *
+ * @param $path Storage path
+ * @return void
+ */
+ final protected function deleteFileCache( $path ) {
+ for ( $attempts=1; $attempts <= 3; $attempts++ ) {
+ if ( $this->memCache->delete( $this->fileCacheKey( $path ) ) ) {
+ return; // done!
+ }
+ }
+ trigger_error( "Unable to delete stat cache for file $path." );
+ }
+
+ /**
+ * Do a batch lookup from cache for file stats for all paths
+ * used in a list of storage paths or FileOp objects.
+ *
+ * @param $items Array List of storage paths or FileOps
+ * @return void
+ */
+ final protected function primeFileCache( array $items ) {
+ wfProfileIn( __METHOD__ );
+ wfProfileIn( __METHOD__ . '-' . $this->name );
+ $paths = array(); // list of storage paths
+ $pathNames = array(); // (cache key => storage path)
+ // Get all the paths/containers from the items...
+ foreach ( $items as $item ) {
+ if ( $item instanceof FileOp ) {
+ $paths = array_merge( $paths, $item->storagePathsRead() );
+ $paths = array_merge( $paths, $item->storagePathsChanged() );
+ } elseif ( self::isStoragePath( $item ) ) {
+ $paths[] = $item;
+ }
+ }
+ // Get all the corresponding cache keys for paths...
+ foreach ( $paths as $path ) {
+ list( $cont, $rel, $s ) = $this->resolveStoragePath( $path );
+ if ( $rel !== null ) { // valid path for this backend
+ $pathNames[$this->fileCacheKey( $path )] = $path;
+ }
+ }
+ // Get all cache entries for these container cache keys...
+ $values = $this->memCache->getBatch( array_keys( $pathNames ) );
+ foreach ( $values as $cacheKey => $val ) {
+ if ( is_array( $val ) ) {
+ $this->trimCache(); // limit memory
+ $this->cache[$pathNames[$cacheKey]]['stat'] = $val;
+ }
+ }
+ wfProfileOut( __METHOD__ . '-' . $this->name );
+ wfProfileOut( __METHOD__ );
+ }
}
/**
array( 'ORDER BY' => 'fa_timestamp DESC' ) );
if ( $res == false || $dbr->numRows( $res ) == 0 ) {
// this revision does not exist?
- return;
+ return null;
}
$ret = $dbr->resultObject( $res );
$row = $ret->fetchObject();
* @since 1.20
*
* @param $tableName string
+ * @return bool
*/
public function tableExists( $tableName ) {
return ( $this->db->tableExists( $tableName, __METHOD__ ) );
/**
* Get an item with the given key. Returns false if it does not exist.
* @param $key string
- *
- * @return bool|Object
+ * @return mixed Returns false on failure
*/
abstract public function get( $key );
* Get an associative array containing the item for each of the given keys.
* Each item will be false if it does not exist.
* @param $keys Array List of strings
- *
* @return Array
*/
public function getBatch( array $keys ) {
* @param $key string
* @param $value mixed
* @param $exptime int Either an interval in seconds or a unix timestamp for expiry
+ * @return bool success
*/
abstract public function set( $key, $value, $exptime = 0 );
* Delete an item.
* @param $key string
* @param $time int Amount of time to delay the operation (mostly memcached-specific)
+ * @return bool success
*/
abstract public function delete( $key, $time = 0 );
+ /**
+ * @param $key string
+ * @param $timeout integer
+ * @return bool success
+ */
public function lock( $key, $timeout = 0 ) {
/* stub */
return true;
}
+ /**
+ * @param $key string
+ * @return bool success
+ */
public function unlock( $key ) {
/* stub */
return true;
}
+ /**
+ * @todo: what is this?
+ * @return Array
+ */
public function keys() {
/* stub */
return array();
/* *** Emulated functions *** */
+ /**
+ * @param $key string
+ * @param $value mixed
+ * @param $exptime integer
+ * @return bool success
+ */
public function add( $key, $value, $exptime = 0 ) {
if ( !$this->get( $key ) ) {
- $this->set( $key, $value, $exptime );
-
- return true;
+ return $this->set( $key, $value, $exptime );
}
+ return true;
}
+ /**
+ * @param $key string
+ * @param $value mixed
+ * @return bool success
+ */
public function replace( $key, $value, $exptime = 0 ) {
if ( $this->get( $key ) !== false ) {
- $this->set( $key, $value, $exptime );
+ return $this->set( $key, $value, $exptime );
}
+ return true;
}
/**
* @param $key String: Key to increase
* @param $value Integer: Value to add to $key (Default 1)
* @return null if lock is not possible else $key value increased by $value
+ * @return success
*/
public function incr( $key, $value = 1 ) {
if ( !$this->lock( $key ) ) {
return $n;
}
+ /**
+ * @param $key String
+ * @param $value Integer
+ * @return bool success
+ */
public function decr( $key, $value = 1 ) {
return $this->incr( $key, - $value );
}
+ /**
+ * @param $text string
+ */
public function debug( $text ) {
if ( $this->debugMode ) {
$class = get_class( $this );
/**
* Convert an optionally relative time to an absolute time
+ * @param $exptime integer
* @return int
*/
protected function convertExpiry( $exptime ) {
}
}
}
-
-
}
if ( ++$this->parser->mPPNodeCount > $this->parser->mOptions->getMaxPPNodeCount() ) {
+ $this->parser->limitationWarn( 'node-count-exceeded',
+ $this->parser->mPPNodeCount,
+ $this->parser->mOptions->getMaxPPNodeCount()
+ );
return '<span class="error">Node-count limit exceeded</span>';
}
if ( $expansionDepth > $this->parser->mOptions->getMaxPPExpandDepth() ) {
+ $this->parser->limitationWarn( 'expansion-depth-exceeded',
+ $expansionDepth,
+ $this->parser->mOptions->getMaxPPExpandDepth()
+ );
return '<span class="error">Expansion depth limit exceeded</span>';
}
wfProfileIn( __METHOD__ );
}
if ( ++$this->parser->mPPNodeCount > $this->parser->mOptions->getMaxPPNodeCount() ) {
+ $this->parser->limitationWarn( 'node-count-exceeded',
+ $this->parser->mPPNodeCount,
+ $this->parser->mOptions->getMaxPPNodeCount()
+ );
return '<span class="error">Node-count limit exceeded</span>';
}
if ( $expansionDepth > $this->parser->mOptions->getMaxPPExpandDepth() ) {
+ $this->parser->limitationWarn( 'expansion-depth-exceeded',
+ $expansionDepth,
+ $this->parser->mOptions->getMaxPPExpandDepth()
+ );
return '<span class="error">Expansion depth limit exceeded</span>';
}
++$expansionDepth;
}
if ( ++$this->parser->mPPNodeCount > $this->parser->mOptions->getMaxPPNodeCount() ) {
+ $this->parser->limitationWarn( 'node-count-exceeded',
+ $this->parser->mPPNodeCount,
+ $this->parser->mOptions->getMaxPPNodeCount()
+ );
return '<span class="error">Node-count limit exceeded</span>';
}
if ( $expansionDepth > $this->parser->mOptions->getMaxPPExpandDepth() ) {
+ $this->parser->limitationWarn( 'expansion-depth-exceeded',
+ $expansionDepth,
+ $this->parser->mOptions->getMaxPPExpandDepth()
+ );
return '<span class="error">Expansion depth limit exceeded</span>';
}
++$expansionDepth;
$this->outputHeader();
if( $this->mTargetID && $this->mDestID && $this->mAction == 'submit' && $this->mMerge ) {
- return $this->merge();
+ $this->merge();
+ return;
}
if ( !$this->mSubmitted ) {
$t = Title::newFromText( $term );
# If the string cannot be used to create a title
if( is_null( $t ) ) {
- return $this->showResults( $term );
+ $this->showResults( $term );
+ return;
}
# If there's an exact or very near match, jump right there.
$t = SearchEngine::getNearMatch( $term );
return;
}
}
- return $this->showResults( $term );
+ $this->showResults( $term );
}
/**
কোনো আসোঁৱাহপূৰ্ণ ৱেব-ভিত্তিক বেনামী প্ৰক্সী সেৱা ব্যৱহাৰ কৰিলে এনে হ’ব পাৰে ।",
'edit_form_incomplete' => "'''এই সম্পাদনাৰ কিছু অংশ চাৰ্ভাৰলৈ নগ’ল; আপোনাৰ সম্পাদনা ঠিকে আছেনে পৰীক্ষা কৰি পুনৰ চেষ্টা কৰক ।'''",
'editing' => '$1 সম্পাদনা',
-'creating' => 'সৃষ্টি কৰি থকা হৈছে $1',
+'creating' => '$1 পৃষ্ঠাখন আপুনি সৃষ্টি কৰি আছে',
'editingsection' => '$1 (বিভাগ) সম্পাদনা কৰি থকা হৈছে',
'editingcomment' => '$1 (নতুন বিভাগ) সম্পাদনা কৰি থকা হৈছে',
'editconflict' => 'সম্পাদনা দ্বন্দ: $1',
'edit-no-change' => 'আপোনাৰ সম্পাদনা আওকাণ কৰা হৈছে, কাৰণ লেখাত কোনো তফাৎ নাই',
'edit-already-exists' => "নতুন পৃষ্ঠা সৃষ্টি কৰা নহ'ল ।
পৃষ্ঠাখন ইতিমধ্যে আছেই ।",
+'defaultmessagetext' => 'সাধাৰণ বাৰ্তা পাঠ্য',
# Parser/template warnings
'expensive-parserfunction-warning' => "'''সতৰ্কবাণী:''' এই পৃষ্ঠাখনত অধিক এক্সপেনচিভ পাৰ্চাৰ ফাংচন কল আছে ।
'revdelete-legend' => 'দৃষ্টিপাত সীমাবদ্ধ কৰক',
'revdelete-hide-text' => 'সংশোধিত পাঠ গোপন কৰক',
'revdelete-hide-image' => 'ফাইলৰ বিষয়বস্তু গোপন কৰক',
-'revdelete-hide-name' => 'কাৰ্য্য আৰু লক্ষ্য গোপন কৰক',
+'revdelete-hide-name' => 'কাৰ্য আৰু লক্ষ্য গোপন কৰক',
'revdelete-hide-comment' => 'সম্পাদনা মন্তব্য আতৰাই থওক',
'revdelete-hide-user' => 'সম্পাদকৰ সদস্যনাম/আই-পি ঠিকনা গোপন কৰক',
'revdelete-hide-restricted' => 'প্ৰশাসকবৃন্দৰ লগতে আনৰ পৰাও তথ্য ৰোধ কৰক',
'revdel-restore-deleted' => 'বাতিল কৰা সংশোধনসমূহ',
'revdel-restore-visible' => 'দৃশ্যমান সংশোধনসমূহ',
'pagehist' => 'পৃষ্ঠা ইতিহাস',
-'deletedhist' => 'মà¦\9aি পà§\87লà§\8bৱা ইতিহাস',
+'deletedhist' => 'বিলà§\8bপ à¦\95ৰাৰ ইতিহাস',
'revdelete-hide-current' => ' $2, $1 তাৰিখৰ এই আইটেমটো গোপন কৰাত সমস্যা হৈছে: এইটো বৰ্তমানৰ সংশোধনী ।
এইটোক গোপন কৰিব পৰা নাযাব ।',
'revdelete-show-no-access' => '$2, $1 তাৰিখৰ এই আইটেমটো দেখুওৱাত সমস্যা হৈছে: এই আইটেমটো "সীমাবদ্ধ" হিছাপে চিহ্নিত ।
'showhideselectedversions' => 'নিৰ্বাচিত সংশোধনসমূহ দেখুৱাওক/আঁৰ কৰক',
'editundo' => 'পূৰ্ববত কৰক',
'diff-multi' => '({{PLURAL:$2|এজন সদস্যৰ|$2জন সদস্যৰ}} দ্বাৰা {{PLURAL:$1|এটা মধ্যৱৰ্তী সংশোধন|$1-টা মধ্যৱৰ্তী সংশোধন}} দেখোৱা হোৱা নাই)',
-'diff-multi-manyusers' => '({{PLURAL:$2|à¦\8fà¦\9cনতà¦\95à§\88|$2-à¦\9cনতà¦\95à§\88}} à¦\85ধিà¦\95 সদসà§\8dযৰ দà§\8dবাৰা {{PLURAL:$1|à¦\8fà¦\9fা মধà§\8dযৱৰà§\8dতà§\80 সà¦\82শà§\8bধন|$1-à¦\9fা মধà§\8dযৱৰà§\8dতà§\80 সà¦\82শà§\8bধন}} দà§\87à¦\96à§\8bৱা হোৱা নাই)',
+'diff-multi-manyusers' => '({{PLURAL:$2|à¦\8fà¦\9cনতà¦\95à§\88|$2-à¦\9cনতà¦\95à§\88}} à¦\85ধিà¦\95 সদসà§\8dযৰ দà§\8dবাৰা {{PLURAL:$1|à¦\8fà¦\9fা মধà§\8dযৱৰà§\8dতà§\80 সà¦\82শà§\8bধন|$1-à¦\9fা মধà§\8dযৱৰà§\8dতà§\80 সà¦\82শà§\8bধন}} দà§\87à¦\96à§\81à¦\93ৱা হোৱা নাই)',
# Search results
'searchresults' => 'অনুসন্ধানৰ ফলাফল',
'filename-bad-prefix' => "আপুনি আপলোড কৰা ফাইলৰ নামটো '''\"\$1\"''' দি আৰম্ভ হৈছে, যিটো ডিজিটেল কেমেৰাই স্বয়ংক্ৰিয়ভাৱে দিয়ে আৰু সি ব্যাখ্যামূলক নহয় ।
অনুগ্ৰহ কৰি আপোনাৰ ফাইলটোৰ বাবে এটা ব্যাখ্যামূলক নাম বাছি লওক ।",
'upload-success-subj' => "আপলোড সফল হ'ল",
-'upload-success-msg' => '[$2] ৰ পৰা à¦\86পà§\8bনাৰ à¦\86পলà§\8bড সফল হà§\88à¦\9bà§\87 । à¦\8fà¦\87à¦\9fà§\8b à¦\87য়াত à¦\89পলবà§\8dদà§\8dধ: [[:{{ns:file}}:$1]]',
+'upload-success-msg' => '[$2] ৰ পৰা আপোনাৰ আপলোড সফল হৈছে । এইটো ইয়াত উপলদ্ধ: [[:{{ns:file}}:$1]]',
'upload-failure-subj' => 'আপল’ডত সমস্যা হৈছে',
'upload-failure-msg' => '[$2] ৰ পৰা আপুনি কৰা আপল’ডত এটা সমস্যাই দেখা দিছে:
'allpages-bad-ns' => '{{SITENAME}} ত কোনো "$1" নামস্থান নাই ।',
'allpages-hide-redirects' => 'পুনঃনিৰ্দেশ লুকুৱাওক',
+# SpecialCachedPage
+'cachedspecial-refresh-now' => 'শেহতীয়া পাঠ্য',
+
# Special:Categories
'categories' => 'শ্ৰেণী',
'categoriespagetext' => 'এই {{PLURAL:$1|বিষয়শ্ৰেণীত|বিষয়শ্ৰেণীসমূহত}} পৃষ্ঠা বা মিডিয়া ফাইল আছে ।
'api-error-uploaddisabled' => "এই ৱিকিত আপল'ড নিষ্ক্ৰিয় কৰা হৈছে।",
'api-error-verification-error' => 'সম্ভৱতঃ এই ফাইলটো ত্ৰুটিপূৰ্ণ বা তাৰ এক্সটেন্ছনটো ভুল।',
+# Durations
+'duration-seconds' => '$1 {{PLURAL:$1|ছেকেণ্ড|ছেকেণ্ড}}',
+'duration-minutes' => '$1 {{PLURAL:$1|মিনিট|মিনিট}}',
+'duration-hours' => '$1 {{PLURAL:$1|ঘন্টা|ঘন্টা}}',
+'duration-days' => '$1 {{PLURAL:$1|দিন|দিন}}',
+'duration-weeks' => '{{PLURAL: $1|সপ্তাহ|সপ্তাহ}}',
+'duration-years' => '$1 {{PLURAL:$1|বছৰ|বছৰ}}',
+'duration-decades' => '$1 {{PLURAL:$1|দশক|দশক}}',
+'duration-centuries' => '$1 {{PLURAL:$1|শতাব্দী|শতাব্দী}}',
+'duration-millennia' => '$1 {{PLURAL:$1|সহস্ৰাব্দ|সহস্ৰাব্দ}}',
+
);
'note' => "'''Nota:'''",
'previewnote' => "'''Alcuerdate de qu'esto ye sólo una vista previa.'''
¡Los cambios entá nun se guardaron!",
+'continue-editing' => 'Siguir editando',
'previewconflict' => "Esta vista previa amuesa'l testu del área d'edición d'arriba tal como apaecerá si escueyes guardar.",
'session_fail_preview' => "'''¡Sentímoslo muncho! Nun se pudo procesar la to edición porque hebo una perda de datos de la sesión.
Inténtalo otra vuelta. Si nun se t'arregla, intenta salir y volver a rexistrate.'''",
'datedefault' => 'Standard',
'prefs-beta' => 'Beta-Funktionen',
'prefs-datetime' => 'Datum und Zeit',
-'prefs-labs' => 'Alpha-Funktionen (experimentell)',
+'prefs-labs' => 'Alpha-Funktionen',
'prefs-personal' => 'Benutzerdaten',
'prefs-rc' => 'Letzte Änderungen',
'prefs-watchlist' => 'Beobachtungsliste',
'parser-template-loop-warning' => 'Template loop detected: [[$1]]',
'parser-template-recursion-depth-warning' => 'Template recursion depth limit exceeded ($1)',
'language-converter-depth-warning' => 'Language converter depth limit exceeded ($1)',
+'node-count-exceeded-category' => 'Pages where node-count is exceeded',
+'node-count-exceeded-warning' => 'Page exceeded the node-count',
+'expansion-depth-exceeded-category' => 'Pages where expansion depth is exceeded',
+'expansion-depth-exceeded-warning' => 'Page exceeded the expansion depth',
# "Undo" feature
'undo-success' => 'The edit can be undone.
El registro de borrados y traslados para la página están provistos debajo como referencia.',
'log-fulllog' => 'Ver el registro completo',
'edit-hook-aborted' => 'Edición cancelada por la extensión.
-No dió explicaciones.',
+No se aportaron explicaciones.',
'edit-gone-missing' => 'No se pudo actualizar la página.
Parece que ha sido borrada.',
'edit-conflict' => 'Conflicto de edición.',
To se ponekad događa kad rabite neispravan web-baziran anonimni posrednik (proxy).",
'edit_form_incomplete' => "'''Neki dijelovi obrasca za uređivanja nisu dostigli do poslužitelja; provjerite jesu li izmjene netaknute i pokušajte ponovno.'''",
'editing' => 'Uređujete $1',
+'creating' => 'Stvori $1',
'editingsection' => 'Uređujete $1 (odlomak)',
'editingcomment' => 'Uređujete $1 (novi odlomak)',
'editconflict' => 'Istovremeno uređivanje: $1',
必要であれば文章をカットアンドペーストしてテキストファイルとして保存し、後ほど保存をやり直してください。
データベースをロックした管理者による説明は以下の通りです:$1",
-'protectedpagewarning' => "'''è¦å\91\8aï¼\9aã\81\93ã\81®ã\83\9aã\83¼ã\82¸ã\81¯ä¿\9dè·ã\81\95ã\82\8cã\81¦ã\81\8aã\82\8a、管理者権限を持つ利用者のみが編集できます。'''
+'protectedpagewarning' => "'''è¦å\91\8aï¼\9aã\81\93ã\81®ã\83\9aã\83¼ã\82¸ã\81¯ä¿\9dè·ã\81\95ã\82\8cã\81¦ã\81\84ã\82\8bã\81\9fã\82\81、管理者権限を持つ利用者のみが編集できます。'''
参考として以下に一番最後の記録を表示します:",
'semiprotectedpagewarning' => "'''注意:'''このページは保護されているため、登録利用者のみが編集できます。
参考として以下に一番最後の記録を表示します:",
'prefs-personal' => 'Hmangtu chanchin tawi',
'prefs-rc' => 'Tihdanglam thar',
'prefs-watchlist' => 'Ralvèn',
-'prefs-watchlist-days' => 'Ralvèna ni tihlang tùr chin:',
+'prefs-watchlist-days' => 'Ralvèna ni tihlan tùr chin:',
'prefs-watchlist-days-max' => 'A rei berah ni $1 {{PLURAL:$1||}}',
'prefs-watchlist-edits' => 'Ralvèn pawhseia tihdanglam zât tihlan tùr tam ber:',
'prefs-watchlist-edits-max' => 'A tam ber: 1000',
'badarticleerror' => 'Aquesta accion pòt pas èsser efectuada sus aquesta pagina.',
'cannotdelete' => 'Impossible de suprimir la pagina o lo fichièr « $1 ».
Benlèu la supression ja es estada efectuada per qualqu’un mai.',
+'cannotdelete-title' => 'Impossible de suprimir la pagina "$1"',
'badtitle' => 'Títol marrit',
'badtitletext' => 'Lo títol de la pagina demandada es invalid, void o s’agís d’un títol interlenga o interprojècte mal ligat. Benlèu conten un o maites caractèrs que pòdon pas èsser utilizats dins los títols.',
'perfcached' => 'Aquò es una version en amagatal e es benlèu pas a jorn. A maximum of {{PLURAL:$1|one result is|$1 results are}} available in the cache.',
Foncion : $1<br />
Requèsta : $2',
'viewsource' => 'Vejatz lo tèxte font',
+'viewsource-title' => 'Veire la font de $1',
'actionthrottled' => 'Accion limitada',
'actionthrottledtext' => "Per luchar contra lo spam, l’utilizacion d'aquesta accion es limitada a un cèrt nombre de còps dins una sosta pro corta. S'avèra qu'avètz depassat aqueste limit. Ensajatz tornamai dins qualques minutas.",
'protectedpagetext' => 'Aquesta pagina es estada protegida per empachar sa modificacion.',
'updated' => '(Mes a jorn)',
'note' => "'''Nòta :'''",
'previewnote' => "'''Atencion, aqueste tèxte es sonque una previsualizacion e es pas encara estat salvat !'''",
+'continue-editing' => "Contunhar l'edicion",
'previewconflict' => 'Aquesta previsualizacion fa veire lo tèxte de la bóstia de modificacion superiora coma apareisserà se causissètz de lo salvar.',
'session_fail_preview' => "'''Podèm pas enregistrar vòstra modificacion a causa d’una pèrda d’informacions concernent vòstra sesilha.
Ensajatz tornarmai.
S'aquò capita pas un còp de mai, [[Special:UserLogout|desconnectatz-vos]], puèi connectatz-vos tornamai.'''",
'token_suffix_mismatch' => "'''Vòstra modificacion es pas estada acceptada perque vòstre navigador a mesclat los caractèrs de ponctuacion dins l’identificant d’edicion. La modificacion es estada regetada per empachar la corrupcion del tèxte de l’article. Aqueste problèma se produtz quand utilizatz un mandatari (proxy) anonim problematic.'''",
'editing' => 'Modificacion de $1',
+'creating' => 'Creacion de $1',
'editingsection' => 'Modificacion de $1 (seccion)',
'editingcomment' => 'Modificacion de $1 (seccion novèla)',
'editconflict' => 'Conflicte de modificacion : $1',
'edit-no-change' => 'Vòstra modificacion es estada ignorada perque cap de cambiament es pas estat fach dins lo tèxte.',
'edit-already-exists' => 'La pagina novèla a pogut èsser creada .
Existís ja.',
+'defaultmessagetext' => 'Messatge per defaut',
# Parser/template warnings
'expensive-parserfunction-warning' => 'Atencion : Aquesta pagina conten tròp d’apèls dispendioses de foncions del parser.
'action-userrights' => 'modificar totes los dreches d’utilizaire',
'action-userrights-interwiki' => 'modificar los dreches d’utilizaire e los sus d’autres wikis',
'action-siteadmin' => 'varrolhar o desvarrolhar la banca de donadas',
+'action-sendemail' => 'mandar corrièrs electronics',
# Recent changes
'nchanges' => '$1 {{PLURAL:$1|cambiament|cambiaments}}',
Una [[Special:WhatLinksHere/$2|tièra completa]] es disponibla.',
'nolinkstoimage' => 'Cap de pagina compòrta pas de ligam cap a aqueste imatge.',
'morelinkstoimage' => 'Vejatz [[Special:WhatLinksHere/$1|mai de ligams]] cap a aqueste imatge.',
+'linkstoimage-redirect' => '$1 (redireccion de fichièr) $2',
'duplicatesoffile' => "{{PLURAL:$1|Lo fichièr seguent es un duplicata|Los fichièrs seguents son de duplicatas}} d'aqueste fichièr ([[Special:FileDuplicateSearch/$2|mai de detalhs]]):",
'sharedupload' => 'Aqueste fichièr proven de $1 e pòt èsser utilizat per d’autres projèctes.',
'sharedupload-desc-there' => "Aqueste fichièr proven de $1 e pòt èsser utilizat per d'autres projèctes. Vejatz [$2 sa pagina de descripcion] per mai d'entresenhas.",
Cada entrada conten de ligams cap a la primièra e la segonda redireccions, e mai la primièra linha de tèxte de la segonda pagina, çò que provesís, de costuma, la « vertadièra » pagina cibla, cap a la quala la primièra redireccion deuriá redirigir.
Las entradas <del>barradas</del> son estadas resolgudas.',
'double-redirect-fixed-move' => '[[$1]] es estat renomenat, aquò es ara una redireccion cap a [[$2]]',
+'double-redirect-fixed-maintenance' => 'Correccion de la doble redireccion de [[$1]] a [[$2]]',
'double-redirect-fixer' => 'Corrector de redireccion',
'brokenredirects' => 'Redireccions copadas',
'watchnologin' => 'Vos sètz pas identificat(ada)',
'watchnologintext' => 'Vos cal èsser [[Special:UserLogin|connectat(ada)]]
per modificar vòstra lista de seguiment.',
+'addwatch' => 'Ajustar a la lista de seguiment',
'addedwatchtext' => 'La pagina "[[:$1]]" es estada aponduda a vòstra [[Special:Watchlist|lista de seguiment]].
Las modificacions venentas d\'aquesta pagina e de la pagina de discussion associada seràn repertoriadas aicí, e la pagina apareisserà <b>en gras</b> dins la [[Special:RecentChanges|tièra dels darrièrs cambiaments]] per èsser localizada mai aisidament.',
+'removewatch' => 'Suprimir de la lista de seguiment',
'removedwatchtext' => 'La pagina « [[:$1]] » es estada levada de vòstra [[Special:Watchlist|lista de seguiment]].',
'watch' => 'Seguir',
'watchthispage' => 'Seguir aquesta pagina',
a partir d'una adreça IP precedentament blocada.",
'ipusubmit' => 'Suprimir aqueste blocatge',
'unblocked' => '[[User:$1|$1]] es estat desblocat',
+'unblocked-range' => '$1 es estat desblocat',
'unblocked-id' => 'Lo blocatge $1 es estat levat',
'blocklist' => 'Utilizaires o adreças IP blocats',
'ipblocklist' => 'Utilizaires o adreças IP blocats',
'spam_reverting' => 'Restabliment de la darrièra version que conten pas de ligam cap a $1',
'spam_blanking' => 'Totas las versions que contenon de ligams cap a $1 son blanquidas',
+# Info page
+'pageinfo-subjectpage' => 'Pagina',
+'pageinfo-edits' => "Nombre d'edicions",
+'pageinfo-authors' => "Nombre d'autors distints",
+'pageinfo-views' => 'Nombre de vistas',
+'pageinfo-viewsperedit' => 'Visitas per modificacions',
+
# Skin names
'skinname-standard' => 'Estandard',
'skinname-nostalgia' => 'Nostalgia',
'exif-gpsdirection-m' => 'Nòrd magnetic',
'exif-iimcategory-edu' => 'Educacion',
+'exif-iimcategory-evn' => 'Environament',
'exif-iimcategory-hth' => 'Santat',
'exif-iimcategory-lab' => 'Tribailh',
'exif-iimcategory-pol' => 'Politic',
'table_pager_first' => 'Primièra pagina',
'table_pager_last' => 'Darrièra pagina',
'table_pager_limit' => 'Far veire $1 elements per pagina',
+'table_pager_limit_label' => 'Elements per pagina:',
'table_pager_limit_submit' => 'Accedir',
'table_pager_empty' => 'Cap de resultat',
# Special:ComparePages
'compare-page1' => 'Pagina 1',
'compare-page2' => 'Pagina 2',
+'compare-submit' => 'Comparar',
# Database error messages
'dberr-header' => 'Aqueste wiki a un problèma',
'ns-specialprotected' => 'Não é possível editar páginas especiais',
'titleprotected' => 'Este título foi protegido contra criação por [[User:$1|$1]].
A justificação foi "\'\'$2\'\'".',
+'filereadonlyerror' => 'Não é possível modificar o ficheiro "$1" porque o repositório de ficheiros "$2" está em modo de leitura.
+
+O administrador que efetuou o bloqueio deu a seguinte explicação: "$3".',
# Virus scanner
'virus-badscanner' => "Má configuração: antivírus desconhecido: ''$1''",
'note' => "'''Nota:'''",
'previewnote' => "'''Lembre-se que esta é apenas uma antevisão do resultado.'''
As modificações ainda não foram gravadas!",
+'continue-editing' => 'Continuar a editar',
'previewconflict' => 'Esta antevisão do resultado apresenta o texto da caixa de edição acima tal como este aparecerá se escolher gravá-lo.',
'session_fail_preview' => "'''Não foi possível processar a edição devido à perda dos dados da sua sessão.
Tente novamente, por favor.
'backend-fail-internal' => 'Ocorreu um erro desconhecido no servidor de armazenamento "$1".',
'backend-fail-contenttype' => 'Não foi possível determinar o tipo de conteúdo do ficheiro para armazenar em " $1 ".',
+# File journal errors
+'filejournal-fail-dbconnect' => 'Não foi possível ligar à base de dados de registos no "backend" de armazenamento "$1".',
+'filejournal-fail-dbquery' => 'Não foi possível atualizar a base de dados de registos do "backend" de armazenamento "$1".',
+
# Lock manager
'lockmanager-notlocked' => 'Não foi possível desbloquear " $1 "; não se encontra bloqueado.',
'lockmanager-fail-closelock' => 'Não foi possível encerrar a referência de bloqueio para "$1".',
'lockmanager-fail-deletelock' => 'Não foi possível eliminar a referência de bloqueio para "$1".',
+'lockmanager-fail-acquirelock' => 'Não foi possível adquirir bloqueio para "$1".',
+'lockmanager-fail-openlock' => 'Não foi possível abrir ficheiro de bloqueio para "$1".',
+'lockmanager-fail-releaselock' => 'Não foi possível libertar bloqueio para "$1".',
+'lockmanager-fail-db-bucket' => 'Não foi possível contactar bases de dados de bloqueio suficientes no "bucket" $1.',
+'lockmanager-fail-db-release' => 'Não foi possível libertar bloqueios na base de dados $1.',
+'lockmanager-fail-svr-release' => 'Não foi possível libertar bloqueios no servidor $1.',
# ZipDirectoryReader
'zip-file-open-error' => 'Foi encontrado um erro ao abrir o ficheiro ZIP para verificação.',
'uploadstash-badtoken' => 'Não foi possível executar essa operação, talvez porque as suas credenciais de edição expiraram. Tente novamente.',
'uploadstash-errclear' => 'Não foi possível apagar os ficheiros.',
'uploadstash-refresh' => 'Actualizar a lista de ficheiros',
+'invalid-chunk-offset' => 'Deslocamento de fragmento inválido',
# img_auth script messages
'img-auth-accessdenied' => 'Acesso negado',
Consulte a [$2 página de descrição do ficheiro] para mais informações, por favor.',
'sharedupload-desc-here' => 'Este ficheiro provém de $1 e pode ser usado por outros projectos.
A descrição na [$2 página de descrição] é mostrada abaixo.',
+'sharedupload-desc-edit' => 'Este ficheiro provém de $1 e pode ser utilizado por outros projetos.
+Talvez você pretenda editar a descrição na sua [$2 página de descrição de ficheiro] lá.',
+'sharedupload-desc-create' => 'Este ficheiro provém de $1 e pode ser utilizado por outros projetos.
+Talvez você pretenda editar a descrição na sua [$2 página de descrição de ficheiro] lá.',
'filepage-nofile' => 'Não existe nenhum ficheiro com este nome.',
'filepage-nofile-link' => 'Não existe nenhum ficheiro com este nome, mas pode [$1 carregá-lo].',
'uploadnewversion-linktext' => 'Carregar uma nova versão deste ficheiro',
'allpages-bad-ns' => 'A {{SITENAME}} não possui o espaço nominal "$1".',
'allpages-hide-redirects' => 'Ocultar redirecionamentos',
+# SpecialCachedPage
+'cachedspecial-refresh-now' => 'Ver mais recente.',
+
# Special:Categories
'categories' => 'Categorias',
'categoriespagetext' => '{{PLURAL:$1|A seguinte categoria contém páginas ou ficheiros multimédia|As seguintes categorias contêm páginas ou ficheiros multimédia}}.
'version-software' => 'Software instalado',
'version-software-product' => 'Produto',
'version-software-version' => 'Versão',
+'version-entrypoints' => 'URLs de ponto de entrada',
+'version-entrypoints-header-entrypoint' => 'Ponto de entrada',
+'version-entrypoints-header-url' => 'URL',
# Special:FilePath
'filepath' => 'Endereço de ficheiro',
Acest fapt se poate întâmpla atunci când folosești un serviciu proxy anonim.'''",
'edit_form_incomplete' => "'''Unele părți ale formularului de modificare nu au ajuns la server; verificați dacă modificările dumneavoastră sunt intacte și reîncercați.'''",
'editing' => 'modificare $1',
-'creating' => 'Se creează $1',
+'creating' => 'Crearea paginii $1',
'editingsection' => 'modificare $1 (secțiune)',
'editingcomment' => 'Modificare $1 (secțiune nouă)',
'editconflict' => 'Conflict de modificare: $1',
'backend-fail-closetemp' => 'Non ge pozze achiudere file temboranèe.',
'backend-fail-read' => "Non ge pozze leggere 'u file $1.",
'backend-fail-create' => "Non ge pozze ccrejà 'u file $1.",
+'backend-fail-readonly' => 'L\'archivije de rete "$1" jè pe stu mumende in sole letture. \'U mutive ha state: "$2"',
'backend-fail-connect' => 'Non ge pozze connettere \'a memorie de rrete "$1".',
# Lock manager
'javascripttest-disabled' => "Sta funzione non g'à state abbilitate sus a sta Uicchi.",
'javascripttest-title' => 'Stoche a esegue $1 test',
'javascripttest-pagetext-noframework' => 'Sta pàgene jè riservate pe le esecuziune de le test de Javascript.',
+'javascripttest-pagetext-unknownframework' => 'Ambiende de teste scanusciute "$1".',
'javascripttest-pagetext-frameworks' => 'Pe piacere scacchie une de le seguende ambiende de test: $1',
# Tooltip help for the actions
'duration-seconds' => '{{PLURAL:$1|seconde|seconde}}',
'duration-minutes' => '{{PLURAL:$1|minute|minute}}',
'duration-hours' => '{{PLURAL: $1|ore|ore}}',
+'duration-days' => '$1 {{PLURAL:$1|sciurne|sciurne}}',
'duration-weeks' => '{{PLURAL: $1|sumàne|sumàne}}',
'duration-years' => '{{PLURAL: $1|anne|anne}}',
'duration-decades' => '$1 {{PLURAL:$1|decade|decade}}',
'allpagesbadtitle' => 'கொடுக்கப்பட்ட தலைப்பு செல்லுபடியற்றது அல்லது பிழையான விக்கியிடை அல்லது மொழி முன்னொட்டைக் கொண்டுள்ளது. இது தலைப்புக்களில் பயன்படுத்த முடியாத எழுத்துக்களையும் கொண்டிருக்கலாம்.',
'allpages-bad-ns' => '{{SITENAME}} தளத்தில் "$1" பெயர்வெளி கிடையாது.',
+# SpecialCachedPage
+'cachedspecial-refresh-now' => 'அண்மையான பதிப்பை காண்க',
+
# Special:Categories
'categories' => 'பகுப்புகள்',
'categoriespagetext' => 'கீழே கொடுத்துள்ள பக்கங்கள் அல்லது ஊடகங்கள் இந்த {{PLURAL:$1|பகுப்பை|பகுப்புக்களை}} கொண்டுள்ளது.
'metadata-help' => 'இந்தக் கோப்பு கூடுதலான தகவல்களைக் கொண்டுளது, இவை பெரும்பாலும் இக்கோப்பை உருவாக்கப் பயன்படுத்திய எண்ணிம ஒளிப்படக்கருவி அல்லது ஒளிவருடியால் சேர்க்கப்பட்டிருக்கலாம். இக்கோப்பு ஏதாவது வகையில் மாற்றியமைக்கப்பட்டிருந்தால் இத்தகவல்கள் அவற்றைச் சரிவர தராமல் இருக்கலாம்.',
'metadata-expand' => 'மேலதிகத் தகவல்களைக் காட்டு',
'metadata-collapse' => 'மேலதிகத் தகவல்களை மறை',
-'metadata-fields' => 'இங்கே காட்டப்பட்டுள்ள எக்சிப் மேல்நிலைத் தரவுகள் படிமவிளக்கப்பக்கத்தில் காட்டப்படும். ஏனைய தரவுகள் இயல்பிருப்பாக மறைக்கப்பட்டிருக்கும்.
+'metadata-fields' => 'இங்கே காட்டப்பட்டுள்ள எக்சிப் மேல்நிலைத் தரவுகள் படிமவிளக்கப்பக்கத்தில் மேல்நிலைத் தரவுகள் அட்டவணை மறைக்கப்பட்டிருக்கும் பொழுது
+ காட்டப்படும்.
* make
* model
* datetimeoriginal
'version-software' => 'நிறுவப்பட்ட மென்பொருள்',
'version-software-product' => 'உற்பத்திப்பொருள்',
'version-software-version' => 'பதிப்பு',
+'version-entrypoints-header-url' => 'உரலி (URL)',
# Special:FilePath
'filepath' => 'கோப்பு வழி',
'category-article-count-limited' => '{{PLURAL:$1|Trang|$1 trang}} sau nằm trong thể loại hiện hành.',
'category-file-count' => '{{PLURAL:$2|Thể loại này có tập tin sau.|{{PLURAL:$1|Tập tin|$1 tập tin}} sau nằm trong thể loại này, trong tổng số $2 tập tin.}}',
'category-file-count-limited' => '{{PLURAL:$1|Tập tin|$1 tập tin}} sau nằm trong thể loại hiện hành.',
-'listingcontinuesabbrev' => 'tiếp',
+'listingcontinuesabbrev' => '(tiếp theo)',
'index-category' => 'Trang được ghi chỉ mục',
'noindex-category' => 'Trang không hiển thị trong bộ máy tìm kiếm',
'broken-file-category' => 'Trang nhúng tập tin không tồn tại',
* @author Kaganer
* @author KaiesTse
* @author Mark85296341
+ * @author Simon Shek
* @author Waihorace
* @author William915
* @author Wong128hk
'createaccount' => '開戶口',
'gotaccount' => '已經有戶口? $1。',
'gotaccountlink' => '登入',
+'userlogin-resetlink' => '唔記得簽到資料?',
'createaccountmail' => '用電郵',
'createaccountreason' => '原因:',
'badretype' => '你入嘅密碼唔一致。',
'filehist-filesize' => '檔案大細',
'filehist-comment' => '註解',
'filehist-missing' => '檔案遺失',
-'imagelinks' => '檔案連結',
+'imagelinks' => '檔案用途',
'linkstoimage' => '以下嘅$1個頁面連結到呢個檔案:',
'linkstoimage-more' => '多過$1版連過去呢個檔案。
下面嘅表只係列示咗連去呢個檔案嘅最頭$1版。
* @author PhiLiP
* @author Shinjiman
* @author Shizhao
+ * @author Simon Shek
* @author Supaiku
* @author Tommyang
* @author Waihorace
'upload-too-many-redirects' => '在网址中有太多重新定向',
'upload-unknown-size' => '未知大小',
'upload-http-error' => '发生HTTP错误:$1',
-'upload-copy-upload-invalid-domain' => 'ä¸\8dè\83½ä»\8e该å\9f\9få\90\8dä¸\8bè½½æ\96\87件。',
+'upload-copy-upload-invalid-domain' => 'ä¸\8dè\83½ä»\8e该å\9f\9få\90\8dä¸\8aè½½æ\96\87件å\89¯æ\9c¬。',
# File backend
'backend-fail-stream' => '无法流传送文件$1。',
请参阅在[$2 文件描述页面]以了解其相关信息。',
'sharedupload-desc-here' => '该文件来自于$1,它可能在其它计划项目中被应用。
它在[$2 文件描述页面]那边上的描述于下面显示。',
-'sharedupload-desc-edit' => '此文件是从 $1 和可能由其他维基项目使用。 !N !也许您想在其[ $2 文件描述页面]编辑说明。',
+'sharedupload-desc-edit' => '该文件来自$1,它可能在其它计划项目中被使用。
+或许您可以在其[$2 文件描述页面]上编辑说明。',
'sharedupload-desc-create' => '此文件是从 $1 和可能由其他维基项目使用。 !N !也许您想在其[ $2 文件描述页面]编辑说明。',
'filepage-nofile' => '不存在此名称的文件。',
'filepage-nofile-link' => '不存在此名称的文件,但您可以[$1 上传它]。',
* @author Philip
* @author Shinjiman
* @author Shizhao
+ * @author Simon Shek
* @author Skjackey tse
* @author Waihorace
* @author Wmr89502270
'customjsprotected' => '你並無權限去編輯此JavaScript頁面,因為他包含了另一位用戶的個人設定。',
'ns-specialprotected' => '特殊頁面是不可以編輯的。',
'titleprotected' => "這個標題已經被[[User:$1|$1]]保護以防止建立。理由是''$2''。",
+'filereadonlyerror' => '無法修改文件" $1 "因為文件庫" $2 "處於唯讀模式。 !
+管理員鎖定它的解釋是:" $3 "。',
# Virus scanner
'virus-badscanner' => "損壞設定: 未知的病毒掃瞄器: ''$1''",
'emailconfirmlink' => '確認您的郵箱地址',
'invalidemailaddress' => '郵箱地址格式不正確,請輸入正確的郵箱位址或清空該輸入框。',
'cannotchangeemail' => '本wiki不允許對賬戶的電郵地址進行更改。',
+'emaildisabled' => '此網站不能發送電子郵件。',
'accountcreated' => '已建立賬戶',
'accountcreatedtext' => '$1的賬戶已經被建立。',
'createaccount-title' => '在{{SITENAME}}中建立新賬戶',
'updated' => '(已更新)',
'note' => "'''注意:'''",
'previewnote' => "'''請記住這只是預覽,內容尚未儲存!'''",
+'continue-editing' => '繼續編輯',
'previewconflict' => '這個預覽顯示了上面文字編輯區中的內容。它將在{{GENDER:|你|妳|你}}選擇保存後出現。',
'session_fail_preview' => "'''很抱歉!由於部份資料遺失,我們無法處理您的編輯。'''
請再試一次。
'upload-too-many-redirects' => '在網址中有太多重新定向',
'upload-unknown-size' => '未知的大小',
'upload-http-error' => '已發生一個HTTP錯誤:$1',
+'upload-copy-upload-invalid-domain' => '不能從該域名上載檔𣗈副本。',
# File backend
'backend-fail-stream' => '無法流傳送文件$1。',
'backend-fail-closetemp' => '無法創建臨時文件。',
'backend-fail-read' => '找不到文件“$1”。',
'backend-fail-create' => '找不到「$1」檔案。',
+'backend-fail-maxsize' => '無法創建檔𣗈$1,因為它大於$2字節。',
'backend-fail-readonly' => '「$1」儲存後端目前是唯讀模式,因為:「$2」',
-'backend-fail-synced' => '文件"$1"在內部後端是不一致的區域。',
-'backend-fail-connect' => '無法連結至檔案後方“$1”。',
-'backend-fail-internal' => '檔案後方“$1”發生了一個未知錯誤。',
+'backend-fail-synced' => 'æ\96\87件"$1"å\9c¨å\85§é\83¨å\98å\84²å¾\8c端æ\98¯ä¸\8dä¸\80è\87´ç\9a\84å\8d\80å\9f\9fã\80\82',
+'backend-fail-connect' => '無法連結至存儲後方“$1”。',
+'backend-fail-internal' => '存儲後方“$1”發生了一個未知錯誤。',
'backend-fail-contenttype' => '無法確定檔案的內容類型以存儲於“$1”。',
-'backend-fail-batchsize' => '鑒於一批後端 $1 檔 {{PLURAL:$1| operation|operations}} ;限制是 $2 {{PLURAL:$2| operation|operations}}。',
+'backend-fail-batchsize' => '存儲後端被給予了$1次檔𣗈 {{PLURAL:$1|操作|操作}} ;限制是$2次{{PLURAL:$2|操作|操作}}。',
+
+# File journal errors
+'filejournal-fail-dbconnect' => '無法連接到後端存儲的日誌資料庫" $1 "。',
+'filejournal-fail-dbquery' => '無法更新後端存儲的日誌資料庫" $1 "。',
# Lock manager
'lockmanager-notlocked' => '無法解鎖「$1」;它沒有被鎖定。',
# img_auth script messages
'img-auth-accessdenied' => '拒絕存取',
-'img-auth-nopathinfo' => 'PATH_INFO缺失。您的服務器尚未設置傳送該信息。它可能是基於CGI的,因而不支持img_auth。[https://www.mediawiki.org/wiki/Manual:Image_Authorization 參見圖片認證。]',
+'img-auth-nopathinfo' => 'PATH_INFO缺失。
+您的服務器尚未設置傳送該信息。
+它可能是基於CGI的,因而不支持img_auth。
+請參見 https://www.mediawiki.org/wiki/Manual:Image_Authorization',
'img-auth-notindir' => '所請求的路徑不在已經設定的上載目錄。',
'img-auth-badtitle' => '不能夠由"$1"建立一個有效標題。',
'img-auth-nologinnWL' => '您而家並未登入,"$1"不在白名單上。',
請參閱在[$2 檔案描述頁面]以了解其相關資訊。',
'sharedupload-desc-here' => '該檔案來自於$1,它可能在其它計劃項目中被應用。
它在[$2 檔案描述頁面]那邊上的描述於下面顯示。',
+'sharedupload-desc-edit' => '該檔案來自$1,它可能在其它計劃項目中被使用。
+或許您可以在其[$2 檔𣗈描述頁面]上編輯說明。',
+'sharedupload-desc-create' => '該檔案來自$1,它可能在其它計劃項目中被使用。
+或許您可以在那邊的[$2 檔𣗈描述頁面]上編輯其說明。',
'filepage-nofile' => '不存在此名稱的檔案。',
'filepage-nofile-link' => '不存在此名稱的檔案,但您可以[$1 上傳它]。',
'uploadnewversion-linktext' => '上傳該檔案的新版本',
'allpagesprefix' => '顯示具有此前綴(名字空間)的頁面:',
'allpagesbadtitle' => '給定的頁面標題是非法的,或者具有一個內部語言或內部 wiki 的前綴。它可能包含一個或更多的不能用於標題的字元。',
'allpages-bad-ns' => '在{{SITENAME}}中沒有一個叫做"$1"的名字空間。',
+'allpages-hide-redirects' => '隱藏重定向頁',
+
+# SpecialCachedPage
+'cachedspecial-viewing-cached-ttl' => '你正在瀏覽本頁的緩存版本,至多可能存在$1的延遲。',
+'cachedspecial-viewing-cached-ts' => '您正在閱讀此頁的緩存版本,這可能不是完整的版本。',
+'cachedspecial-refresh-now' => '查看最新。',
# Special:Categories
'categories' => '頁面分類',
'ipb-confirm' => '確認封禁',
'badipaddress' => '無效IP地址',
'blockipsuccesssub' => '查封成功',
-'blockipsuccesstext' => '[[Special:Contributions/$1|$1]]已經被查封。
-<br />參看[[Special:BlockList|被封IP地址列表]]以覆審查封。',
+'blockipsuccesstext' => '[[Special:Contributions/$1|$1]]已經被查封。<br />
+參看[[Special:BlockList|被封IP地址列表]]以覆審查封。',
'ipb-blockingself' => '你要封禁自己!確認要這樣做嗎?',
'ipb-confirmhideuser' => '你要封禁用戶並隱藏其用戶名,這會隱藏在所有列表及日誌中涉及此用戶之用戶名。你確定要這樣做嗎?',
'ipb-edit-dropdown' => '編輯查封原因',
'vector.css' => '/* 此處的 CSS 將影響使用 Vector 面板的用戶 */',
'print.css' => '/* 此處的 CSS 將影響打印輸出 */',
'handheld.css' => '/* 此處的 CSS 將影響在 $wgHandheldStyle 設定手提裝置面板 */',
+'noscript.css' => '/* 此處的 CSS 將影響沒有啓用 JavaScript 的用戶 */',
+'group-autoconfirmed.css' => '/* 此處的 CSS 將只會影響自動確認用戶 */',
+'group-bot.css' => '/* 此處的 CSS 將只會影響機器人 */',
+'group-sysop.css' => '/* 此處的 CSS 將只會影響管理員 */',
+'group-bureaucrat.css' => '/* 此處的 CSS 將只會影響行政員 */',
# Scripts
'common.js' => '/* 此處的JavaScript將載入於所有用戶每一個頁面。 */',
'variantname-zh-sg' => 'disable',
Variants for Chinese language
*/
-'variantname-zh-hans' => '簡體',
-'variantname-zh-hant' => '繁體',
+'variantname-zh-hans' => '中文(简体)',
+'variantname-zh-hant' => '中文(繁體)',
'variantname-zh-cn' => '大陸簡體',
'variantname-zh-tw' => '台灣正體',
'variantname-zh-hk' => '香港繁體',
+'variantname-zh-mo' => '澳門繁體',
'variantname-zh-sg' => '新加坡簡體',
+'variantname-zh-my' => '马来西亚简体',
'variantname-zh' => '不轉換',
+# Variants for Gan language
+'variantname-gan-hans' => '中文(简体)',
+'variantname-gan-hant' => '中文(繁體)',
+
# Metadata
'metadata' => '元數據',
'metadata-help' => '此檔案中包含有擴展的訊息。這些訊息可能是由數位相機或掃描儀在創建或數字化過程中所添加的。
'version-software' => '已經安裝的軟件',
'version-software-product' => '產品',
'version-software-version' => '版本',
+'version-entrypoints' => '入口點URL',
+'version-entrypoints-header-entrypoint' => '入口點',
'version-entrypoints-header-url' => 'URL',
# Special:FilePath
}
if ( $channel === null ) {
$this->cleanupChanneled();
- if( php_sapi_name() == 'cli' ) {
- fwrite( STDOUT, $out );
- } else {
- print( $out );
- }
+ print( $out );
} else {
$out = preg_replace( '/\n\z/', '', $out );
$this->outputChanneled( $out, $channel );
*/
public function cleanupChanneled() {
if ( !$this->atLineStart ) {
- if( php_sapi_name() == 'cli' ) {
- fwrite( STDOUT, "\n" );
- } else {
- print "\n";
- }
+ print "\n";
$this->atLineStart = true;
}
}
return;
}
- $cli = php_sapi_name() == 'cli';
-
// End the current line if necessary
if ( !$this->atLineStart && $channel !== $this->lastChannel ) {
- if( $cli ) {
- fwrite( STDOUT, "\n" );
- } else {
- print "\n";
- }
+ print "\n";
}
- if( $cli ) {
- fwrite( STDOUT, $msg );
- } else {
- print $msg;
- }
+ print $msg;
$this->atLineStart = false;
if ( $channel === null ) {
// For unchanneled messages, output trailing newline immediately
- if( $cli ) {
- fwrite( STDOUT, "\n" );
- } else {
- print "\n";
- }
+ print "\n";
$this->atLineStart = true;
}
$this->lastChannel = $channel;
'parser-template-loop-warning',
'parser-template-recursion-depth-warning',
'language-converter-depth-warning',
+ 'node-count-exceeded-category',
+ 'node-count-exceeded-warning',
+ 'expansion-depth-exceeded-category',
+ 'expansion-depth-exceeded-warning',
),
'undo' => array(
'undo-success',
}
public function execute() {
- global $wgMemCachedServers;
+ global $wgMemCachedServers, $wgMemCachedTimeout;
$iterations = $this->getOption( 'i', 100 );
if ( $this->hasArg() ) {
foreach ( $wgMemCachedServers as $server ) {
$this->output( $server . " ", $server );
- $mcc = new MemCachedClientforWiki( array( 'persistant' => true ) );
+ $mcc = new MemCachedClientforWiki( array(
+ 'persistant' => true,
+ 'timeout' => $wgMemCachedTimeout
+ ) );
$mcc->set_servers( array( $server ) );
$set = 0;
$incr = 0;
/**
* Simple message parser, does $N replacement and nothing else.
* This may be overridden to provide a more complex message parser.
- *
+ *
* This function will not be called for nonexistent messages.
*/
parser: function() {
return parameters[index] !== undefined ? parameters[index] : '$' + match;
} );
},
-
+
/**
* Appends (does not replace) parameters for replacement to the .parameters property.
*
text = this.parser();
text = mw.html.escape( text );
}
-
+
if ( this.format === 'parse' ) {
text = this.parser();
}
* emulates console.log in console-less environments.
*/
log: function() { },
-
+
/**
* @var constructor Make the Map constructor publicly available.
*/
* @var constructor Make the Message constructor publicly available.
*/
Message: Message,
-
+
/**
* List of configuration values
*
* in the global window object.
*/
config: null,
-
+
/**
* @var object
*
* Empty object that plugins can be installed in.
*/
libs: {},
-
+
/* Extension points */
-
+
legacy: {},
-
+
/**
* Localization system
*/
messages: new Map(),
-
+
/* Public Methods */
-
+
/**
* Gets a message object, similar to wfMessage()
*
}
return new Message( mw.messages, key, parameters );
},
-
+
/**
* Gets a message string, similar to wfMsg()
*
msg: function ( /* key, parameter_1, parameter_2, .. */ ) {
return mw.message.apply( mw.message, arguments ).toString();
},
-
+
/**
* Client-side module loader which integrates with the MediaWiki ResourceLoader
*/
loader: ( function () {
-
+
/* Private Members */
-
+
/**
* Mapping of registered modules
*
ready = false,
// Selector cache for the marker element. Use getMarker() to get/use the marker!
$marker = null;
-
+
/* Cache document ready status */
-
+
$(document).ready( function () {
ready = true;
} );
-
+
/* Private methods */
-
+
function getMarker() {
// Cached ?
if ( $marker ) {
}
return true;
}
-
+
/**
* Generates an ISO8601 "basic" string from a UNIX timestamp
*/
pad( d.getUTCHours(), d.getUTCMinutes(), d.getUTCSeconds() ), 'Z'
].join( '' );
}
-
+
/**
* Recursively resolves dependencies and detects circular references
*/
function recurse( module, resolved, unresolved ) {
var n, deps, len;
-
+
if ( registry[module] === undefined ) {
throw new Error( 'Unknown dependency: ' + module );
}
}
resolved[resolved.length] = module;
}
-
+
/**
* Gets a list of module names that a module depends on in their proper dependency order
*
*/
function resolve( module ) {
var modules, m, deps, n, resolved;
-
+
// Allow calling with an array of module names
if ( $.isArray( module ) ) {
modules = [];
throw new Error( 'Invalid module argument: ' + module );
}
-
+
/**
* Narrows a list of module names down to those matching a specific
* state (see comment on top of this scope for a list of valid states).
*/
function filter( states, modules ) {
var list, module, s, m;
-
+
// Allow states to be given as a string
if ( typeof states === 'string' ) {
states = [states];
}
return list;
}
-
+
/**
* Automatically executes jobs and modules which are pending with satistifed dependencies.
*
*/
function handlePending( module ) {
var j, r;
-
+
try {
// Run jobs whose dependencies have just been met
for ( j = 0; j < jobs.length; j += 1 ) {
throw e;
}
}
-
+
/**
* Adds a script tag to the DOM, either using document.write or low-level DOM manipulation,
* depending on whether document-ready has occured yet and whether we are in async mode.
if ( $.isFunction( callback ) ) {
// Attach handlers for all browsers (based on jQuery.ajax)
script.onload = script.onreadystatechange = function() {
-
+
if (
!done
&& (
|| /loaded|complete/.test( script.readyState )
)
) {
-
+
done = true;
-
+
callback();
-
+
// Handle memory leak in IE. This seems to fail in
// IE7 sometimes (Permission Denied error when
// accessing script.parentNode) so wrap it in
if ( script.parentNode ) {
script.parentNode.removeChild( script );
}
-
+
// Dereference the script
script = undefined;
} catch ( e ) { }
}
};
}
-
+
if ( window.opera ) {
// Appending to the <head> blocks rendering completely in Opera,
// so append to the <body> after document ready. This means the
}
}
}
-
+
/**
* Executes a loaded module, making it ready to use
*
*/
function execute( module, callback ) {
var style, media, i, script, markModuleReady, nestedAddScript;
-
+
if ( registry[module] === undefined ) {
throw new Error( 'Module has not been registered yet: ' + module );
} else if ( registry[module].state === 'registered' ) {
} else if ( registry[module].state === 'ready' ) {
throw new Error( 'Module has already been loaded: ' + module );
}
-
+
// Add styles
if ( $.isPlainObject( registry[module].style ) ) {
// 'media' type ignored, see documentation of mw.loader.implement
callback();
return;
}
-
+
addScript( arr[i], function() {
nestedAddScript( arr, callback, async, i + 1 );
}, async );
};
-
+
if ( $.isArray( script ) ) {
registry[module].state = 'loading';
nestedAddScript( script, markModuleReady, registry[module].async, 0 );
registry[module].state = 'error';
}
}
-
+
/**
* Adds a dependencies to the queue with optional callbacks to be run
* when the dependencies are ready or fail
*/
function request( dependencies, ready, error, async ) {
var regItemDeps, regItemDepLen, n;
-
+
// Allow calling by single module name
if ( typeof dependencies === 'string' ) {
dependencies = [dependencies];
// Work the queue
mw.loader.work();
}
-
+
function sortQuery(o) {
var sorted = {}, key, a = [];
for ( key in o ) {
}
return sorted;
}
-
+
/**
* Converts a module map of the form { foo: [ 'bar', 'baz' ], bar: [ 'baz, 'quux' ] }
* to a query string of the form foo.bar,baz|bar.baz,quux
}
return arr.join( '|' );
}
-
+
/**
* Asynchronously append a script tag to the end of the body
* that invokes load.php
// Append &* to avoid triggering the IE6 extension check
addScript( sourceLoadScript + '?' + $.param( request ) + '&*', null, async );
}
-
+
/* Public Methods */
return {
addStyleTag: addStyleTag,
source, group, g, i, modules, maxVersion, sourceLoadScript,
currReqBase, currReqBaseLength, moduleMap, l,
lastDotIndex, prefix, suffix, bytesAdded, async;
-
+
// Build a list of request parameters common to all requests.
reqBase = {
skin: mw.config.get( 'skin' ),
// Split module batch by source and by group.
splits = {};
maxQueryLength = mw.config.get( 'wgResourceLoaderMaxQueryLength', -1 );
-
+
// Appends a list of modules from the queue to the batch
for ( q = 0; q < queue.length; q += 1 ) {
// Only request modules which are registered
if ( !batch.length ) {
return;
}
-
+
// The queue has been processed into the batch, clear up the queue.
queue = [];
-
+
// Always order modules alphabetically to help reduce cache
// misses for otherwise identical content.
batch.sort();
-
+
// Split batch by source and by group.
for ( b = 0; b < batch.length; b += 1 ) {
bSource = registry[batch[b]].source;
bSourceGroup = splits[bSource][bGroup];
bSourceGroup[bSourceGroup.length] = batch[b];
}
-
+
// Clear the batch - this MUST happen before we append any
// script elements to the body or it's possible that a script
// will be locally cached, instantly load, and work the batch
// again, all before we've cleared it causing each request to
// include modules which are already loaded.
batch = [];
-
+
for ( source in splits ) {
-
+
sourceLoadScript = sources[source].loadScript;
-
+
for ( group in splits[source] ) {
-
+
// Cache access to currently selected list of
// modules for this group from this source.
modules = splits[source][group];
-
+
// Calculate the highest timestamp
maxVersion = 0;
for ( g = 0; g < modules.length; g += 1 ) {
maxVersion = registry[modules[g]].version;
}
}
-
+
currReqBase = $.extend( { 'version': formatVersionNumber( maxVersion ) }, reqBase );
currReqBaseLength = $.param( currReqBase ).length;
async = true;
// We may need to split up the request to honor the query string length limit,
// so build it piece by piece.
l = currReqBaseLength + 9; // '&modules='.length == 9
-
+
moduleMap = {}; // { prefix: [ suffixes ] }
-
+
for ( i = 0; i < modules.length; i += 1 ) {
// Determine how many bytes this module would add to the query string
lastDotIndex = modules[i].lastIndexOf( '.' );
bytesAdded = moduleMap[prefix] !== undefined
? suffix.length + 3 // '%2C'.length == 3
: modules[i].length + 3; // '%7C'.length == 3
-
+
// If the request would become too long, create a new one,
// but don't create empty requests
if ( maxQueryLength > 0 && !$.isEmptyObject( moduleMap ) && l + bytesAdded > maxQueryLength ) {
}
}
},
-
+
/**
* Register a source.
*
}
return true;
}
-
+
if ( sources[id] !== undefined ) {
throw new Error( 'source already registered: ' + id );
}
-
+
sources[id] = props;
-
+
return true;
},
-
+
/**
* Registers a module, letting the system know about it and its
* properties. Startup modules contain calls to this function.
registry[module].dependencies = dependencies;
}
},
-
+
/**
* Implements a module, giving the system a course of action to take
* upon loading. Results of a request for one or more modules contain
execute( module );
}
},
-
+
/**
* Executes a function as soon as one or more required modules are ready
*
request( dependencies, ready, error );
}
},
-
+
/**
* Loads an external script or one or more modules for future use
*
request( filtered, null, null, async );
return;
},
-
+
/**
* Changes the state of a module
*
registry[module].state = state;
}
},
-
+
/**
* Gets the version of a module
*
}
return null;
},
-
+
/**
* @deprecated since 1.18 use mw.loader.getVersion() instead
*/
version: function () {
return mw.loader.getVersion.apply( mw.loader, arguments );
},
-
+
/**
* Gets the state of a module
*
}
return null;
},
-
+
/**
* Get names of all registered modules.
*
return key;
} );
},
-
+
/**
* For backwards-compatibility with Squid-cached pages. Loads mw.user
*/
}
};
}() ),
-
+
/** HTML construction helper functions */
html: ( function () {
function escapeCallback( s ) {
escape: function ( s ) {
return s.replace( /['"<>&]/g, escapeCallback );
},
-
+
/**
* Wrapper object for raw HTML passed to mw.html.element().
* @constructor
Raw: function ( value ) {
this.value = value;
},
-
+
/**
* Wrapper object for CDATA element contents passed to mw.html.element()
* @constructor
Cdata: function ( value ) {
this.value = value;
},
-
+
/**
* Create an HTML element string, with safe escaping.
*
*/
element: function ( name, attrs, contents ) {
var v, attrName, s = '<' + name;
-
+
for ( attrName in attrs ) {
v = attrs[attrName];
// Convert name=true, to name=name
tokens: new Map()
}
};
-
+
}( jQuery ) );
// Alias $j to jQuery for backwards compatibility
--- /dev/null
+<?php
+
+// It would be great if we were able to use PHPUnit's getMockForAbstractClass
+// instead of the MaintenanceFixup hack below. However, we cannot do
+// without changing the visibility and without working around hacks in
+// Maintenance.php
+//
+// For the same reason, we cannot just use FakeMaintenance.
+
+/**
+ * makes parts of the API of Maintenance that is hidden by protected visibily
+ * visible for testing, and makes up for a stream closing hack in Maintenance.php.
+ *
+ * This class is solely used for being able to test Maintenance right now
+ * without having to apply major refactorings to fix some design issues in
+ * Maintenance.php. Before adding more functions here, please consider whether
+ * this approach is correct, or a refactoring Maintenance to separate concers
+ * is more appropriate.
+ *
+ * Upon refactoring, keep in mind that besides the maintenance scrits themselves
+ * and tests right here, also at least Extension:Maintenance make use of
+ * Maintenance.
+ *
+ * Due to a hack in Maintenance.php using register_shutdown_function, be sure to
+ * finally call simulateShutdown on MaintenanceFixup instance before a test
+ * ends.
+ *
+ */
+class MaintenanceFixup extends Maintenance {
+
+ // --- Making up for the register_shutdown_function hack in Maintenance.php
+
+ /**
+ * The test case that generated this instance.
+ *
+ * This member is motivated by allowing the destructor to check whether or not
+ * the test failed, in order to avoid unnecessary nags about omitted shutdown
+ * simulation.
+ * But as it is already available, we also usi it to flagging tests as failed
+ *
+ * @var MediaWikiTestCase
+ */
+ private $testCase;
+
+ /**
+ * shutdownSimulated === true iff simulateShutdown has done it's work
+ *
+ * @var bool
+ */
+ private $shutdownSimulated = false;
+
+ /**
+ * Simulates what Maintenance wants to happen at script's end.
+ */
+ public function simulateShutdown() {
+
+ if ( $this->shutdownSimulated ) {
+ $this->testCase->fail( __METHOD__ . " called more than once" );
+ }
+
+ // The cleanup action.
+ $this->outputChanneled( false );
+
+ // Bookkeeping that we simulated the clean up.
+ $this->shutdownSimulated = true;
+ }
+
+ // Note that the "public" here does not change visibility
+ public function outputChanneled( $msg, $channel = null ) {
+ if ( $this->shutdownSimulated ) {
+ if ( $msg !== false ) {
+ $this->testCase->fail( "Already past simulated shutdown, but msg is "
+ . "not false. Did the hack in Maintenance.php change? Please "
+ . "adapt the test case or Maintenance.php" );
+ }
+
+ // The current call is the one registered via register_shutdown_function.
+ // We can safely ignore it, as we simulated this one via simulateShutdown
+ // before (if we did not, the destructor of this instance will warn about
+ // it)
+ return;
+ }
+
+ return call_user_func_array ( array( "parent", __FUNCTION__ ), func_get_args() );
+ }
+
+ /**
+ * Safety net around register_shutdown_function of Maintenance.php
+ */
+ public function __destruct() {
+ if ( ( ! $this->shutdownSimulated ) && ( ! $this->testCase->hasFailed() ) ) {
+ // Someone generated a MaintenanceFixup instance without calling
+ // simulateShutdown. We'd have to raise a PHPUnit exception to correctly
+ // flag this illegal usage. However, we are already in a destruktor, which
+ // would trigger undefined behaviour. Hence, we can only report to the
+ // error output :( Hopefully people read the PHPUnit output.
+ fwrite( STDERR, "ERROR! Instance of " . __CLASS__ . " destructed without "
+ . "calling simulateShutdown method. Call simulateShutdown on the "
+ . "instance before it gets destructed." );
+ }
+
+ // The following guard is required, as PHP does not offer default destructors :(
+ if ( is_callable( "parent::__destruct" ) ) {
+ parent::__destruct();
+ }
+ }
+
+ public function __construct( MediaWikiTestCase $testCase ) {
+ parent::__construct();
+ $this->testCase = $testCase;
+ }
+
+
+
+ // --- Making protected functions visible for test
+
+ public function output( $out, $channel = null ) {
+ // Just to make PHP not nag about signature mismatches, we copied
+ // Maintenance::output signature. However, we do not use (or rely on)
+ // those variables. Instead we pass to Maintenance::output whatever we
+ // receive at runtime.
+ return call_user_func_array ( array( "parent", __FUNCTION__ ), func_get_args() );
+ }
+
+
+
+ // --- Requirements for getting instance of abstract class
+
+ public function execute() {
+ $this->testCase->fail( __METHOD__ . " called unexpectedly" );
+ }
+
+}
+
+class MaintenanceTest extends MediaWikiTestCase {
+
+
+ /**
+ * The main Maintenance instance that is used for testing.
+ *
+ * @var MaintenanceFixup
+ */
+ private $m;
+
+
+ protected function setUp() {
+ parent::setUp();
+ $this->m = new MaintenanceFixup( $this );
+ }
+
+
+ /**
+ * asserts the output before and after simulating shutdown
+ *
+ * This function simulates shutdown of self::m.
+ *
+ * @param $preShutdownOutput string: expected output before simulating shutdown
+ * @param $expectNLAppending bool: Whether or not shutdown simulation is expected
+ * to add a newline to the output. If false, $preShutdownOutput is the
+ * expected output after shutdown simulation. Otherwise,
+ * $preShutdownOutput with an appended newline is the expected output
+ * after shutdown simulation.
+ */
+ private function assertOutputPrePostShutdown( $preShutdownOutput, $expectNLAppending ) {
+
+ $this->assertEquals( $preShutdownOutput, $this->getActualOutput(),
+ "Output before shutdown simulation" );
+
+ $this->m->simulateShutdown();
+
+ $postShutdownOutput = $preShutdownOutput . ( $expectNLAppending ? "\n" : "" );
+ $this->expectOutputString( $postShutdownOutput );
+ }
+
+
+ // Although the following tests do not seem to be too consistent (compare for
+ // example the newlines within the test.*StringString tests, or the
+ // test.*Intermittent.* tests), the objective of these tests is not to describe
+ // consistent behaviour, but rather currently existing behaviour.
+
+
+ function testOutputEmpty() {
+ $this->m->output( "" );
+ $this->assertOutputPrePostShutdown( "", False );
+ }
+
+ function testOutputString() {
+ $this->m->output( "foo" );
+ $this->assertOutputPrePostShutdown( "foo", False );
+ }
+
+ function testOutputStringString() {
+ $this->m->output( "foo" );
+ $this->m->output( "bar" );
+ $this->assertOutputPrePostShutdown( "foobar", False );
+ }
+
+ function testOutputStringNL() {
+ $this->m->output( "foo\n" );
+ $this->assertOutputPrePostShutdown( "foo\n", False );
+ }
+
+ function testOutputStringNLNL() {
+ $this->m->output( "foo\n\n" );
+ $this->assertOutputPrePostShutdown( "foo\n\n", False );
+ }
+
+ function testOutputStringNLString() {
+ $this->m->output( "foo\nbar" );
+ $this->assertOutputPrePostShutdown( "foo\nbar", False );
+ }
+
+ function testOutputStringNLStringNL() {
+ $this->m->output( "foo\nbar\n" );
+ $this->assertOutputPrePostShutdown( "foo\nbar\n", False );
+ }
+
+ function testOutputStringNLStringNLLinewise() {
+ $this->m->output( "foo\n" );
+ $this->m->output( "bar\n" );
+ $this->assertOutputPrePostShutdown( "foo\nbar\n", False );
+ }
+
+ function testOutputStringNLStringNLArbitrary() {
+ $this->m->output( "" );
+ $this->m->output( "foo" );
+ $this->m->output( "" );
+ $this->m->output( "\n" );
+ $this->m->output( "ba" );
+ $this->m->output( "" );
+ $this->m->output( "r\n" );
+ $this->assertOutputPrePostShutdown( "foo\nbar\n", False );
+ }
+
+ function testOutputStringNLStringNLArbitraryAgain() {
+ $this->m->output( "" );
+ $this->m->output( "foo" );
+ $this->m->output( "" );
+ $this->m->output( "\nb" );
+ $this->m->output( "a" );
+ $this->m->output( "" );
+ $this->m->output( "r\n" );
+ $this->assertOutputPrePostShutdown( "foo\nbar\n", False );
+ }
+
+ function testOutputWNullChannelEmpty() {
+ $this->m->output( "", null );
+ $this->assertOutputPrePostShutdown( "", False );
+ }
+
+ function testOutputWNullChannelString() {
+ $this->m->output( "foo", null );
+ $this->assertOutputPrePostShutdown( "foo", False );
+ }
+
+ function testOutputWNullChannelStringString() {
+ $this->m->output( "foo", null );
+ $this->m->output( "bar", null );
+ $this->assertOutputPrePostShutdown( "foobar", False );
+ }
+
+ function testOutputWNullChannelStringNL() {
+ $this->m->output( "foo\n", null );
+ $this->assertOutputPrePostShutdown( "foo\n", False );
+ }
+
+ function testOutputWNullChannelStringNLNL() {
+ $this->m->output( "foo\n\n", null );
+ $this->assertOutputPrePostShutdown( "foo\n\n", False );
+ }
+
+ function testOutputWNullChannelStringNLString() {
+ $this->m->output( "foo\nbar", null );
+ $this->assertOutputPrePostShutdown( "foo\nbar", False );
+ }
+
+ function testOutputWNullChannelStringNLStringNL() {
+ $this->m->output( "foo\nbar\n", null );
+ $this->assertOutputPrePostShutdown( "foo\nbar\n", False );
+ }
+
+ function testOutputWNullChannelStringNLStringNLLinewise() {
+ $this->m->output( "foo\n", null );
+ $this->m->output( "bar\n", null );
+ $this->assertOutputPrePostShutdown( "foo\nbar\n", False );
+ }
+
+ function testOutputWNullChannelStringNLStringNLArbitrary() {
+ $this->m->output( "", null );
+ $this->m->output( "foo", null );
+ $this->m->output( "", null );
+ $this->m->output( "\n", null );
+ $this->m->output( "ba", null );
+ $this->m->output( "", null );
+ $this->m->output( "r\n", null );
+ $this->assertOutputPrePostShutdown( "foo\nbar\n", False );
+ }
+
+ function testOutputWNullChannelStringNLStringNLArbitraryAgain() {
+ $this->m->output( "", null );
+ $this->m->output( "foo", null );
+ $this->m->output( "", null );
+ $this->m->output( "\nb", null );
+ $this->m->output( "a", null );
+ $this->m->output( "", null );
+ $this->m->output( "r\n", null );
+ $this->assertOutputPrePostShutdown( "foo\nbar\n", False );
+ }
+
+ function testOutputWChannelString() {
+ $this->m->output( "foo", "bazChannel" );
+ $this->assertOutputPrePostShutdown( "foo", True );
+ }
+
+ function testOutputWChannelStringNL() {
+ $this->m->output( "foo\n", "bazChannel" );
+ $this->assertOutputPrePostShutdown( "foo", True );
+ }
+
+ function testOutputWChannelStringNLNL() {
+ // If this test fails, note that output takes strings with double line
+ // endings (although output's implementation in this situation calls
+ // outputChanneled with a string ending in a nl ... which is not allowed
+ // according to the documentation of outputChanneled)
+ $this->m->output( "foo\n\n", "bazChannel" );
+ $this->assertOutputPrePostShutdown( "foo\n", True );
+ }
+
+ function testOutputWChannelStringNLString() {
+ $this->m->output( "foo\nbar", "bazChannel" );
+ $this->assertOutputPrePostShutdown( "foo\nbar", True );
+ }
+
+ function testOutputWChannelStringNLStringNL() {
+ $this->m->output( "foo\nbar\n", "bazChannel" );
+ $this->assertOutputPrePostShutdown( "foo\nbar", True );
+ }
+
+ function testOutputWChannelStringNLStringNLLinewise() {
+ $this->m->output( "foo\n", "bazChannel" );
+ $this->m->output( "bar\n", "bazChannel" );
+ $this->assertOutputPrePostShutdown( "foobar", True );
+ }
+
+ function testOutputWChannelStringNLStringNLArbitrary() {
+ $this->m->output( "", "bazChannel" );
+ $this->m->output( "foo", "bazChannel" );
+ $this->m->output( "", "bazChannel" );
+ $this->m->output( "\n", "bazChannel" );
+ $this->m->output( "ba", "bazChannel" );
+ $this->m->output( "", "bazChannel" );
+ $this->m->output( "r\n", "bazChannel" );
+ $this->assertOutputPrePostShutdown( "foobar", True );
+ }
+
+ function testOutputWChannelStringNLStringNLArbitraryAgain() {
+ $this->m->output( "", "bazChannel" );
+ $this->m->output( "foo", "bazChannel" );
+ $this->m->output( "", "bazChannel" );
+ $this->m->output( "\nb", "bazChannel" );
+ $this->m->output( "a", "bazChannel" );
+ $this->m->output( "", "bazChannel" );
+ $this->m->output( "r\n", "bazChannel" );
+ $this->assertOutputPrePostShutdown( "foo\nbar", True );
+ }
+
+ function testOutputWMultipleChannelsChannelChange() {
+ $this->m->output( "foo", "bazChannel" );
+ $this->m->output( "bar", "bazChannel" );
+ $this->m->output( "qux", "quuxChannel" );
+ $this->m->output( "corge", "bazChannel" );
+ $this->assertOutputPrePostShutdown( "foobar\nqux\ncorge", True );
+ }
+
+ function testOutputWMultipleChannelsChannelChangeNL() {
+ $this->m->output( "foo", "bazChannel" );
+ $this->m->output( "bar\n", "bazChannel" );
+ $this->m->output( "qux\n", "quuxChannel" );
+ $this->m->output( "corge", "bazChannel" );
+ $this->assertOutputPrePostShutdown( "foobar\nqux\ncorge", True );
+ }
+
+ function testOutputWAndWOChannelStringStartWO() {
+ $this->m->output( "foo" );
+ $this->m->output( "bar", "bazChannel" );
+ $this->m->output( "qux" );
+ $this->m->output( "quux", "bazChannel" );
+ $this->assertOutputPrePostShutdown( "foobar\nquxquux", True );
+ }
+
+ function testOutputWAndWOChannelStringStartW() {
+ $this->m->output( "foo", "bazChannel" );
+ $this->m->output( "bar" );
+ $this->m->output( "qux", "bazChannel" );
+ $this->m->output( "quux" );
+ $this->assertOutputPrePostShutdown( "foo\nbarqux\nquux", False );
+ }
+
+ function testOutputWChannelTypeSwitch() {
+ $this->m->output( "foo", 1 );
+ $this->m->output( "bar", 1.0 );
+ $this->assertOutputPrePostShutdown( "foo\nbar", True );
+ }
+
+ function testOutputIntermittentEmpty() {
+ $this->m->output( "foo" );
+ $this->m->output( "" );
+ $this->m->output( "bar" );
+ $this->assertOutputPrePostShutdown( "foobar", False );
+ }
+
+ function testOutputIntermittentFalse() {
+ $this->m->output( "foo" );
+ $this->m->output( false );
+ $this->m->output( "bar" );
+ $this->assertOutputPrePostShutdown( "foobar", False );
+ }
+
+ function testOutputIntermittentFalseAfterOtherChannel() {
+ $this->m->output( "qux", "quuxChannel" );
+ $this->m->output( "foo" );
+ $this->m->output( false );
+ $this->m->output( "bar" );
+ $this->assertOutputPrePostShutdown( "qux\nfoobar", False );
+ }
+
+ function testOutputWNullChannelIntermittentEmpty() {
+ $this->m->output( "foo", null );
+ $this->m->output( "", null );
+ $this->m->output( "bar", null );
+ $this->assertOutputPrePostShutdown( "foobar", False );
+ }
+
+ function testOutputWNullChannelIntermittentFalse() {
+ $this->m->output( "foo", null );
+ $this->m->output( false, null );
+ $this->m->output( "bar", null );
+ $this->assertOutputPrePostShutdown( "foobar", False );
+ }
+
+ function testOutputWChannelIntermittentEmpty() {
+ $this->m->output( "foo", "bazChannel" );
+ $this->m->output( "", "bazChannel" );
+ $this->m->output( "bar", "bazChannel" );
+ $this->assertOutputPrePostShutdown( "foobar", True );
+ }
+
+ function testOutputWChannelIntermittentFalse() {
+ $this->m->output( "foo", "bazChannel" );
+ $this->m->output( false, "bazChannel" );
+ $this->m->output( "bar", "bazChannel" );
+ $this->assertOutputPrePostShutdown( "foobar", True );
+ }
+
+ // Note that (per documentation) outputChanneled does take strings that end
+ // in \n, hence we do not test such strings.
+
+ function testOutputChanneledEmpty() {
+ $this->m->outputChanneled( "" );
+ $this->assertOutputPrePostShutdown( "\n", False );
+ }
+
+ function testOutputChanneledString() {
+ $this->m->outputChanneled( "foo" );
+ $this->assertOutputPrePostShutdown( "foo\n", False );
+ }
+
+ function testOutputChanneledStringString() {
+ $this->m->outputChanneled( "foo" );
+ $this->m->outputChanneled( "bar" );
+ $this->assertOutputPrePostShutdown( "foo\nbar\n", False );
+ }
+
+ function testOutputChanneledStringNLString() {
+ $this->m->outputChanneled( "foo\nbar" );
+ $this->assertOutputPrePostShutdown( "foo\nbar\n", False );
+ }
+
+ function testOutputChanneledStringNLStringNLArbitraryAgain() {
+ $this->m->outputChanneled( "" );
+ $this->m->outputChanneled( "foo" );
+ $this->m->outputChanneled( "" );
+ $this->m->outputChanneled( "\nb" );
+ $this->m->outputChanneled( "a" );
+ $this->m->outputChanneled( "" );
+ $this->m->outputChanneled( "r" );
+ $this->assertOutputPrePostShutdown( "\nfoo\n\n\nb\na\n\nr\n", False );
+ }
+
+ function testOutputChanneledWNullChannelEmpty() {
+ $this->m->outputChanneled( "", null );
+ $this->assertOutputPrePostShutdown( "\n", False );
+ }
+
+ function testOutputChanneledWNullChannelString() {
+ $this->m->outputChanneled( "foo", null );
+ $this->assertOutputPrePostShutdown( "foo\n", False );
+ }
+
+ function testOutputChanneledWNullChannelStringString() {
+ $this->m->outputChanneled( "foo", null );
+ $this->m->outputChanneled( "bar", null );
+ $this->assertOutputPrePostShutdown( "foo\nbar\n", False );
+ }
+
+ function testOutputChanneledWNullChannelStringNLString() {
+ $this->m->outputChanneled( "foo\nbar", null );
+ $this->assertOutputPrePostShutdown( "foo\nbar\n", False );
+ }
+
+ function testOutputChanneledWNullChannelStringNLStringNLArbitraryAgain() {
+ $this->m->outputChanneled( "", null );
+ $this->m->outputChanneled( "foo", null );
+ $this->m->outputChanneled( "", null );
+ $this->m->outputChanneled( "\nb", null );
+ $this->m->outputChanneled( "a", null );
+ $this->m->outputChanneled( "", null );
+ $this->m->outputChanneled( "r", null );
+ $this->assertOutputPrePostShutdown( "\nfoo\n\n\nb\na\n\nr\n", False );
+ }
+
+ function testOutputChanneledWChannelString() {
+ $this->m->outputChanneled( "foo", "bazChannel" );
+ $this->assertOutputPrePostShutdown( "foo", True );
+ }
+
+ function testOutputChanneledWChannelStringNLString() {
+ $this->m->outputChanneled( "foo\nbar", "bazChannel" );
+ $this->assertOutputPrePostShutdown( "foo\nbar", True );
+ }
+
+ function testOutputChanneledWChannelStringString() {
+ $this->m->outputChanneled( "foo", "bazChannel" );
+ $this->m->outputChanneled( "bar", "bazChannel" );
+ $this->assertOutputPrePostShutdown( "foobar", True );
+ }
+
+ function testOutputChanneledWChannelStringNLStringNLArbitraryAgain() {
+ $this->m->outputChanneled( "", "bazChannel" );
+ $this->m->outputChanneled( "foo", "bazChannel" );
+ $this->m->outputChanneled( "", "bazChannel" );
+ $this->m->outputChanneled( "\nb", "bazChannel" );
+ $this->m->outputChanneled( "a", "bazChannel" );
+ $this->m->outputChanneled( "", "bazChannel" );
+ $this->m->outputChanneled( "r", "bazChannel" );
+ $this->assertOutputPrePostShutdown( "foo\nbar", True );
+ }
+
+ function testOutputChanneledWMultipleChannelsChannelChange() {
+ $this->m->outputChanneled( "foo", "bazChannel" );
+ $this->m->outputChanneled( "bar", "bazChannel" );
+ $this->m->outputChanneled( "qux", "quuxChannel" );
+ $this->m->outputChanneled( "corge", "bazChannel" );
+ $this->assertOutputPrePostShutdown( "foobar\nqux\ncorge", True );
+ }
+
+ function testOutputChanneledWMultipleChannelsChannelChangeEnclosedNull() {
+ $this->m->outputChanneled( "foo", "bazChannel" );
+ $this->m->outputChanneled( "bar", null );
+ $this->m->outputChanneled( "qux", null );
+ $this->m->outputChanneled( "corge", "bazChannel" );
+ $this->assertOutputPrePostShutdown( "foo\nbar\nqux\ncorge", True );
+ }
+
+ function testOutputChanneledWMultipleChannelsChannelAfterNullChange() {
+ $this->m->outputChanneled( "foo", "bazChannel" );
+ $this->m->outputChanneled( "bar", null );
+ $this->m->outputChanneled( "qux", null );
+ $this->m->outputChanneled( "corge", "quuxChannel" );
+ $this->assertOutputPrePostShutdown( "foo\nbar\nqux\ncorge", True );
+ }
+
+ function testOutputChanneledWAndWOChannelStringStartWO() {
+ $this->m->outputChanneled( "foo" );
+ $this->m->outputChanneled( "bar", "bazChannel" );
+ $this->m->outputChanneled( "qux" );
+ $this->m->outputChanneled( "quux", "bazChannel" );
+ $this->assertOutputPrePostShutdown( "foo\nbar\nqux\nquux", True );
+ }
+
+ function testOutputChanneledWAndWOChannelStringStartW() {
+ $this->m->outputChanneled( "foo", "bazChannel" );
+ $this->m->outputChanneled( "bar" );
+ $this->m->outputChanneled( "qux", "bazChannel" );
+ $this->m->outputChanneled( "quux" );
+ $this->assertOutputPrePostShutdown( "foo\nbar\nqux\nquux\n", False );
+ }
+
+ function testOutputChanneledWChannelTypeSwitch() {
+ $this->m->outputChanneled( "foo", 1 );
+ $this->m->outputChanneled( "bar", 1.0 );
+ $this->assertOutputPrePostShutdown( "foo\nbar", True );
+ }
+
+ function testOutputChanneledWOChannelIntermittentEmpty() {
+ $this->m->outputChanneled( "foo" );
+ $this->m->outputChanneled( "" );
+ $this->m->outputChanneled( "bar" );
+ $this->assertOutputPrePostShutdown( "foo\n\nbar\n", False );
+ }
+
+ function testOutputChanneledWOChannelIntermittentFalse() {
+ $this->m->outputChanneled( "foo" );
+ $this->m->outputChanneled( false );
+ $this->m->outputChanneled( "bar" );
+ $this->assertOutputPrePostShutdown( "foo\nbar\n", False );
+ }
+
+ function testOutputChanneledWNullChannelIntermittentEmpty() {
+ $this->m->outputChanneled( "foo", null );
+ $this->m->outputChanneled( "", null );
+ $this->m->outputChanneled( "bar", null );
+ $this->assertOutputPrePostShutdown( "foo\n\nbar\n", False );
+ }
+
+ function testOutputChanneledWNullChannelIntermittentFalse() {
+ $this->m->outputChanneled( "foo", null );
+ $this->m->outputChanneled( false, null );
+ $this->m->outputChanneled( "bar", null );
+ $this->assertOutputPrePostShutdown( "foo\nbar\n", False );
+ }
+
+ function testOutputChanneledWChannelIntermittentEmpty() {
+ $this->m->outputChanneled( "foo", "bazChannel" );
+ $this->m->outputChanneled( "", "bazChannel" );
+ $this->m->outputChanneled( "bar", "bazChannel" );
+ $this->assertOutputPrePostShutdown( "foobar", True );
+ }
+
+ function testOutputChanneledWChannelIntermittentFalse() {
+ $this->m->outputChanneled( "foo", "bazChannel" );
+ $this->m->outputChanneled( false, "bazChannel" );
+ $this->m->outputChanneled( "bar", "bazChannel" );
+ $this->assertOutputPrePostShutdown( "foo\nbar", True );
+ }
+
+ function testCleanupChanneledClean() {
+ $this->m->cleanupChanneled();
+ $this->assertOutputPrePostShutdown( "", False );
+ }
+
+ function testCleanupChanneledAfterOutput() {
+ $this->m->output( "foo" );
+ $this->m->cleanupChanneled();
+ $this->assertOutputPrePostShutdown( "foo", False );
+ }
+
+ function testCleanupChanneledAfterOutputWNullChannel() {
+ $this->m->output( "foo", null );
+ $this->m->cleanupChanneled();
+ $this->assertOutputPrePostShutdown( "foo", False );
+ }
+
+ function testCleanupChanneledAfterOutputWChannel() {
+ $this->m->output( "foo", "bazChannel" );
+ $this->m->cleanupChanneled();
+ $this->assertOutputPrePostShutdown( "foo\n", False );
+ }
+
+ function testCleanupChanneledAfterNLOutput() {
+ $this->m->output( "foo\n" );
+ $this->m->cleanupChanneled();
+ $this->assertOutputPrePostShutdown( "foo\n", False );
+ }
+
+ function testCleanupChanneledAfterNLOutputWNullChannel() {
+ $this->m->output( "foo\n", null );
+ $this->m->cleanupChanneled();
+ $this->assertOutputPrePostShutdown( "foo\n", False );
+ }
+
+ function testCleanupChanneledAfterNLOutputWChannel() {
+ $this->m->output( "foo\n", "bazChannel" );
+ $this->m->cleanupChanneled();
+ $this->assertOutputPrePostShutdown( "foo\n", False );
+ }
+
+ function testCleanupChanneledAfterOutputChanneledWOChannel() {
+ $this->m->outputChanneled( "foo" );
+ $this->m->cleanupChanneled();
+ $this->assertOutputPrePostShutdown( "foo\n", False );
+ }
+
+ function testCleanupChanneledAfterOutputChanneledWNullChannel() {
+ $this->m->outputChanneled( "foo", null );
+ $this->m->cleanupChanneled();
+ $this->assertOutputPrePostShutdown( "foo\n", False );
+ }
+
+ function testCleanupChanneledAfterOutputChanneledWChannel() {
+ $this->m->outputChanneled( "foo", "bazChannel" );
+ $this->m->cleanupChanneled();
+ $this->assertOutputPrePostShutdown( "foo\n", False );
+ }
+
+ function testMultipleMaintenanceObjectsInteractionOutput() {
+ $m2 = new MaintenanceFixup( $this );
+
+ $this->m->output( "foo" );
+ $m2->output( "bar" );
+
+ $this->assertEquals( "foobar", $this->getActualOutput(),
+ "Output before shutdown simulation (m2)" );
+ $m2->simulateShutdown();
+ $this->assertOutputPrePostShutdown( "foobar", False );
+ }
+
+ function testMultipleMaintenanceObjectsInteractionOutputWNullChannel() {
+ $m2 = new MaintenanceFixup( $this );
+
+ $this->m->output( "foo", null );
+ $m2->output( "bar", null );
+
+ $this->assertEquals( "foobar", $this->getActualOutput(),
+ "Output before shutdown simulation (m2)" );
+ $m2->simulateShutdown();
+ $this->assertOutputPrePostShutdown( "foobar", False );
+ }
+
+ function testMultipleMaintenanceObjectsInteractionOutputWChannel() {
+ $m2 = new MaintenanceFixup( $this );
+
+ $this->m->output( "foo", "bazChannel" );
+ $m2->output( "bar", "bazChannel" );
+
+ $this->assertEquals( "foobar", $this->getActualOutput(),
+ "Output before shutdown simulation (m2)" );
+ $m2->simulateShutdown();
+ $this->assertOutputPrePostShutdown( "foobar\n", True );
+ }
+
+ function testMultipleMaintenanceObjectsInteractionOutputWNullChannelNL() {
+ $m2 = new MaintenanceFixup( $this );
+
+ $this->m->output( "foo\n", null );
+ $m2->output( "bar\n", null );
+
+ $this->assertEquals( "foo\nbar\n", $this->getActualOutput(),
+ "Output before shutdown simulation (m2)" );
+ $m2->simulateShutdown();
+ $this->assertOutputPrePostShutdown( "foo\nbar\n", False );
+ }
+
+ function testMultipleMaintenanceObjectsInteractionOutputWChannelNL() {
+ $m2 = new MaintenanceFixup( $this );
+
+ $this->m->output( "foo\n", "bazChannel" );
+ $m2->output( "bar\n", "bazChannel" );
+
+ $this->assertEquals( "foobar", $this->getActualOutput(),
+ "Output before shutdown simulation (m2)" );
+ $m2->simulateShutdown();
+ $this->assertOutputPrePostShutdown( "foobar\n", True );
+ }
+
+ function testMultipleMaintenanceObjectsInteractionOutputChanneled() {
+ $m2 = new MaintenanceFixup( $this );
+
+ $this->m->outputChanneled( "foo" );
+ $m2->outputChanneled( "bar" );
+
+ $this->assertEquals( "foo\nbar\n", $this->getActualOutput(),
+ "Output before shutdown simulation (m2)" );
+ $m2->simulateShutdown();
+ $this->assertOutputPrePostShutdown( "foo\nbar\n", False );
+ }
+
+ function testMultipleMaintenanceObjectsInteractionOutputChanneledWNullChannel() {
+ $m2 = new MaintenanceFixup( $this );
+
+ $this->m->outputChanneled( "foo", null );
+ $m2->outputChanneled( "bar", null );
+
+ $this->assertEquals( "foo\nbar\n", $this->getActualOutput(),
+ "Output before shutdown simulation (m2)" );
+ $m2->simulateShutdown();
+ $this->assertOutputPrePostShutdown( "foo\nbar\n", False );
+ }
+
+ function testMultipleMaintenanceObjectsInteractionOutputChanneledWChannel() {
+ $m2 = new MaintenanceFixup( $this );
+
+ $this->m->outputChanneled( "foo", "bazChannel" );
+ $m2->outputChanneled( "bar", "bazChannel" );
+
+ $this->assertEquals( "foobar", $this->getActualOutput(),
+ "Output before shutdown simulation (m2)" );
+ $m2->simulateShutdown();
+ $this->assertOutputPrePostShutdown( "foobar\n", True );
+ }
+
+ function testMultipleMaintenanceObjectsInteractionCleanupChanneledWChannel() {
+ $m2 = new MaintenanceFixup( $this );
+
+ $this->m->outputChanneled( "foo", "bazChannel" );
+ $m2->outputChanneled( "bar", "bazChannel" );
+
+ $this->assertEquals( "foobar", $this->getActualOutput(),
+ "Output before first cleanup" );
+ $this->m->cleanupChanneled();
+ $this->assertEquals( "foobar\n", $this->getActualOutput(),
+ "Output after first cleanup" );
+ $m2->cleanupChanneled();
+ $this->assertEquals( "foobar\n\n", $this->getActualOutput(),
+ "Output after second cleanup" );
+
+ $m2->simulateShutdown();
+ $this->assertOutputPrePostShutdown( "foobar\n\n", False );
+ }
+
+
+}
\ No newline at end of file