merged from master

This commit is contained in:
daniel 2012-04-29 21:41:03 +02:00
commit af28c6f55a
187 changed files with 3867 additions and 833 deletions

View file

@ -291,8 +291,6 @@ production.
array.
* (bug 29753) mw.util.tooltipAccessKeyPrefix should be alt-shift for Chrome
on Windows
* (bug 25095) Special:Categories should also include the first relevant item
when "from" is filled.
* (bug 12262) Indents and lists are now aligned
* (bug 34972) An error occurred while changing your watchlist settings for
[[Special:WhatLinksHere/Example]]

View file

@ -44,6 +44,8 @@ production.
$wgDebugLogFile['memcached'] to some filepath.
* (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.
@ -78,7 +80,7 @@ production.
* (bug 31236) "Next" and "Previous" buttons are shown incorrectly in
an RTL environment.
* (bug 35680) jQuery upgraded to 1.7.2.
* (bug 35681) jQuery UI upgraded to 1.8.18.
* jQuery UI upgraded to 1.8.19.
* (bug 35705) QUnit upgraded from 1.2.0 to 1.5.0
* (bug 35749) Updated maintenance/checkSyntax.php to use Git instead of
Subversion when invoked with the --modified option.
@ -88,6 +90,7 @@ production.
* (bug 33564) transwiki import sometimes result in invalid title.
* (bug 35572) Blocks appear to succeed even if query fails due to wrong DB structure
* (bug 31757) Add a word-separator between help-messages in HTMLForm
* (bug 30410) Removed deprecated $wgFilterCallback and the 'filtered' API error.
=== API changes in 1.20 ===
* (bug 34316) Add ability to retrieve maximum upload size from MediaWiki API.

View file

@ -981,6 +981,12 @@ $article: in case all revisions of the file are deleted a reference to the
$user: user who performed the deletion
$reason: reason
'FileTransformed': When a file is transformed and moved into storage
$file: reference to the File object
$thumb: the MediaTransformOutput object
$tmpThumbPath: The temporary file system path of the transformed file
$thumbPath: The permanent storage path of the transformed file
'FileUpload': When a file upload occurs
$file : Image object representing the file that was uploaded
$reupload : Boolean indicating if there was a previously another image there or not (since 1.17)

View file

@ -525,10 +525,16 @@ $wgAutoloadLocalClasses = array(
'FileBackend' => 'includes/filerepo/backend/FileBackend.php',
'FileBackendStore' => 'includes/filerepo/backend/FileBackendStore.php',
'FileBackendStoreShardListIterator' => 'includes/filerepo/backend/FileBackendStore.php',
'FileBackendStoreShardDirIterator' => 'includes/filerepo/backend/FileBackendStore.php',
'FileBackendStoreShardFileIterator' => 'includes/filerepo/backend/FileBackendStore.php',
'FileBackendMultiWrite' => 'includes/filerepo/backend/FileBackendMultiWrite.php',
'FSFileBackend' => 'includes/filerepo/backend/FSFileBackend.php',
'FSFileBackendList' => 'includes/filerepo/backend/FSFileBackend.php',
'FSFileBackendDirList' => 'includes/filerepo/backend/FSFileBackend.php',
'FSFileBackendFileList' => 'includes/filerepo/backend/FSFileBackend.php',
'SwiftFileBackend' => 'includes/filerepo/backend/SwiftFileBackend.php',
'SwiftFileBackendList' => 'includes/filerepo/backend/SwiftFileBackend.php',
'SwiftFileBackendDirList' => 'includes/filerepo/backend/SwiftFileBackend.php',
'SwiftFileBackendFileList' => 'includes/filerepo/backend/SwiftFileBackend.php',
'FileJournal' => 'includes/filerepo/backend/filejournal/FileJournal.php',
'DBFileJournal' => 'includes/filerepo/backend/filejournal/DBFileJournal.php',

View file

@ -51,13 +51,13 @@ class ChangesFeed {
* @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;

View file

@ -3825,21 +3825,6 @@ $wgSpamRegex = array();
/** Same as the above except for edit summaries */
$wgSummarySpamRegex = array();
/**
* Similarly you can get a function to do the job. The function will be given
* the following args:
* - a Title object for the article the edit is made on
* - the text submitted in the textarea (wpTextbox1)
* - the section number.
* The return should be boolean indicating whether the edit matched some evilness:
* - true : block it
* - false : let it through
*
* @deprecated since 1.17 Use hooks. See SpamBlacklist extension.
* @var $wgFilterCallback bool|string|Closure
*/
$wgFilterCallback = false;
/**
* Whether to use DNS blacklists in $wgDnsBlacklistUrls to check for open proxies
* @since 1.16

View file

@ -36,11 +36,6 @@ class EditPage {
*/
const AS_HOOK_ERROR = 210;
/**
* Status: The filter function set in $wgFilterCallback returned true (= block it)
*/
const AS_FILTERING = 211;
/**
* Status: A hook function returned an error
*/
@ -868,7 +863,7 @@ class EditPage {
# If we just undid one rev, use an autosummary
$firstrev = $oldrev->getNext();
if ( $firstrev->getId() == $undo ) {
if ( $firstrev && $firstrev->getId() == $undo ) {
$undoSummary = wfMsgForContent( 'undo-summary', $undo, $undorev->getUserText() );
if ( $this->summary === '' ) {
$this->summary = $undoSummary;
@ -1090,7 +1085,6 @@ class EditPage {
return true;
case self::AS_HOOK_ERROR:
case self::AS_FILTERING:
return false;
case self::AS_PARSE_ERROR:
@ -1167,8 +1161,7 @@ class EditPage {
* AS_CONTENT_TOO_BIG and AS_BLOCKED_PAGE_FOR_USER. All that stuff needs to be cleaned up some time.
*/
function internalAttemptSave( &$result, $bot = false ) {
global $wgFilterCallback, $wgUser, $wgRequest, $wgParser;
global $wgMaxArticleSize;
global $wgUser, $wgRequest, $wgParser, $wgMaxArticleSize;
$status = Status::newGood();
@ -1214,13 +1207,6 @@ class EditPage {
wfProfileOut( __METHOD__ );
return $status;
}
if ( $wgFilterCallback && is_callable( $wgFilterCallback ) && $wgFilterCallback( $this->mTitle, $this->textbox1, $this->section, $this->hookError, $this->summary ) ) {
# Error messages or other handling should be performed by the filter function
$status->setResult( false, self::AS_FILTERING );
wfProfileOut( __METHOD__ . '-checks' );
wfProfileOut( __METHOD__ );
return $status;
}
if ( !wfRunHooks( 'EditFilter', array( $this, $this->textbox1, $this->section, &$this->hookError, $this->summary ) ) ) {
# Error messages etc. could be handled within the hook...
$status->fatal( 'hookaborted' );
@ -2073,7 +2059,7 @@ class EditPage {
# Optional notices on a per-namespace and per-page basis
$editnotice_ns = 'editnotice-' . $this->mTitle->getNamespace();
$editnotice_ns_message = wfMessage( $editnotice_ns )->inContentLanguage();
$editnotice_ns_message = wfMessage( $editnotice_ns );
if ( $editnotice_ns_message->exists() ) {
$wgOut->addWikiText( $editnotice_ns_message->plain() );
}
@ -2082,7 +2068,7 @@ class EditPage {
$editnotice_base = $editnotice_ns;
while ( count( $parts ) > 0 ) {
$editnotice_base .= '-' . array_shift( $parts );
$editnotice_base_msg = wfMessage( $editnotice_base )->inContentLanguage();
$editnotice_base_msg = wfMessage( $editnotice_base );
if ( $editnotice_base_msg->exists() ) {
$wgOut->addWikiText( $editnotice_base_msg->plain() );
}
@ -2090,7 +2076,7 @@ class EditPage {
} else {
# Even if there are no subpages in namespace, we still don't want / in MW ns.
$editnoticeText = $editnotice_ns . '-' . str_replace( '/', '-', $this->mTitle->getDBkey() );
$editnoticeMsg = wfMessage( $editnoticeText )->inContentLanguage();
$editnoticeMsg = wfMessage( $editnoticeText );
if ( $editnoticeMsg->exists() ) {
$wgOut->addWikiText( $editnoticeMsg->plain() );
}

View file

@ -53,11 +53,11 @@ class MWException extends Exception {
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 ];
@ -74,6 +74,7 @@ class MWException extends Exception {
return $result;
}
}
return null;
}
/**

View file

@ -80,10 +80,16 @@ class ExternalEdit extends ContextSource {
} elseif ( $this->getRequest()->getVal( 'mode' ) == 'file' ) {
$type = "Edit file";
$image = wfLocalFile( $this->getTitle() );
$urls = array( 'File' => array(
'Extension' => $image->getExtension(),
'URL' => $image->getCanonicalURL()
) );
if ( $image ) {
$urls = array(
'File' => array(
'Extension' => $image->getExtension(),
'URL' => $image->getCanonicalURL()
)
);
} else{
$urls = array();
}
} else {
$type = "Edit text";
# *.wiki file extension is used by some editors for syntax

View file

@ -87,7 +87,8 @@ class ImagePage extends Article {
$diffOnly = $wgRequest->getBool( 'diffonly', $wgUser->getOption( 'diffonly' ) );
if ( $this->getTitle()->getNamespace() != NS_FILE || ( isset( $diff ) && $diffOnly ) ) {
return parent::view();
parent::view();
return;
}
$this->loadFile();
@ -97,7 +98,8 @@ class ImagePage extends Article {
// 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

View file

@ -2573,12 +2573,24 @@ $templates
$extraQuery
);
$context = new ResourceLoaderContext( $resourceLoader, new FauxRequest( $query ) );
// Drop modules that know they're empty
// Extract modules that know they're empty
$emptyModules = array ();
foreach ( $modules as $key => $module ) {
if ( $module->isKnownEmpty( $context ) ) {
$emptyModules[$key] = 'ready';
unset( $modules[$key] );
}
}
// Inline empty modules: since they're empty, just mark them as 'ready'
if ( count( $emptyModules ) > 0 && $only !== ResourceLoaderModule::TYPE_STYLES ) {
// If we're only getting the styles, we don't need to do anything for empty modules.
$links .= Html::inlineScript(
ResourceLoader::makeLoaderConditionalScript(
ResourceLoader::makeLoaderStateScript( $emptyModules )
)
) . "\n";
}
// If there are no modules left, skip this group
if ( $modules === array() ) {
continue;
@ -2642,7 +2654,7 @@ $templates
// Automatically select style/script elements
if ( $only === ResourceLoaderModule::TYPE_STYLES ) {
$link = Html::linkedStyle( $url );
} else if ( $loadCall ) {
} else if ( $loadCall ) {
$link = Html::inlineScript(
ResourceLoader::makeLoaderConditionalScript(
Xml::encodeJsCall( 'mw.loader.load', array( $url, 'text/javascript', true ) )
@ -2670,7 +2682,7 @@ $templates
*/
function getHeadScripts() {
global $wgResourceLoaderExperimentalAsyncLoading;
// Startup - this will immediately load jquery and mediawiki modules
$scripts = $this->makeResourceLoaderLink( 'startup', ResourceLoaderModule::TYPE_SCRIPTS, true );
@ -2702,7 +2714,7 @@ $templates
)
);
}
if ( $wgResourceLoaderExperimentalAsyncLoading ) {
$scripts .= $this->getScriptsForBottomQueue( true );
}
@ -2748,43 +2760,87 @@ $templates
// Legacy Scripts
$scripts .= "\n" . $this->mScripts;
$userScripts = array();
$defaultModules = array();
// Add site JS if enabled
if ( $wgUseSiteJs ) {
$scripts .= $this->makeResourceLoaderLink( 'site', ResourceLoaderModule::TYPE_SCRIPTS,
/* $useESI = */ false, /* $extraQuery = */ array(), /* $loadCall = */ $inHead
);
if( $this->getUser()->isLoggedIn() ){
$userScripts[] = 'user.groups';
}
$defaultModules['site'] = 'loading';
} else {
// The wiki is configured to not allow a site module.
$defaultModules['site'] = 'missing';
}
// Add user JS if enabled
if ( $wgAllowUserJs && $this->getUser()->isLoggedIn() ) {
if( $this->getTitle() && $this->getTitle()->isJsSubpage() && $this->userCanPreview() ) {
# XXX: additional security check/prompt?
// We're on a preview of a JS subpage
// Exclude this page from the user module in case it's in there (bug 26283)
$scripts .= $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_SCRIPTS, false,
array( 'excludepage' => $this->getTitle()->getPrefixedDBkey() ), $inHead
);
// Load the previewed JS
$scripts .= Html::inlineScript( "\n" . $this->getRequest()->getText( 'wpTextbox1' ) . "\n" ) . "\n";
if ( $wgAllowUserJs ) {
if ( $this->getUser()->isLoggedIn() ) {
if( $this->getTitle() && $this->getTitle()->isJsSubpage() && $this->userCanPreview() ) {
# XXX: additional security check/prompt?
// We're on a preview of a JS subpage
// Exclude this page from the user module in case it's in there (bug 26283)
$scripts .= $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_SCRIPTS, false,
array( 'excludepage' => $this->getTitle()->getPrefixedDBkey() ), $inHead
);
// Load the previewed JS
$scripts .= Html::inlineScript( "\n" . $this->getRequest()->getText( 'wpTextbox1' ) . "\n" ) . "\n";
// FIXME: If the user is previewing, say, ./vector.js, his ./common.js will be loaded
// asynchronously and may arrive *after* the inline script here. So the previewed code
// may execute before ./common.js runs. Normally, ./common.js runs before ./vector.js...
} else {
// Include the user module normally, i.e., raw to avoid it being wrapped in a closure.
$scripts .= $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_SCRIPTS,
/* $useESI = */ false, /* $extraQuery = */ array(), /* $loadCall = */ $inHead
);
}
$defaultModules['user'] = 'loading';
} else {
// Include the user module normally
// We can't do $userScripts[] = 'user'; because the user module would end up
// being wrapped in a closure, so load it raw like 'site'
$scripts .= $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_SCRIPTS,
// Non-logged-in users have no user module. Treat it as empty and 'ready' to avoid
// blocking default gadgets that might depend on it. Although arguably default-enabled
// gadgets should not depend on the user module, it's harmless and less error-prone to
// handle this case.
$defaultModules['user'] = 'ready';
}
} else {
// User JS disabled
$defaultModules['user'] = 'missing';
}
// Group JS is only enabled if site JS is enabled.
if ( $wgUseSiteJs ) {
if ( $this->getUser()->isLoggedIn() ) {
$scripts .= $this->makeResourceLoaderLink( 'user.groups', ResourceLoaderModule::TYPE_COMBINED,
/* $useESI = */ false, /* $extraQuery = */ array(), /* $loadCall = */ $inHead
);
$defaultModules['user.groups'] = 'loading';
} else {
// Non-logged-in users have no user.groups module. Treat it as empty and 'ready' to
// avoid blocking gadgets that might depend upon the module.
$defaultModules['user.groups'] = 'ready';
}
} else {
// Site (and group JS) disabled
$defaultModules['user.groups'] = 'missing';
}
$loaderInit = '';
if ( $inHead ) {
// We generate loader calls anyway, so no need to fix the client-side loader's state to 'loading'.
foreach ( $defaultModules as $m => $state ) {
if ( $state == 'loading' ) {
unset( $defaultModules[$m] );
}
}
}
$scripts .= $this->makeResourceLoaderLink( $userScripts, ResourceLoaderModule::TYPE_COMBINED,
/* $useESI = */ false, /* $extraQuery = */ array(), /* $loadCall = */ $inHead
);
return $scripts;
if ( count( $defaultModules ) > 0 ) {
$loaderInit = Html::inlineScript(
ResourceLoader::makeLoaderConditionalScript(
ResourceLoader::makeLoaderStateScript( $defaultModules )
)
) . "\n";
}
return $loaderInit . $scripts;
}
/**
@ -2939,12 +2995,11 @@ $templates
}
/**
* @param $unused
* @param $addContentType bool
* @param $addContentType bool: Whether <meta> specifying content type should be returned
*
* @return string HTML tag links to be put in the header.
* @return array in format "link name or number => 'link html'".
*/
public function getHeadLinks( $unused = null, $addContentType = false ) {
public function getHeadLinksArray( $addContentType = false ) {
global $wgUniversalEditButton, $wgFavicon, $wgAppleTouchIcon, $wgEnableAPI,
$wgSitename, $wgVersion, $wgHtml5, $wgMimeType,
$wgFeed, $wgOverrideSiteFeed, $wgAdvertisedFeedTypes,
@ -2957,20 +3012,20 @@ $templates
if ( $wgHtml5 ) {
# More succinct than <meta http-equiv=Content-Type>, has the
# same effect
$tags[] = Html::element( 'meta', array( 'charset' => 'UTF-8' ) );
$tags['meta-charset'] = Html::element( 'meta', array( 'charset' => 'UTF-8' ) );
} else {
$tags[] = Html::element( 'meta', array(
$tags['meta-content-type'] = Html::element( 'meta', array(
'http-equiv' => 'Content-Type',
'content' => "$wgMimeType; charset=UTF-8"
) );
$tags[] = Html::element( 'meta', array( // bug 15835
$tags['meta-content-style-type'] = Html::element( 'meta', array( // bug 15835
'http-equiv' => 'Content-Style-Type',
'content' => 'text/css'
) );
}
}
$tags[] = Html::element( 'meta', array(
$tags['meta-generator'] = Html::element( 'meta', array(
'name' => 'generator',
'content' => "MediaWiki $wgVersion",
) );
@ -2979,7 +3034,7 @@ $templates
if( $p !== 'index,follow' ) {
// http://www.robotstxt.org/wc/meta-user.html
// Only show if it's different from the default robots policy
$tags[] = Html::element( 'meta', array(
$tags['meta-robots'] = Html::element( 'meta', array(
'name' => 'robots',
'content' => $p,
) );
@ -2990,7 +3045,7 @@ $templates
"/<.*?" . ">/" => '',
"/_/" => ' '
);
$tags[] = Html::element( 'meta', array(
$tags['meta-keywords'] = Html::element( 'meta', array(
'name' => 'keywords',
'content' => preg_replace(
array_keys( $strip ),
@ -3007,7 +3062,11 @@ $templates
} else {
$a = 'name';
}
$tags[] = Html::element( 'meta',
$tagName = "meta-{$tag[0]}";
if ( isset( $tags[$tagName] ) ) {
$tagName .= $tag[1];
}
$tags[$tagName] = Html::element( 'meta',
array(
$a => $tag[0],
'content' => $tag[1]
@ -3026,14 +3085,14 @@ $templates
&& ( $this->getTitle()->exists() || $this->getTitle()->quickUserCan( 'create', $user ) ) ) {
// Original UniversalEditButton
$msg = $this->msg( 'edit' )->text();
$tags[] = Html::element( 'link', array(
$tags['universal-edit-button'] = Html::element( 'link', array(
'rel' => 'alternate',
'type' => 'application/x-wiki',
'title' => $msg,
'href' => $this->getTitle()->getLocalURL( 'action=edit' )
) );
// Alternate edit link
$tags[] = Html::element( 'link', array(
$tags['alternative-edit'] = Html::element( 'link', array(
'rel' => 'edit',
'title' => $msg,
'href' => $this->getTitle()->getLocalURL( 'action=edit' )
@ -3046,15 +3105,15 @@ $templates
# uses whichever one appears later in the HTML source. Make sure
# apple-touch-icon is specified first to avoid this.
if ( $wgAppleTouchIcon !== false ) {
$tags[] = Html::element( 'link', array( 'rel' => 'apple-touch-icon', 'href' => $wgAppleTouchIcon ) );
$tags['apple-touch-icon'] = Html::element( 'link', array( 'rel' => 'apple-touch-icon', 'href' => $wgAppleTouchIcon ) );
}
if ( $wgFavicon !== false ) {
$tags[] = Html::element( 'link', array( 'rel' => 'shortcut icon', 'href' => $wgFavicon ) );
$tags['favicon'] = Html::element( 'link', array( 'rel' => 'shortcut icon', 'href' => $wgFavicon ) );
}
# OpenSearch description link
$tags[] = Html::element( 'link', array(
$tags['opensearch'] = Html::element( 'link', array(
'rel' => 'search',
'type' => 'application/opensearchdescription+xml',
'href' => wfScript( 'opensearch_desc' ),
@ -3066,7 +3125,7 @@ $templates
# for the MediaWiki API (and potentially additional custom API
# support such as WordPress or Twitter-compatible APIs for a
# blogging extension, etc)
$tags[] = Html::element( 'link', array(
$tags['rsd'] = Html::element( 'link', array(
'rel' => 'EditURI',
'type' => 'application/rsd+xml',
// Output a protocol-relative URL here if $wgServer is protocol-relative
@ -3086,14 +3145,14 @@ $templates
if ( !$urlvar ) {
$variants = $lang->getVariants();
foreach ( $variants as $_v ) {
$tags[] = Html::element( 'link', array(
$tags["variant-$_v"] = Html::element( 'link', array(
'rel' => 'alternate',
'hreflang' => $_v,
'href' => $this->getTitle()->getLocalURL( array( 'variant' => $_v ) ) )
);
}
} else {
$tags[] = Html::element( 'link', array(
$tags['canonical'] = Html::element( 'link', array(
'rel' => 'canonical',
'href' => $this->getTitle()->getCanonicalUrl()
) );
@ -3116,7 +3175,7 @@ $templates
}
if ( $copyright ) {
$tags[] = Html::element( 'link', array(
$tags['copyright'] = Html::element( 'link', array(
'rel' => 'copyright',
'href' => $copyright )
);
@ -3165,7 +3224,17 @@ $templates
}
}
}
return implode( "\n", $tags );
return $tags;
}
/**
* @param $unused
* @param $addContentType bool: Whether <meta> specifying content type should be returned
*
* @return string HTML tag links to be put in the header.
*/
public function getHeadLinks( $unused = null, $addContentType = false ) {
return implode( "\n", $this->getHeadLinksArray( $addContentType ) );
}
/**
@ -3260,7 +3329,7 @@ $templates
$otherTags .= $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_STYLES, false,
array( 'excludepage' => $this->getTitle()->getPrefixedDBkey() )
);
// Load the previewed CSS
// If needed, Janus it first. This is user-supplied CSS, so it's
// assumed to be right for the content language directionality.

View file

@ -740,17 +740,9 @@ class Title {
}
}
// Strip off subpages
$pagename = $this->getText();
if ( strpos( $pagename, '/' ) !== false ) {
list( $username , ) = explode( '/', $pagename, 2 );
} else {
$username = $pagename;
}
if ( $wgContLang->needsGenderDistinction() &&
MWNamespace::hasGenderDistinction( $this->mNamespace ) ) {
$gender = GenderCache::singleton()->getGenderOf( $username, __METHOD__ );
$gender = GenderCache::singleton()->getGenderOf( $this->getText(), __METHOD__ );
return $wgContLang->getGenderNsText( $this->mNamespace, $gender );
}

View file

@ -121,11 +121,26 @@ class WikiPage extends Page {
* @return WikiPage|null
*/
public static function newFromID( $id ) {
$t = Title::newFromID( $id );
if ( $t ) {
return self::factory( $t );
$dbr = wfGetDB( DB_SLAVE );
$row = $dbr->selectRow( 'page', self::selectFields(), array( 'page_id' => $id ), __METHOD__ );
if ( !$row ) {
return null;
}
return null;
return self::newFromRow( $row );
}
/**
* Constructor from a database row
*
* @since 1.20
* @param $row object: database row containing at least fields returned
* by selectFields().
* @return WikiPage
*/
public static function newFromRow( $row ) {
$page = self::factory( Title::newFromRow( $row ) );
$page->loadFromRow( $row );
return $page;
}
/**
@ -271,6 +286,17 @@ class WikiPage extends Page {
}
}
$this->loadFromRow( $data );
}
/**
* Load the object from a database row
*
* @since 1.20
* @param $data object: database row containing at least fields returned
* by selectFields()
*/
public function loadFromRow( $data ) {
$lc = LinkCache::singleton();
if ( $data ) {

View file

@ -698,6 +698,35 @@ abstract class ApiBase extends ContextSource {
);
}
/**
* @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
*
@ -1215,7 +1244,6 @@ abstract class ApiBase extends ContextSource {
'noimageredirect-anon' => array( 'code' => 'noimageredirect-anon', 'info' => "Anonymous users can't create image redirects" ),
'noimageredirect-logged' => array( 'code' => 'noimageredirect', 'info' => "You don't have permission to create image redirects" ),
'spamdetected' => array( 'code' => 'spamdetected', 'info' => "Your edit was refused because it contained a spam fragment: \"\$1\"" ),
'filtered' => array( 'code' => 'filtered', 'info' => "The filter callback function refused your edit" ),
'contenttoobig' => array( 'code' => 'contenttoobig', 'info' => "The content you supplied exceeds the article size limit of \$1 kilobytes" ),
'noedit-anon' => array( 'code' => 'noedit-anon', 'info' => "Anonymous users can't edit pages" ),
'noedit' => array( 'code' => 'noedit', 'info' => "You don't have permission to edit pages" ),

View file

@ -46,24 +46,11 @@ class ApiDelete extends ApiBase {
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 );
@ -241,7 +228,7 @@ class ApiDelete extends ApiBase {
public function getPossibleErrors() {
return array_merge( parent::getPossibleErrors(),
$this->getRequireOnlyOneParameterErrorMessages( array( 'title', 'pageid' ) ),
$this->getTitleOrPageIdErrorMessage(),
array(
array( 'invalidtitle', 'title' ),
array( 'nosuchpageid', 'pageid' ),

View file

@ -48,18 +48,9 @@ class ApiEditPage extends ApiBase {
$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();
@ -295,9 +286,6 @@ class ApiEditPage extends ApiBase {
case EditPage::AS_SPAM_ERROR:
$this->dieUsageMsg( array( 'spamdetected', $result['spam'] ) );
case EditPage::AS_FILTERING:
$this->dieUsageMsg( 'filtered' );
case EditPage::AS_BLOCKED_PAGE_FOR_USER:
$this->dieUsageMsg( 'blockedtext' );
@ -385,7 +373,7 @@ class ApiEditPage extends ApiBase {
global $wgMaxArticleSize;
return array_merge( parent::getPossibleErrors(),
$this->getRequireOnlyOneParameterErrorMessages( array( 'title', 'pageid' ) ),
$this->getTitleOrPageIdErrorMessage(),
array(
array( 'nosuchpageid', 'pageid' ),
array( 'missingtext' ),
@ -402,7 +390,6 @@ class ApiEditPage extends ApiBase {
array( 'noimageredirect-logged' ),
array( 'spamdetected', 'spam' ),
array( 'summaryrequired' ),
array( 'filtered' ),
array( 'blockedtext' ),
array( 'contenttoobig', $wgMaxArticleSize ),
array( 'noedit-anon' ),
@ -488,14 +475,14 @@ class ApiEditPage extends ApiBase {
'sectiontitle' => 'The title for a new section',
'text' => 'Page content',
'token' => array( 'Edit token. You can get one of these through prop=info.',
'The token should always be sent as the last parameter, or at least, after the text parameter'
"The token should always be sent as the last parameter, or at least, after the {$p}text parameter"
),
'summary' => 'Edit summary. Also section title when section=new',
'summary' => "Edit summary. Also section title when {$p}section=new and {$p}sectiontitle is not set",
'minor' => 'Minor edit',
'notminor' => 'Non-minor edit',
'bot' => 'Mark this edit as bot',
'basetimestamp' => array( 'Timestamp of the base revision (obtained through prop=revisions&rvprop=timestamp).',
'Used to detect edit conflicts; leave unset to ignore conflicts.'
'Used to detect edit conflicts; leave unset to ignore conflicts'
),
'starttimestamp' => array( 'Timestamp when you obtained the edit token.',
'Used to detect edit conflicts; leave unset to ignore conflicts'
@ -509,7 +496,8 @@ class ApiEditPage extends ApiBase {
'md5' => array( "The MD5 hash of the {$p}text parameter, or the {$p}prependtext and {$p}appendtext parameters concatenated.",
'If set, the edit won\'t be done unless the hash is correct' ),
'prependtext' => "Add this text to the beginning of the page. Overrides {$p}text",
'appendtext' => "Add this text to the end of the page. Overrides {$p}text",
'appendtext' => array( "Add this text to the end of the page. Overrides {$p}text.",
"Use {$p}section=new to append a new section" ),
'undo' => "Undo this revision. Overrides {$p}text, {$p}prependtext and {$p}appendtext",
'undoafter' => 'Undo all revisions from undo to this one. If not set, just undo one revision',
'redirect' => 'Automatically resolve redirects',

View file

@ -55,7 +55,7 @@ class ApiEmailUser extends ApiBase {
'Subject' => $params['subject'],
'CCMe' => $params['ccme'],
);
$retval = SpecialEmailUser::submit( $data );
$retval = SpecialEmailUser::submit( $data, $this->getContext() );
if ( $retval instanceof Status ) {
// SpecialEmailUser sometimes returns a status

View file

@ -328,6 +328,9 @@ class ApiParse extends ApiBase {
// Try the parser cache first
// getParserOutput will save to Parser cache if able
$pout = $page->getParserOutput( $popts );
if ( !$pout ) {
$this->dieUsage( "There is no revision ID {$page->getLatest()}", 'missingrev' );
}
if ( $getWikitext ) {
$this->content = $page->getContent( Revision::RAW ); #FIXME: use $this->content everywhere
$this->text = ContentHandler::getContentText( $this->content ); #FIXME: serialize, get format from params; or use object structure in result?

View file

@ -37,19 +37,8 @@ class ApiProtect extends ApiBase {
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'] ) );
}
} elseif ( isset( $params['pageid'] ) ) {
$titleObj = Title::newFromID( $params['pageid'] );
if ( !$titleObj ) {
$this->dieUsageMsg( array( 'nosuchpageid', $params['pageid'] ) );
}
}
$titleObj = $this->getTitleOrPageId( $params );
$pageObj = WikiPage::factory( $titleObj );
$errors = $titleObj->getUserPermissionsErrors( 'protect', $this->getUser() );
if ( $errors ) {
@ -115,7 +104,6 @@ class ApiProtect extends ApiBase {
$watch = $params['watch'] ? 'watch' : $params['watchlist'];
$this->setWatch( $watch, $titleObj );
$pageObj = WikiPage::factory( $titleObj );
$status = $pageObj->doUpdateRestrictions( $protections, $expiryarray, $cascade, $params['reason'], $this->getUser() );
if ( !$status->isOK() ) {
@ -202,7 +190,7 @@ class ApiProtect extends ApiBase {
public function getPossibleErrors() {
return array_merge( parent::getPossibleErrors(),
$this->getRequireOnlyOneParameterErrorMessages( array( 'title', 'pageid' ) ),
$this->getTitleOrPageIdErrorMessage(),
array(
array( 'invalidtitle', 'title' ),
array( 'nosuchpageid', 'pageid' ),

View file

@ -34,6 +34,16 @@ class ApiQueryAllUsers extends ApiQueryBase {
parent::__construct( $query, $moduleName, 'au' );
}
/**
* This function converts the user name to a canonical form
* which is stored in the database.
* @param String $name
* @return String
*/
private function getCanonicalUserName( $name ) {
return str_replace( '_', ' ', $name );
}
public function execute() {
$db = $this->getDB();
$params = $this->extractRequestParams();
@ -57,8 +67,8 @@ class ApiQueryAllUsers extends ApiQueryBase {
$useIndex = true;
$dir = ( $params['dir'] == 'descending' ? 'older' : 'newer' );
$from = is_null( $params['from'] ) ? null : $this->keyToTitle( $params['from'] );
$to = is_null( $params['to'] ) ? null : $this->keyToTitle( $params['to'] );
$from = is_null( $params['from'] ) ? null : $this->getCanonicalUserName( $params['from'] );
$to = is_null( $params['to'] ) ? null : $this->getCanonicalUserName( $params['to'] );
# MySQL doesn't seem to use 'equality propagation' here, so like the
# ActiveUsers special page, we have to use rc_user_text for some cases.
@ -68,7 +78,7 @@ class ApiQueryAllUsers extends ApiQueryBase {
if ( !is_null( $params['prefix'] ) ) {
$this->addWhere( $userFieldToSort .
$db->buildLike( $this->keyToTitle( $params['prefix'] ), $db->anyString() ) );
$db->buildLike( $this->getCanonicalUserName( $params['prefix'] ), $db->anyString() ) );
}
if ( !is_null( $params['rights'] ) ) {
@ -190,15 +200,14 @@ class ApiQueryAllUsers extends ApiQueryBase {
$lastUserData = null;
if ( !$fit ) {
$this->setContinueEnumParameter( 'from',
$this->keyToTitle( $lastUserData['name'] ) );
$this->setContinueEnumParameter( 'from', $lastUserData['name'] );
break;
}
}
if ( $count > $limit ) {
// We've reached the one extra which shows that there are additional pages to be had. Stop here...
$this->setContinueEnumParameter( 'from', $this->keyToTitle( $row->user_name ) );
$this->setContinueEnumParameter( 'from', $row->user_name );
break;
}
@ -235,17 +244,23 @@ class ApiQueryAllUsers extends ApiQueryBase {
'MediaWiki configuration error: the database contains more user groups than known to User::getAllGroups() function' );
}
$lastUserObj = User::newFromName( $lastUser );
$lastUserObj = User::newFromId( $row->user_id );
// Add user's group info
if ( $fld_groups ) {
if ( !isset( $lastUserData['groups'] ) && $lastUserObj ) {
$lastUserData['groups'] = ApiQueryUsers::getAutoGroups( $lastUserObj );
if ( !isset( $lastUserData['groups'] ) ) {
if ( $lastUserObj ) {
$lastUserData['groups'] = ApiQueryUsers::getAutoGroups( $lastUserObj );
} else {
// This should not normally happen
$lastUserData['groups'] = array();
}
}
if ( !is_null( $row->ug_group2 ) ) {
$lastUserData['groups'][] = $row->ug_group2;
}
$result->setIndexedTagName( $lastUserData['groups'], 'g' );
}
@ -254,13 +269,20 @@ class ApiQueryAllUsers extends ApiQueryBase {
$result->setIndexedTagName( $lastUserData['implicitgroups'], 'g' );
}
if ( $fld_rights ) {
if ( !isset( $lastUserData['rights'] ) && $lastUserObj ) {
$lastUserData['rights'] = User::getGroupPermissions( $lastUserObj->getAutomaticGroups() );
if ( !isset( $lastUserData['rights'] ) ) {
if ( $lastUserObj ) {
$lastUserData['rights'] = User::getGroupPermissions( $lastUserObj->getAutomaticGroups() );
} else {
// This should not normally happen
$lastUserData['rights'] = array();
}
}
if ( !is_null( $row->ug_group2 ) ) {
$lastUserData['rights'] = array_unique( array_merge( $lastUserData['rights'],
User::getGroupPermissions( array( $row->ug_group2 ) ) ) );
}
$result->setIndexedTagName( $lastUserData['rights'], 'r' );
}
}
@ -269,8 +291,7 @@ class ApiQueryAllUsers extends ApiQueryBase {
$fit = $result->addValue( array( 'query', $this->getModuleName() ),
null, $lastUserData );
if ( !$fit ) {
$this->setContinueEnumParameter( 'from',
$this->keyToTitle( $lastUserData['name'] ) );
$this->setContinueEnumParameter( 'from', $lastUserData['name'] );
}
}

View file

@ -54,22 +54,9 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
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'] );
@ -383,7 +370,7 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
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' ),

View file

@ -173,7 +173,7 @@ class ApiUpload extends ApiBase {
*/
private function getChunkResult(){
$result = array();
$result['result'] = 'Continue';
$request = $this->getMain()->getRequest();
$chunkPath = $request->getFileTempname( 'chunk' );
@ -185,7 +185,7 @@ class ApiUpload extends ApiBase {
$this->mParams['offset']);
if ( !$status->isGood() ) {
$this->dieUsage( $status->getWikiText(), 'stashfailed' );
return ;
return array();
}
// Check we added the last chunk:
@ -194,7 +194,7 @@ class ApiUpload extends ApiBase {
if ( !$status->isGood() ) {
$this->dieUsage( $status->getWikiText(), 'stashfailed' );
return ;
return array();
}
// We have a new filekey for the fully concatenated file.

View file

@ -48,7 +48,7 @@ class GenderCache {
$username = $username->getName();
}
$username = strtr( $username, '_', ' ' );
$username = self::normalizeUsername( $username );
if ( !isset( $this->cache[$username] ) ) {
if ( $this->misses >= $this->missLimit && $wgUser->getName() !== $username ) {
@ -60,11 +60,7 @@ class GenderCache {
} else {
$this->misses++;
if ( !User::isValidUserName( $username ) ) {
$this->cache[$username] = $this->getDefault();
} else {
$this->doQuery( $username, $caller );
}
$this->doQuery( $username, $caller );
}
}
@ -86,7 +82,6 @@ class GenderCache {
foreach ( $data as $ns => $pagenames ) {
if ( !MWNamespace::hasGenderDistinction( $ns ) ) continue;
foreach ( array_keys( $pagenames ) as $username ) {
if ( isset( $this->cache[$username] ) ) continue;
$users[$username] = true;
}
}
@ -102,26 +97,28 @@ class GenderCache {
public function doQuery( $users, $caller = '' ) {
$default = $this->getDefault();
foreach ( (array) $users as $index => $value ) {
$name = strtr( $value, '_', ' ' );
if ( isset( $this->cache[$name] ) ) {
// Skip users whose gender setting we already know
unset( $users[$index] );
} else {
$users[$index] = $name;
$usersToCheck = array();
foreach ( (array) $users as $value ) {
$name = self::normalizeUsername( $value );
// Skip users whose gender setting we already know
if ( !isset( $this->cache[$name] ) ) {
// For existing users, this value will be overwritten by the correct value
$this->cache[$name] = $default;
// query only for valid names, which can be in the database
if( User::isValidUserName( $name ) ) {
$usersToCheck[] = $name;
}
}
}
if ( count( $users ) === 0 ) {
if ( count( $usersToCheck ) === 0 ) {
return;
}
$dbr = wfGetDB( DB_SLAVE );
$table = array( 'user', 'user_properties' );
$fields = array( 'user_name', 'up_value' );
$conds = array( 'user_name' => $users );
$conds = array( 'user_name' => $usersToCheck );
$joins = array( 'user_properties' =>
array( 'LEFT JOIN', array( 'user_id = up_user', 'up_property' => 'gender' ) ) );
@ -129,11 +126,20 @@ class GenderCache {
if ( strval( $caller ) !== '' ) {
$comment .= "/$caller";
}
$res = $dbr->select( $table, $fields, $conds, $comment, $joins, $joins );
$res = $dbr->select( $table, $fields, $conds, $comment, array(), $joins );
foreach ( $res as $row ) {
$this->cache[$row->user_name] = $row->up_value ? $row->up_value : $default;
}
}
private static function normalizeUsername( $username ) {
// Strip off subpages
$indexSlash = strpos( $username, '/' );
if ( $indexSlash !== false ) {
$username = substr( $username, 0, $indexSlash );
}
// normalize underscore/spaces
return strtr( $username, '_', ' ' );
}
}

View file

@ -195,7 +195,7 @@ class LinkBatch {
}
$genderCache = GenderCache::singleton();
$genderCache->dolinkBatch( $this->data, $this->caller );
$genderCache->doLinkBatch( $this->data, $this->caller );
return true;
}

View file

@ -20,6 +20,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
* @ingroup Database
*/

View file

@ -2,10 +2,26 @@
/**
* @defgroup Database Database
*
* This file deals with database interface functions
* and query specifics/optimisations.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
* @ingroup Database
* This file deals with database interface functions
* and query specifics/optimisations
*/
/** Number of times to re-try an operation in case of deadlock */

View file

@ -1,4 +1,25 @@
<?php
/**
* This file contains database error classes.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
* @ingroup Database
*/
/**
* Database error base class

View file

@ -2,7 +2,22 @@
/**
* This is the IBM DB2 database abstraction layer.
* See maintenance/ibm_db2/README for development notes
* and other specific information
* and other specific information.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
* @ingroup Database

View file

@ -2,6 +2,21 @@
/**
* This is the MS SQL Server Native database abstraction layer.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
* @ingroup Database
* @author Joel Penner <a-joelpe at microsoft dot com>

View file

@ -2,6 +2,21 @@
/**
* This is the MySQL database abstraction layer.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
* @ingroup Database
*/

View file

@ -2,6 +2,21 @@
/**
* This is the Oracle database abstraction layer.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
* @ingroup Database
*/

View file

@ -2,6 +2,21 @@
/**
* This is the Postgres database abstraction layer.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
* @ingroup Database
*/

View file

@ -3,6 +3,21 @@
* This is the SQLite database abstraction layer.
* See maintenance/sqlite/README for development notes and other specific information
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
* @ingroup Database
*/

View file

@ -1,4 +1,26 @@
<?php
/**
* This file contains database-related utiliy classes.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
* @ingroup Database
*/
/**
* Utility class.
* @ingroup Database

View file

@ -1,6 +1,21 @@
<?php
/**
* Generator of database load balancing objects
* Generator of database load balancing objects.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
* @ingroup Database

View file

@ -1,6 +1,21 @@
<?php
/**
* Advanced generator of database load balancing objects for wiki farms
* Advanced generator of database load balancing objects for wiki farms.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
* @ingroup Database

View file

@ -1,4 +1,25 @@
<?php
/**
* Simple generator of database connections that always returns the same object.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
* @ingroup Database
*/
/**
* An LBFactory class that always returns a single database object.

View file

@ -1,6 +1,21 @@
<?php
/**
* Database load balancing
* Database load balancing.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
* @ingroup Database

View file

@ -1,6 +1,21 @@
<?php
/**
* Database load monitoring
* Database load monitoring.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
* @ingroup Database

View file

@ -1,8 +1,22 @@
<?php
/**
* Result of a ORMTable::select, which returns ORMRow objects.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @since 1.20
*
* @file ORMResult.php
@ -10,6 +24,7 @@
* @licence GNU GPL v2 or later
* @author Jeroen De Dauw < jeroendedauw@gmail.com >
*/
class ORMResult implements Iterator {
/**

View file

@ -1,11 +1,25 @@
<?php
/**
* Abstract base class for representing objects that are stored in some DB table.
* This is basically an ORM-like wrapper around rows in database tables that
* aims to be both simple and very flexible. It is centered around an associative
* array of fields and various methods to do common interaction with the database.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* These methods are likely candidates for overriding:
* * getDefaults
* * remove
@ -37,6 +51,7 @@
* @licence GNU GPL v2 or later
* @author Jeroen De Dauw < jeroendedauw@gmail.com >
*/
abstract class ORMRow {
/**

View file

@ -1,8 +1,22 @@
<?php
/**
* Abstract base class for representing a single database table.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @since 1.20
*
* @file ORMTable.php
@ -10,6 +24,7 @@
* @licence GNU GPL v2 or later
* @author Jeroen De Dauw < jeroendedauw@gmail.com >
*/
abstract class ORMTable {
/**

View file

@ -1,4 +1,24 @@
<?php
/**
* Debug toolbar related code
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
*/
/**
* New debugger system that outputs a toolbar on page view

View file

@ -782,6 +782,7 @@ class FileRepo {
/**
* Import a file from the local file system into the repo.
* This does no locking nor journaling and overrides existing files.
* This function can be used to write to otherwise read-only foreign repos.
* This is intended for copying generated thumbnails into the repo.
*
* @param $src string File system path
@ -794,7 +795,8 @@ class FileRepo {
/**
* Purge a file from the repo. This does no locking nor journaling.
* This is intended for purging thumbnail.
* This function can be used to write to otherwise read-only foreign repos.
* This is intended for purging thumbnails.
*
* @param $path string Virtual URL or storage path
* @return FileRepoStatus
@ -803,17 +805,31 @@ class FileRepo {
return $this->quickPurgeBatch( array( $path ) );
}
/**
* Deletes a directory if empty.
* This function can be used to write to otherwise read-only foreign repos.
*
* @param $dir string Virtual URL (or storage path) of directory to clean
* @return Status
*/
public function quickCleanDir( $dir ) {
$status = $this->newGood();
$status->merge( $this->backend->clean(
array( 'dir' => $this->resolveToStoragePath( $dir ) ) ) );
return $status;
}
/**
* Import a batch of files from the local file system into the repo.
* This does no locking nor journaling and overrides existing files.
* This function can be used to write to otherwise read-only foreign repos.
* This is intended for copying generated thumbnails into the repo.
*
* @param $src Array List of tuples (file system path, virtual URL or storage path)
* @return FileRepoStatus
*/
public function quickImportBatch( array $pairs ) {
$this->assertWritableRepo(); // fail out if read-only
$status = $this->newGood();
$operations = array();
foreach ( $pairs as $pair ) {
@ -834,15 +850,14 @@ class FileRepo {
}
/**
* Purge a batch of files from the repo. This does no locking nor journaling.
* This is intended for purging thumbnails.
* Purge a batch of files from the repo.
* This function can be used to write to otherwise read-only foreign repos.
* This does no locking nor journaling and is intended for purging thumbnails.
*
* @param $path Array List of virtual URLs or storage paths
* @return FileRepoStatus
*/
public function quickPurgeBatch( array $paths ) {
$this->assertWritableRepo(); // fail out if read-only
$status = $this->newGood();
$operations = array();
foreach ( $paths as $path ) {
@ -1087,7 +1102,7 @@ class FileRepo {
}
/**
* Deletes a directory if empty
* Deletes a directory if empty.
*
* @param $dir string Virtual URL (or storage path) of directory to clean
* @return Status

View file

@ -7,11 +7,11 @@
/**
* @brief Class for a file system (FS) based file backend.
*
*
* All "containers" each map to a directory under the backend's base directory.
* For backwards-compatibility, some container paths can be set to custom paths.
* The wiki ID will not be used in any custom paths, so this should be avoided.
*
*
* Having directories with thousands of files will diminish performance.
* Sharding can be accomplished by using FileRepo-style hash paths.
*
@ -76,7 +76,7 @@ class FSFileBackend extends FileBackendStore {
/**
* Sanity check a relative file system path for validity
*
*
* @param $path string Normalized relative path
* @return bool
*/
@ -95,14 +95,14 @@ class FSFileBackend extends FileBackendStore {
/**
* Given the short (unresolved) and full (resolved) name of
* a container, return the file system path of the container.
*
*
* @param $shortCont string
* @param $fullCont string
* @return string|null
* @return string|null
*/
protected function containerFSRoot( $shortCont, $fullCont ) {
if ( isset( $this->containerPaths[$shortCont] ) ) {
return $this->containerPaths[$shortCont];
return $this->containerPaths[$shortCont];
} elseif ( isset( $this->basePath ) ) {
return "{$this->basePath}/{$fullCont}";
}
@ -111,7 +111,7 @@ class FSFileBackend extends FileBackendStore {
/**
* Get the absolute file system path for a storage path
*
*
* @param $storagePath string Storage path
* @return string|null
*/
@ -439,6 +439,41 @@ class FSFileBackend extends FileBackendStore {
clearstatcache(); // clear the PHP file stat cache
}
/**
* @see FileBackendStore::doDirectoryExists()
* @return bool|null
*/
protected function doDirectoryExists( $fullCont, $dirRel, array $params ) {
list( $b, $shortCont, $r ) = FileBackend::splitStoragePath( $params['dir'] );
$contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
$dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
$this->trapWarnings(); // don't trust 'false' if there were errors
$exists = is_dir( $dir );
$hadError = $this->untrapWarnings();
return $hadError ? null : $exists;
}
/**
* @see FileBackendStore::getDirectoryListInternal()
* @return Array|null
*/
public function getDirectoryListInternal( $fullCont, $dirRel, array $params ) {
list( $b, $shortCont, $r ) = FileBackend::splitStoragePath( $params['dir'] );
$contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
$dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
$exists = is_dir( $dir );
if ( !$exists ) {
wfDebug( __METHOD__ . "() given directory does not exist: '$dir'\n" );
return array(); // nothing under this dir
} elseif ( !is_readable( $dir ) ) {
wfDebug( __METHOD__ . "() given directory is unreadable: '$dir'\n" );
return null; // bad permissions?
}
return new FSFileBackendDirList( $dir, $params );
}
/**
* @see FileBackendStore::getFileListInternal()
* @return array|FSFileBackendFileList|null
@ -451,13 +486,11 @@ class FSFileBackend extends FileBackendStore {
if ( !$exists ) {
wfDebug( __METHOD__ . "() given directory does not exist: '$dir'\n" );
return array(); // nothing under this dir
}
$readable = is_readable( $dir );
if ( !$readable ) {
} elseif ( !is_readable( $dir ) ) {
wfDebug( __METHOD__ . "() given directory is unreadable: '$dir'\n" );
return null; // bad permissions?
}
return new FSFileBackendFileList( $dir );
return new FSFileBackendFileList( $dir, $params );
}
/**
@ -501,6 +534,14 @@ class FSFileBackend extends FileBackendStore {
return $tmpFile;
}
/**
* @see FileBackendStore::directoriesAreVirtual()
* @return bool
*/
protected function directoriesAreVirtual() {
return false;
}
/**
* Chmod a file, suppressing the warnings
*
@ -543,51 +584,63 @@ class FSFileBackend extends FileBackendStore {
}
/**
* Wrapper around RecursiveDirectoryIterator that catches
* exception or does any custom behavoir that we may want.
* Wrapper around RecursiveDirectoryIterator/DirectoryIterator that
* catches exception or does any custom behavoir that we may want.
* Do not use this class from places outside FSFileBackend.
*
* @ingroup FileBackend
*/
class FSFileBackendFileList implements Iterator {
/** @var RecursiveIteratorIterator */
abstract class FSFileBackendList implements Iterator {
/** @var Iterator */
protected $iter;
protected $suffixStart; // integer
protected $pos = 0; // integer
/** @var Array */
protected $params = array();
/**
* @param $dir string file system directory
*/
public function __construct( $dir ) {
public function __construct( $dir, array $params ) {
$dir = realpath( $dir ); // normalize
$this->suffixStart = strlen( $dir ) + 1; // size of "path/to/dir/"
$this->params = $params;
try {
# Get an iterator that will return leaf nodes (non-directories)
if ( MWInit::classExists( 'FilesystemIterator' ) ) { // PHP >= 5.3
# RecursiveDirectoryIterator extends FilesystemIterator.
# FilesystemIterator::SKIP_DOTS default is inconsistent in PHP 5.3.x.
$flags = FilesystemIterator::CURRENT_AS_FILEINFO | FilesystemIterator::SKIP_DOTS;
$this->iter = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator( $dir, $flags ) );
} else { // PHP < 5.3
# RecursiveDirectoryIterator extends DirectoryIterator
$this->iter = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator( $dir ) );
}
$this->iter = $this->initIterator( $dir );
} catch ( UnexpectedValueException $e ) {
$this->iter = null; // bad permissions? deleted?
}
}
/**
* @see Iterator::current()
* @return string|bool String or false
* Return an appropriate iterator object to wrap
*
* @param $dir string file system directory
* @return Iterator
*/
public function current() {
// Return only the relative path and normalize slashes to FileBackend-style
// Make sure to use the realpath since the suffix is based upon that
return str_replace( '\\', '/',
substr( realpath( $this->iter->current() ), $this->suffixStart ) );
protected function initIterator( $dir ) {
if ( !empty( $this->params['topOnly'] ) ) { // non-recursive
# Get an iterator that will get direct sub-nodes
return new DirectoryIterator( $dir );
} else { // recursive
# Get an iterator that will return leaf nodes (non-directories)
if ( MWInit::classExists( 'FilesystemIterator' ) ) { // PHP >= 5.3
# RecursiveDirectoryIterator extends FilesystemIterator.
# FilesystemIterator::SKIP_DOTS default is inconsistent in PHP 5.3.x.
$flags = FilesystemIterator::CURRENT_AS_SELF | FilesystemIterator::SKIP_DOTS;
return new RecursiveIteratorIterator(
new RecursiveDirectoryIterator( $dir, $flags ),
RecursiveIteratorIterator::CHILD_FIRST // include dirs
);
} else { // PHP < 5.3
# RecursiveDirectoryIterator extends DirectoryIterator
return new RecursiveIteratorIterator(
new RecursiveDirectoryIterator( $dir ),
RecursiveIteratorIterator::CHILD_FIRST // include dirs
);
}
}
}
/**
@ -598,6 +651,14 @@ class FSFileBackendFileList implements Iterator {
return $this->pos;
}
/**
* @see Iterator::current()
* @return string|bool String or false
*/
public function current() {
return $this->getRelPath( $this->iter->current()->getPathname() );
}
/**
* @see Iterator::next()
* @return void
@ -605,6 +666,7 @@ class FSFileBackendFileList implements Iterator {
public function next() {
try {
$this->iter->next();
$this->filterViaNext();
} catch ( UnexpectedValueException $e ) {
$this->iter = null;
}
@ -619,6 +681,7 @@ class FSFileBackendFileList implements Iterator {
$this->pos = 0;
try {
$this->iter->rewind();
$this->filterViaNext();
} catch ( UnexpectedValueException $e ) {
$this->iter = null;
}
@ -631,4 +694,44 @@ class FSFileBackendFileList implements Iterator {
public function valid() {
return $this->iter && $this->iter->valid();
}
/**
* Filter out items by advancing to the next ones
*/
protected function filterViaNext() {}
/**
* Return only the relative path and normalize slashes to FileBackend-style.
* Uses the "real path" since the suffix is based upon that.
*
* @param $path string
* @return string
*/
protected function getRelPath( $path ) {
return strtr( substr( realpath( $path ), $this->suffixStart ), '\\', '/' );
}
}
class FSFileBackendDirList extends FSFileBackendList {
protected function filterViaNext() {
while ( $this->iter->valid() ) {
if ( $this->iter->current()->isDot() || !$this->iter->current()->isDir() ) {
$this->iter->next(); // skip non-directories and dot files
} else {
break;
}
}
}
}
class FSFileBackendFileList extends FSFileBackendList {
protected function filterViaNext() {
while ( $this->iter->valid() ) {
if ( !$this->iter->current()->isFile() ) {
$this->iter->next(); // skip non-files and dot files
} else {
break;
}
}
}
}

View file

@ -176,8 +176,9 @@ abstract class FileBackend {
* contents as the new contents to be written there.
*
* $opts is an associative of boolean flags, including:
* 'force' : Errors that would normally cause a rollback do not.
* The remaining operations are still attempted if any fail.
* '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.
@ -315,8 +316,8 @@ abstract class FileBackend {
* otherwise safe from modification from other processes. Normally,
* the file will be a new temp file, which should be adequate.
* $params include:
* srcs : ordered source storage paths (e.g. chunk1, chunk2, ...)
* dst : file system path to 0-byte temp file
* srcs : ordered source storage paths (e.g. chunk1, chunk2, ...)
* dst : file system path to 0-byte temp file
*
* @param $params Array Operation parameters
* @return Status
@ -383,7 +384,8 @@ abstract class FileBackend {
* is that of an empty container, in which case it should be deleted.
*
* $params include:
* dir : storage directory
* dir : storage directory
* recursive : recursively delete empty subdirectories first (@since 1.20)
*
* @param $params Array
* @return Status
@ -546,22 +548,89 @@ abstract class FileBackend {
abstract public function getLocalCopy( array $params );
/**
* Get an iterator to list out all stored files under a storage directory.
* Check if a directory exists at a given storage path.
* Backends using key/value stores will check if the path is a
* virtual directory, meaning there are files under the given directory.
*
* Storage backends with eventual consistency might return stale data.
*
* $params include:
* dir : storage directory
*
* @return bool|null Returns null on failure
* @since 1.20
*/
abstract public function directoryExists( array $params );
/**
* Get an iterator to list *all* directories under a storage directory.
* If the directory is of the form "mwstore://backend/container",
* then all directories in the container should be listed.
* If the directory is of form "mwstore://backend/container/dir",
* then all directories directly under that directory should be listed.
* Results should be storage directories relative to the given directory.
*
* Storage backends with eventual consistency might return stale data.
*
* $params include:
* dir : storage directory
* topOnly : only return direct child dirs of the directory
*
* @return Traversable|Array|null Returns null on failure
* @since 1.20
*/
abstract public function getDirectoryList( array $params );
/**
* Same as FileBackend::getDirectoryList() except only lists
* directories that are immediately under the given directory.
*
* Storage backends with eventual consistency might return stale data.
*
* $params include:
* dir : storage directory
*
* @return Traversable|Array|null Returns null on failure
* @since 1.20
*/
final public function getTopDirectoryList( array $params ) {
return $this->getDirectoryList( array( 'topOnly' => true ) + $params );
}
/**
* Get an iterator to list *all* stored files under a storage directory.
* If the directory is of the form "mwstore://backend/container",
* then all files in the container should be listed.
* If the directory is of form "mwstore://backend/container/dir",
* then all files under that container directory should be listed.
* then all files under that directory should be listed.
* Results should be storage paths relative to the given directory.
*
* Storage backends with eventual consistency might return stale data.
*
* $params include:
* dir : storage path directory
* 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 );
/**
* Same as FileBackend::getFileList() except only lists
* files that are immediately under the given directory.
*
* Storage backends with eventual consistency might return stale data.
*
* $params include:
* dir : storage directory
*
* @return Traversable|Array|null Returns null on failure
* @since 1.20
*/
final public function getTopFileList( array $params ) {
return $this->getFileList( array( 'topOnly' => true ) + $params );
}
/**
* Invalidate any in-process file existence and property cache.
* If $paths is given, then only the cache for those files will be cleared.
@ -708,6 +777,7 @@ abstract class FileBackend {
*
* @param $path string
* @return bool
* @since 1.20
*/
final public static function isPathTraversalFree( $path ) {
return ( self::normalizeContainerPath( $path ) !== null );

View file

@ -21,7 +21,6 @@ class FileBackendGroup {
protected $backends = array();
protected function __construct() {}
protected function __clone() {}
/**
* @return FileBackendGroup

View file

@ -313,7 +313,7 @@ class FileBackendMultiWrite extends FileBackend {
}
/**
* @see FileBackend::getFileList()
* @see FileBackend::concatenate()
*/
public function concatenate( array $params ) {
// We are writing to an FS file, so we don't need to do this per-backend
@ -401,6 +401,22 @@ class FileBackendMultiWrite extends FileBackend {
return $this->backends[$this->masterIndex]->getLocalCopy( $realParams );
}
/**
* @see FileBackend::directoryExists()
*/
public function directoryExists( array $params ) {
$realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
return $this->backends[$this->masterIndex]->directoryExists( $realParams );
}
/**
* @see FileBackend::getSubdirectoryList()
*/
public function getDirectoryList( array $params ) {
$realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
return $this->backends[$this->masterIndex]->getDirectoryList( $realParams );
}
/**
* @see FileBackend::getFileList()
*/

View file

@ -19,18 +19,31 @@
* @since 1.19
*/
abstract class FileBackendStore extends FileBackend {
/** @var BagOStuff */
protected $memCache;
/** @var Array Map of paths to small (RAM/disk) cache items */
protected $cache = array(); // (storage path => key => value)
protected $maxCacheSize = 100; // integer; max paths with entries
protected $maxCacheSize = 300; // integer; max paths with entries
/** @var Array Map of paths to large (RAM/disk) cache items */
protected $expensiveCache = array(); // (storage path => key => value)
protected $maxExpensiveCacheSize = 10; // integer; max paths with entries
protected $maxExpensiveCacheSize = 5; // integer; max paths with entries
/** @var Array Map of container names to sharding settings */
protected $shardViaHashLevels = array(); // (container name => config array)
protected $maxFileSize = 4294967296; // integer bytes (4GiB)
/**
* @see FileBackend::__construct()
*
* @param $config Array
*/
public function __construct( array $config ) {
parent::__construct( $config );
$this->memCache = new EmptyBagOStuff(); // disabled by default
}
/**
* Get the maximum allowable file size given backend
* medium restrictions and basic performance constraints.
@ -73,6 +86,7 @@ abstract class FileBackendStore extends FileBackend {
} else {
$status = $this->doCreateInternal( $params );
$this->clearCache( array( $params['dst'] ) );
$this->deleteFileCache( $params['dst'] ); // persistent cache
}
wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
@ -104,6 +118,7 @@ abstract class FileBackendStore extends FileBackend {
} else {
$status = $this->doStoreInternal( $params );
$this->clearCache( array( $params['dst'] ) );
$this->deleteFileCache( $params['dst'] ); // persistent cache
}
wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
@ -132,6 +147,7 @@ abstract class FileBackendStore extends FileBackend {
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;
@ -158,6 +174,7 @@ abstract class FileBackendStore extends FileBackend {
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;
@ -185,6 +202,8 @@ abstract class FileBackendStore extends FileBackend {
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;
@ -371,6 +390,17 @@ abstract class FileBackendStore extends FileBackend {
wfProfileIn( __METHOD__ . '-' . $this->name );
$status = Status::newGood();
// Recursive: first delete all empty subdirs recursively
if ( !empty( $params['recursive'] ) && !$this->directoriesAreVirtual() ) {
$subDirsRel = $this->getTopDirectoryList( array( 'dir' => $params['dir'] ) );
if ( $subDirsRel !== null ) { // no errors
foreach ( $subDirsRel as $subDirRel ) {
$subDir = $params['dir'] . "/{$subDirRel}"; // full path
$status->merge( $this->doClean( array( 'dir' => $subDir ) + $params ) );
}
}
}
list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
if ( $dir === null ) {
$status->fatal( 'backend-fail-invalidpath', $params['dir'] );
@ -390,11 +420,13 @@ abstract class FileBackendStore extends FileBackend {
if ( $shard !== null ) { // confined to a single container/shard
$status->merge( $this->doCleanInternal( $fullCont, $dir, $params ) );
$this->deleteContainerCache( $fullCont ); // purge cache
} else { // directory is on several shards
wfDebug( __METHOD__ . ": iterating over all container shards.\n" );
list( $b, $shortCont, $r ) = self::splitStoragePath( $params['dir'] );
foreach ( $this->getContainerSuffixes( $shortCont ) as $suffix ) {
$status->merge( $this->doCleanInternal( "{$fullCont}{$suffix}", $dir, $params ) );
$this->deleteContainerCache( "{$fullCont}{$suffix}" ); // purge cache
}
}
@ -463,7 +495,10 @@ abstract class FileBackendStore extends FileBackend {
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.
@ -480,9 +515,10 @@ abstract class FileBackendStore extends FileBackend {
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__ );
@ -646,8 +682,79 @@ abstract class FileBackendStore extends FileBackend {
}
/**
* @copydoc FileBackend::getFileList()
* @return Array|null|Traversable
* @see FileBackend::directoryExists()
* @return bool|null
*/
final public function directoryExists( array $params ) {
list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
if ( $dir === null ) {
return false; // invalid storage path
}
if ( $shard !== null ) { // confined to a single container/shard
return $this->doDirectoryExists( $fullCont, $dir, $params );
} else { // directory is on several shards
wfDebug( __METHOD__ . ": iterating over all container shards.\n" );
list( $b, $shortCont, $r ) = self::splitStoragePath( $params['dir'] );
$res = false; // response
foreach ( $this->getContainerSuffixes( $shortCont ) as $suffix ) {
$exists = $this->doDirectoryExists( "{$fullCont}{$suffix}", $dir, $params );
if ( $exists ) {
$res = true;
break; // found one!
} elseif ( $exists === null ) { // error?
$res = null; // if we don't find anything, it is indeterminate
}
}
return $res;
}
}
/**
* @see FileBackendStore::directoryExists()
*
* @param $container string Resolved container name
* @param $dir string Resolved path relative to container
* @param $params Array
* @return bool|null
*/
abstract protected function doDirectoryExists( $container, $dir, array $params );
/**
* @see FileBackend::getDirectoryList()
* @return Traversable|Array|null Returns null on failure
*/
final public function getDirectoryList( array $params ) {
list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
if ( $dir === null ) { // invalid storage path
return null;
}
if ( $shard !== null ) {
// File listing is confined to a single container/shard
return $this->getDirectoryListInternal( $fullCont, $dir, $params );
} else {
wfDebug( __METHOD__ . ": iterating over all container shards.\n" );
// File listing spans multiple containers/shards
list( $b, $shortCont, $r ) = self::splitStoragePath( $params['dir'] );
return new FileBackendStoreShardDirIterator( $this,
$fullCont, $dir, $this->getContainerSuffixes( $shortCont ), $params );
}
}
/**
* Do not call this function from places outside FileBackend
*
* @see FileBackendStore::getDirectoryList()
*
* @param $container string Resolved container name
* @param $dir string Resolved path relative to container
* @param $params Array
* @return Traversable|Array|null Returns null on failure
*/
abstract public function getDirectoryListInternal( $container, $dir, array $params );
/**
* @see FileBackend::getFileList()
* @return Traversable|Array|null Returns null on failure
*/
final public function getFileList( array $params ) {
list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
@ -661,7 +768,7 @@ abstract class FileBackendStore extends FileBackend {
wfDebug( __METHOD__ . ": iterating over all container shards.\n" );
// File listing spans multiple containers/shards
list( $b, $shortCont, $r ) = self::splitStoragePath( $params['dir'] );
return new FileBackendStoreShardListIterator( $this,
return new FileBackendStoreShardFileIterator( $this,
$fullCont, $dir, $this->getContainerSuffixes( $shortCont ), $params );
}
}
@ -674,7 +781,7 @@ abstract class FileBackendStore extends FileBackend {
* @param $container string Resolved container name
* @param $dir string Resolved path relative to container
* @param $params Array
* @return Traversable|Array|null
* @return Traversable|Array|null Returns null on failure
*/
abstract public function getFileListInternal( $container, $dir, array $params );
@ -775,9 +882,13 @@ abstract class FileBackendStore extends FileBackend {
}
}
// Clear any cache entries (after locks acquired)
// Clear any file cache entries (after locks acquired)
$this->clearCache();
// Load from the persistent file and container caches
$this->primeFileCache( $performOps );
$this->primeContainerCache( $performOps );
// Actually attempt the operation batch...
$subStatus = FileOp::attemptBatch( $performOps, $opts, $this->fileJournal );
@ -820,10 +931,20 @@ abstract class FileBackendStore extends FileBackend {
*/
protected function doClearCache( array $paths = null ) {}
/**
* Is this a key/value store where directories are just virtual?
* Virtual directories exists in so much as files exists that are
* prefixed with the directory path followed by a forward slash.
*
* @return bool
*/
abstract protected function directoriesAreVirtual();
/**
* Move a cache entry to the top (such as when accessed)
*
* @param $path string Storage path
* @return void
*/
protected function pingCache( $path ) {
if ( isset( $this->cache[$path] ) ) {
@ -849,6 +970,7 @@ abstract class FileBackendStore extends FileBackend {
* Move a cache entry to the top (such as when accessed)
*
* @param $path string Storage path
* @return void
*/
protected function pingExpensiveCache( $path ) {
if ( isset( $this->expensiveCache[$path] ) ) {
@ -977,6 +1099,19 @@ abstract class FileBackendStore extends FileBackend {
return ''; // no sharding
}
/**
* Check if a storage path maps to a single shard.
* Container dirs like "a", where the container shards on "x/xy",
* can reside on several shards. Such paths are tricky to handle.
*
* @param $storagePath string Storage path
* @return bool
*/
final public function isSingleShardPathInternal( $storagePath ) {
list( $c, $r, $shard ) = $this->resolveStoragePath( $storagePath );
return ( $shard !== null );
}
/**
* Get the sharding config for a container.
* If greater than 0, then all file storage paths within
@ -1056,29 +1191,198 @@ abstract class FileBackendStore extends FileBackend {
protected function resolveContainerPath( $container, $relStoragePath ) {
return $relStoragePath;
}
/**
* Get the cache key for a container
*
* @param $container Resolved container name
* @return string
*/
private function containerCacheKey( $container ) {
return wfMemcKey( 'backend', $this->getName(), 'container', $container );
}
/**
* Set the cached info for a container
*
* @param $container Resolved container name
* @param $val mixed Information to cache
* @return void
*/
final protected function setContainerCache( $container, $val ) {
$this->memCache->set( $this->containerCacheKey( $container ), $val, 14*86400 );
}
/**
* Delete the cached info for a container
*
* @param $container Resolved container name
* @return void
*/
final protected function deleteContainerCache( $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
* @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...
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;
} elseif ( is_string( $item ) ) { // full container name
$contNames[$this->containerCacheKey( $item )] = $item;
}
}
// Get all the corresponding cache keys for paths...
foreach ( $paths as $path ) {
list( $fullCont, $r, $s ) = $this->resolveStoragePath( $path );
if ( $fullCont !== null ) { // valid path for this backend
$contNames[$this->containerCacheKey( $fullCont )] = $fullCont;
}
}
$contInfo = array(); // (resolved container name => cache value)
// Get all cache entries for these container cache keys...
$values = $this->memCache->getBatch( array_keys( $contNames ) );
foreach ( $values as $cacheKey => $val ) {
$contInfo[$contNames[$cacheKey]] = $val;
}
// Populate the container process cache for the backend...
$this->doPrimeContainerCache( array_filter( $contInfo, 'is_array' ) );
wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
}
/**
* Fill the backend-specific process cache given an array of
* resolved container names and their corresponding cached info.
* Only containers that actually exist should appear in the map.
*
* @param $containerInfo Array Map of resolved container names to cached info
* @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__ );
}
}
/**
* FileBackendStore helper function to handle file listings that span container shards.
* FileBackendStore helper function to handle listings that span container shards.
* Do not use this class from places outside of FileBackendStore.
*
* @ingroup FileBackend
*/
class FileBackendStoreShardListIterator implements Iterator {
/* @var FileBackendStore */
abstract class FileBackendStoreShardListIterator implements Iterator {
/** @var FileBackendStore */
protected $backend;
/* @var Array */
/** @var Array */
protected $params;
/* @var Array */
/** @var Array */
protected $shardSuffixes;
protected $container; // string
protected $directory; // string
protected $container; // string; full container name
protected $directory; // string; resolved relative path
/* @var Traversable */
/** @var Traversable */
protected $iter;
protected $curShard = 0; // integer
protected $pos = 0; // integer
/** @var Array */
protected $multiShardPaths = array(); // (rel path => 1)
/**
* @param $backend FileBackendStore
* @param $container string Full storage container name
@ -1127,6 +1431,8 @@ class FileBackendStoreShardListIterator implements Iterator {
} else {
$this->iter->next();
}
// Filter out items that we already listed
$this->filterViaNext();
// Find the next non-empty shard if no elements are left
$this->nextShardIteratorIfNotValid();
}
@ -1139,6 +1445,8 @@ class FileBackendStoreShardListIterator implements Iterator {
$this->pos = 0;
$this->curShard = 0;
$this->setIteratorFromCurrentShard();
// Filter out items that we already listed
$this->filterViaNext();
// Find the next non-empty shard if this one has no elements
$this->nextShardIteratorIfNotValid();
}
@ -1148,7 +1456,7 @@ class FileBackendStoreShardListIterator implements Iterator {
* @return bool
*/
public function valid() {
if ( $this->iter == null ) {
if ( $this->iter === null ) {
return false; // some failure?
} elseif ( is_array( $this->iter ) ) {
return ( current( $this->iter ) !== false ); // no paths can have this value
@ -1157,6 +1465,25 @@ class FileBackendStoreShardListIterator implements Iterator {
}
}
/**
* Filter out duplicate items by advancing to the next ones
*/
protected function filterViaNext() {
while ( $this->iter->valid() ) {
$rel = $this->iter->current(); // path relative to given directory
$path = $this->params['dir'] . "/{$rel}"; // full storage path
if ( !$this->backend->isSingleShardPathInternal( $path ) ) {
// Don't keep listing paths that are on multiple shards
if ( isset( $this->multiShardPaths[$rel] ) ) {
$this->iter->next(); // we already listed this path
} else {
$this->multiShardPaths[$rel] = 1;
break;
}
}
}
}
/**
* If the list iterator for this container shard is out of items,
* then move on to the next container that has items.
@ -1176,7 +1503,35 @@ class FileBackendStoreShardListIterator implements Iterator {
*/
protected function setIteratorFromCurrentShard() {
$suffix = $this->shardSuffixes[$this->curShard];
$this->iter = $this->backend->getFileListInternal(
$this->iter = $this->listFromShard(
"{$this->container}{$suffix}", $this->directory, $this->params );
}
/**
* Get the list for a given container shard
*
* @param $container string Resolved container name
* @param $dir string Resolved path relative to container
* @param $params Array
* @return Traversable|Array|null
*/
abstract protected function listFromShard( $container, $dir, array $params );
}
/**
* Iterator for listing directories
*/
class FileBackendStoreShardDirIterator extends FileBackendStoreShardListIterator {
protected function listFromShard( $container, $dir, array $params ) {
return $this->backend->getDirectoryListInternal( $container, $dir, $params );
}
}
/**
* Iterator for listing regular files
*/
class FileBackendStoreShardFileIterator extends FileBackendStoreShardListIterator {
protected function listFromShard( $container, $dir, array $params ) {
return $this->backend->getFileListInternal( $container, $dir, $params );
}
}

View file

@ -24,7 +24,7 @@ class SwiftFileBackend extends FileBackendStore {
protected $auth; // Swift authentication handler
protected $authTTL; // integer seconds
protected $swiftAnonUser; // string; username to handle unauthenticated requests
protected $maxContCacheSize = 100; // integer; max containers with entries
protected $maxContCacheSize = 300; // integer; max containers with entries
/** @var CF_Connection */
protected $conn; // Swift connection handle
@ -57,13 +57,15 @@ class SwiftFileBackend extends FileBackendStore {
// Optional settings
$this->authTTL = isset( $config['swiftAuthTTL'] )
? $config['swiftAuthTTL']
: 120; // some sane number
: 5 * 60; // some sane number
$this->swiftAnonUser = isset( $config['swiftAnonUser'] )
? $config['swiftAnonUser']
: '';
$this->shardViaHashLevels = isset( $config['shardViaHashLevels'] )
? $config['shardViaHashLevels']
: '';
// Cache container info to mask latency
$this->memCache = wfGetMainCache();
}
/**
@ -535,12 +537,39 @@ class SwiftFileBackend extends FileBackendStore {
return $data;
}
/**
* @see FileBackendStore::doDirectoryExists()
* @return bool|null
*/
protected function doDirectoryExists( $fullCont, $dir, array $params ) {
try {
$container = $this->getContainer( $fullCont );
$prefix = ( $dir == '' ) ? null : "{$dir}/";
return ( count( $container->list_objects( 1, null, $prefix ) ) > 0 );
} catch ( NoSuchContainerException $e ) {
return false;
} catch ( InvalidResponseException $e ) {
} catch ( Exception $e ) { // some other exception?
$this->logException( $e, __METHOD__, array( 'cont' => $fullCont, 'dir' => $dir ) );
}
return null; // error
}
/**
* @see FileBackendStore::getDirectoryListInternal()
* @return SwiftFileBackendDirList
*/
public function getDirectoryListInternal( $fullCont, $dir, array $params ) {
return new SwiftFileBackendDirList( $this, $fullCont, $dir, $params );
}
/**
* @see FileBackendStore::getFileListInternal()
* @return SwiftFileBackendFileList
*/
public function getFileListInternal( $fullCont, $dir, array $params ) {
return new SwiftFileBackendFileList( $this, $fullCont, $dir );
return new SwiftFileBackendFileList( $this, $fullCont, $dir, $params );
}
/**
@ -548,17 +577,96 @@ class SwiftFileBackend extends FileBackendStore {
*
* @param $fullCont string Resolved container name
* @param $dir string Resolved storage directory with no trailing slash
* @param $after string Storage path of file to list items after
* @param $after string|null Storage path of file to list items after
* @param $limit integer Max number of items to list
* @return Array
* @param $params Array Includes flag for 'topOnly'
* @return Array List of relative paths of dirs directly under $dir
*/
public function getFileListPageInternal( $fullCont, $dir, $after, $limit ) {
public function getDirListPageInternal( $fullCont, $dir, &$after, $limit, array $params ) {
$dirs = array();
try {
$container = $this->getContainer( $fullCont );
$prefix = ( $dir == '' ) ? null : "{$dir}/";
// Non-recursive: only list dirs right under $dir
if ( !empty( $params['topOnly'] ) ) {
$objects = $container->list_objects( $limit, $after, $prefix, null, '/' );
foreach ( $objects as $object ) { // files and dirs
if ( substr( $object, -1 ) === '/' ) {
$dirs[] = $object; // directories end in '/'
}
$after = $object; // update last item
}
// Recursive: list all dirs under $dir and its subdirs
} else {
// Get directory from last item of prior page
$lastDir = $this->getParentDir( $after ); // must be first page
$objects = $container->list_objects( $limit, $after, $prefix );
foreach ( $objects as $object ) { // files
$objectDir = $this->getParentDir( $object ); // directory of object
if ( $objectDir !== false ) { // file has a parent dir
// Swift stores paths in UTF-8, using binary sorting.
// See function "create_container_table" in common/db.py.
// If a directory is not "greater" than the last one,
// then it was already listed by the calling iterator.
if ( $objectDir > $lastDir ) {
$pDir = $objectDir;
do { // add dir and all its parent dirs
$dirs[] = "{$pDir}/";
$pDir = $this->getParentDir( $pDir );
} while ( $pDir !== false // sanity
&& $pDir > $lastDir // not done already
&& strlen( $pDir ) > strlen( $dir ) // within $dir
);
}
$lastDir = $objectDir;
}
$after = $object; // update last item
}
}
} catch ( NoSuchContainerException $e ) {
} catch ( InvalidResponseException $e ) {
} catch ( Exception $e ) { // some other exception?
$this->logException( $e, __METHOD__, array( 'cont' => $fullCont, 'dir' => $dir ) );
}
return $dirs;
}
protected function getParentDir( $path ) {
return ( strpos( $path, '/' ) !== false ) ? dirname( $path ) : false;
}
/**
* Do not call this function outside of SwiftFileBackendFileList
*
* @param $fullCont string Resolved container name
* @param $dir string Resolved storage directory with no trailing slash
* @param $after string|null Storage path of file to list items after
* @param $limit integer Max number of items to list
* @param $params Array Includes flag for 'topOnly'
* @return Array List of relative paths of files under $dir
*/
public function getFileListPageInternal( $fullCont, $dir, &$after, $limit, array $params ) {
$files = array();
try {
$container = $this->getContainer( $fullCont );
$prefix = ( $dir == '' ) ? null : "{$dir}/";
$files = $container->list_objects( $limit, $after, $prefix );
// Non-recursive: only list files right under $dir
if ( !empty( $params['topOnly'] ) ) { // files and dirs
$objects = $container->list_objects( $limit, $after, $prefix, null, '/' );
foreach ( $objects as $object ) {
if ( substr( $object, -1 ) !== '/' ) {
$files[] = $object; // directories end in '/'
}
}
// Recursive: list all files under $dir and its subdirs
} else { // files
$files = $container->list_objects( $limit, $after, $prefix );
}
$after = end( $files ); // update last item
reset( $files ); // reset pointer
} catch ( NoSuchContainerException $e ) {
} catch ( InvalidResponseException $e ) {
} catch ( Exception $e ) { // some other exception?
@ -664,6 +772,14 @@ class SwiftFileBackend extends FileBackendStore {
return $tmpFile;
}
/**
* @see FileBackendStore::directoriesAreVirtual()
* @return bool
*/
protected function directoriesAreVirtual() {
return true;
}
/**
* Get headers to send to Swift when reading a file based
* on a FileBackend params array, e.g. that of getLocalCopy().
@ -750,23 +866,30 @@ class SwiftFileBackend extends FileBackendStore {
* Use $reCache if the file count or byte count is needed.
*
* @param $container string Container name
* @param $reCache bool Refresh the process cache
* @param $bypassCache bool Bypass all caches and load from Swift
* @return CF_Container
* @throws InvalidResponseException
*/
protected function getContainer( $container, $reCache = false ) {
protected function getContainer( $container, $bypassCache = false ) {
$conn = $this->getConnection(); // Swift proxy connection
if ( $reCache ) {
unset( $this->connContainers[$container] ); // purge cache
if ( $bypassCache ) { // purge cache
unset( $this->connContainers[$container] );
} elseif ( !isset( $this->connContainers[$container] ) ) {
$this->primeContainerCache( array( $container ) ); // check persistent cache
}
if ( !isset( $this->connContainers[$container] ) ) {
$contObj = $conn->get_container( $container );
// NoSuchContainerException not thrown: container must exist
if ( count( $this->connContainers ) >= $this->maxContCacheSize ) { // trim cache?
reset( $this->connContainers );
$key = key( $this->connContainers );
unset( $this->connContainers[$key] );
unset( $this->connContainers[key( $this->connContainers )] );
}
$this->connContainers[$container] = $contObj; // cache it
if ( !$bypassCache ) {
$this->setContainerCache( $container, // update persistent cache
array( 'bytes' => $contObj->bytes_used, 'count' => $contObj->object_count )
);
}
}
return $this->connContainers[$container];
}
@ -776,6 +899,7 @@ class SwiftFileBackend extends FileBackendStore {
*
* @param $container string Container name
* @return CF_Container
* @throws InvalidResponseException
*/
protected function createContainer( $container ) {
$conn = $this->getConnection(); // Swift proxy connection
@ -789,6 +913,7 @@ class SwiftFileBackend extends FileBackendStore {
*
* @param $container string Container name
* @return void
* @throws InvalidResponseException
*/
protected function deleteContainer( $container ) {
$conn = $this->getConnection(); // Swift proxy connection
@ -796,6 +921,28 @@ class SwiftFileBackend extends FileBackendStore {
unset( $this->connContainers[$container] ); // purge cache
}
/**
* @see FileBackendStore::doPrimeContainerCache()
* @return void
*/
protected function doPrimeContainerCache( array $containerInfo ) {
try {
$conn = $this->getConnection(); // Swift proxy connection
foreach ( $containerInfo as $container => $info ) {
$this->connContainers[$container] = new CF_Container(
$conn->cfs_auth,
$conn->cfs_http,
$container,
$info['count'],
$info['bytes']
);
}
} catch ( InvalidResponseException $e ) {
} catch ( Exception $e ) { // some other exception?
$this->logException( $e, __METHOD__, array() );
}
}
/**
* Log an unexpected exception for this backend
*
@ -816,22 +963,24 @@ class SwiftFileBackend extends FileBackendStore {
}
/**
* SwiftFileBackend helper class to page through object listings.
* SwiftFileBackend helper class to page through listings.
* Swift also has a listing limit of 10,000 objects for sanity.
* Do not use this class from places outside SwiftFileBackend.
*
* @ingroup FileBackend
*/
class SwiftFileBackendFileList implements Iterator {
abstract class SwiftFileBackendList implements Iterator {
/** @var Array */
protected $bufferIter = array();
protected $bufferAfter = null; // string; list items *after* this path
protected $pos = 0; // integer
/** @var Array */
protected $params = array();
/** @var SwiftFileBackend */
protected $backend;
protected $container; //
protected $dir; // string storage directory
protected $container; // string; container name
protected $dir; // string; storage directory
protected $suffixStart; // integer
const PAGE_SIZE = 5000; // file listing buffer size
@ -840,8 +989,9 @@ class SwiftFileBackendFileList implements Iterator {
* @param $backend SwiftFileBackend
* @param $fullCont string Resolved container name
* @param $dir string Resolved directory relative to container
* @param $params Array
*/
public function __construct( SwiftFileBackend $backend, $fullCont, $dir ) {
public function __construct( SwiftFileBackend $backend, $fullCont, $dir, array $params ) {
$this->backend = $backend;
$this->container = $fullCont;
$this->dir = $dir;
@ -853,14 +1003,7 @@ class SwiftFileBackendFileList implements Iterator {
} else { // dir within container
$this->suffixStart = strlen( $this->dir ) + 1; // size of "path/to/dir/"
}
}
/**
* @see Iterator::current()
* @return string|bool String or false
*/
public function current() {
return substr( current( $this->bufferIter ), $this->suffixStart );
$this->params = $params;
}
/**
@ -882,10 +1025,9 @@ class SwiftFileBackendFileList implements Iterator {
// Check if there are no files left in this page and
// advance to the next page if this page was not empty.
if ( !$this->valid() && count( $this->bufferIter ) ) {
$this->bufferAfter = end( $this->bufferIter );
$this->bufferIter = $this->backend->getFileListPageInternal(
$this->container, $this->dir, $this->bufferAfter, self::PAGE_SIZE
);
$this->bufferIter = $this->pageFromList(
$this->container, $this->dir, $this->bufferAfter, self::PAGE_SIZE, $this->params
); // updates $this->bufferAfter
}
}
@ -896,9 +1038,9 @@ class SwiftFileBackendFileList implements Iterator {
public function rewind() {
$this->pos = 0;
$this->bufferAfter = null;
$this->bufferIter = $this->backend->getFileListPageInternal(
$this->container, $this->dir, $this->bufferAfter, self::PAGE_SIZE
);
$this->bufferIter = $this->pageFromList(
$this->container, $this->dir, $this->bufferAfter, self::PAGE_SIZE, $this->params
); // updates $this->bufferAfter
}
/**
@ -906,6 +1048,64 @@ class SwiftFileBackendFileList implements Iterator {
* @return bool
*/
public function valid() {
return ( current( $this->bufferIter ) !== false ); // no paths can have this value
if ( $this->bufferIter === null ) {
return false; // some failure?
} else {
return ( current( $this->bufferIter ) !== false ); // no paths can have this value
}
}
/**
* Get the given list portion (page)
*
* @param $container string Resolved container name
* @param $dir string Resolved path relative to container
* @param $after string|null
* @param $limit integer
* @param $params Array
* @return Traversable|Array|null Returns null on failure
*/
abstract protected function pageFromList( $container, $dir, &$after, $limit, array $params );
}
/**
* Iterator for listing directories
*/
class SwiftFileBackendDirList extends SwiftFileBackendList {
/**
* @see Iterator::current()
* @return string|bool String (relative path) or false
*/
public function current() {
return substr( current( $this->bufferIter ), $this->suffixStart, -1 );
}
/**
* @see SwiftFileBackendList::pageFromList()
* @return Array|null
*/
protected function pageFromList( $container, $dir, &$after, $limit, array $params ) {
return $this->backend->getDirListPageInternal( $container, $dir, $after, $limit, $params );
}
}
/**
* Iterator for listing regular files
*/
class SwiftFileBackendFileList extends SwiftFileBackendList {
/**
* @see Iterator::current()
* @return string|bool String (relative path) or false
*/
public function current() {
return substr( current( $this->bufferIter ), $this->suffixStart );
}
/**
* @see SwiftFileBackendList::pageFromList()
* @return Array|null
*/
protected function pageFromList( $container, $dir, &$after, $limit, array $params ) {
return $this->backend->getFileListPageInternal( $container, $dir, $after, $limit, $params );
}
}

View file

@ -139,8 +139,6 @@ class ScopedLock {
$this->type = $type;
}
protected function __clone() {}
/**
* Get a ScopedLock object representing a lock on resource paths.
* Any locks are released once this object goes out of scope.

View file

@ -17,8 +17,6 @@ class LockManagerGroup {
protected $managers = array();
protected function __construct() {}
protected function __clone() {}
/**
* @return LockManagerGroup
*/

View file

@ -143,7 +143,7 @@ class ArchivedFile {
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();

View file

@ -842,6 +842,13 @@ abstract class File {
}
}
// If the backend is ready-only, don't keep generating thumbnails
// only to return transformation errors, just return the error now.
if ( $this->repo->getReadOnlyReason() !== false ) {
$thumb = $this->transformErrorOutput( $thumbPath, $thumbUrl, $params, $flags );
break;
}
// Create a temp FS file with the same extension and the thumbnail
$thumbExt = FileBackend::extensionFromPath( $thumbPath );
$tmpFile = TempFSFile::factory( 'transform_', $thumbExt );
@ -871,6 +878,8 @@ abstract class File {
} else {
$thumb = $this->transformErrorOutput( $thumbPath, $thumbUrl, $params, $flags );
}
// Give extensions a chance to do something with this thumbnail...
wfRunHooks( 'FileTransformed', array( $this, $thumb, $tmpThumbPath, $thumbPath ) );
}
// Purge. Useful in the event of Core -> Squid connection failure or squid

View file

@ -249,6 +249,6 @@ class ForeignAPIFile extends File {
# Delete the thumbnails
$this->repo->quickPurgeBatch( $purgeList );
# Clear out the thumbnail directory if empty
$this->repo->cleanDir( $dir );
$this->repo->quickCleanDir( $dir );
}
}

View file

@ -779,7 +779,7 @@ class LocalFile extends File {
# Delete the thumbnails
$this->repo->quickPurgeBatch( $purgeList );
# Clear out the thumbnail directory if empty
$this->repo->cleanDir( $dir );
$this->repo->quickCleanDir( $dir );
}
/** purgeDescription inherited */

View file

@ -230,6 +230,7 @@ abstract class DatabaseUpdater {
* @since 1.20
*
* @param $tableName string
* @return bool
*/
public function tableExists( $tableName ) {
return ( $this->db->tableExists( $tableName, __METHOD__ ) );

View file

@ -56,8 +56,7 @@ abstract class BagOStuff {
/**
* 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 );
@ -65,7 +64,6 @@ abstract class BagOStuff {
* 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 ) {
@ -81,6 +79,7 @@ abstract class BagOStuff {
* @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 );
@ -88,19 +87,33 @@ abstract class BagOStuff {
* 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();
@ -122,24 +135,36 @@ abstract class BagOStuff {
/* *** 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 ) ) {
@ -157,10 +182,18 @@ abstract class BagOStuff {
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 );
@ -170,6 +203,7 @@ abstract class BagOStuff {
/**
* Convert an optionally relative time to an absolute time
* @param $exptime integer
* @return int
*/
protected function convertExpiry( $exptime ) {
@ -180,5 +214,3 @@ abstract class BagOStuff {
}
}
}

View file

@ -338,9 +338,10 @@ class MWMemcached {
$this->_debugprint( sprintf( "MemCache: delete %s (%s)\n", $key, $res ) );
}
if ( $res == "DELETED" ) {
if ( $res == "DELETED" || $res == "NOT_FOUND" ) {
return true;
}
return false;
}

View file

@ -958,10 +958,18 @@ class PPFrame_DOM implements PPFrame {
}
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__ );

View file

@ -915,9 +915,17 @@ class PPFrame_Hash implements PPFrame {
}
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;

View file

@ -1073,9 +1073,17 @@ class PPFrame_HipHop implements PPFrame {
}
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;

View file

@ -1,12 +1,31 @@
<?php
/**
* @defgroup Profiler Profiler
* Base class and functions for profiling.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
* @ingroup Profiler
* This file is only included if profiling is enabled
*/
/**
* @defgroup Profiler Profiler
*/
/**
* Begin profiling of a function
* @param $functionname String: name of the function we will profile

View file

@ -1,5 +1,22 @@
<?php
/**
* Base class for simple profiling.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
* @ingroup Profiler
*/

View file

@ -1,5 +1,22 @@
<?php
/**
* Profiler showing output in page source.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
* @ingroup Profiler
*/

View file

@ -1,5 +1,22 @@
<?php
/**
* Profiler showing execution trace.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
* @ingroup Profiler
*/

View file

@ -1,5 +1,22 @@
<?php
/**
* Profiler sending messages over UDP.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
* @ingroup Profiler
*/

View file

@ -1,9 +1,31 @@
<?php
/**
* Stub profiling functions
* Stub profiling functions.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
* @ingroup Profiler
*/
/**
* Stub profiler that does nothing
*
* @ingroup Profiler
*/
class ProfilerStub extends Profiler {
public function isStub() {
return true;

View file

@ -70,8 +70,10 @@ abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
*/
protected function getContent( $title ) {
if ( $title->getNamespace() === NS_MEDIAWIKI ) {
$message = wfMessage( $title->getDBkey() )->inContentLanguage();
return $message->exists() ? $message->plain() : '';
// The first "true" is to use the database, the second is to use the content langue
// and the last one is to specify the message key already contains the language in it ("/de", etc.)
$text = MessageCache::singleton()->get( $title->getDBkey(), true, true, true );
return $text === false ? '' : $text;
}
if ( !$title->isCssJsSubpage() && !$title->isCssOrJsPage() ) {
return null;

View file

@ -1,4 +1,26 @@
<?php
/**
* Base implementations for deletable items.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
* @ingroup RevisionDelete
*/
/**
* List for revision table items
*

View file

@ -1,4 +1,25 @@
<?php
/**
* Interface definition for deletable items.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
* @ingroup RevisionDelete
*/
/**
* Abstract base class for a list of deletable items. The list class

View file

@ -1,9 +1,6 @@
<?php
/**
* Backend functions for suppressing and unsuppressing all references to a given user,
* used when blocking with HideUser enabled. This was spun out of SpecialBlockip.php
* in 1.18; at some point it needs to be rewritten to either use RevisionDelete abstraction,
* or at least schema abstraction.
* Backend functions for suppressing and unsuppressing all references to a given user.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -23,6 +20,15 @@
* @file
* @ingroup RevisionDelete
*/
/**
* Backend functions for suppressing and unsuppressing all references to a given user,
* used when blocking with HideUser enabled. This was spun out of SpecialBlockip.php
* in 1.18; at some point it needs to be rewritten to either use RevisionDelete abstraction,
* or at least schema abstraction.
*
* @ingroup RevisionDelete
*/
class RevisionDeleteUser {
/**

View file

@ -2,7 +2,23 @@
/**
* Revision/log/file deletion backend
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
* @ingroup RevisionDelete
*/
/**

View file

@ -2,6 +2,21 @@
/**
* Basic search engine
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
* @ingroup Search
*/

View file

@ -4,6 +4,21 @@
*
* See deferred.txt
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
* @ingroup Search
*/

View file

@ -59,16 +59,12 @@ class SpecialCategories extends SpecialPage {
* @ingroup SpecialPage Pager
*/
class CategoryPager extends AlphabeticPager {
private $conds = array( 'cat_pages > 0' );
function __construct( IContextSource $context, $from ) {
parent::__construct( $context );
$from = str_replace( ' ', '_', $from );
if( $from !== '' ) {
$from = Title::capitalize( $from, NS_CATEGORY );
$dbr = wfGetDB( DB_SLAVE );
$this->conds[] = 'cat_title >= ' . $dbr->addQuotes( $from );
$this->setOffset( '' );
$this->mOffset = $from;
}
}
@ -76,7 +72,7 @@ class CategoryPager extends AlphabeticPager {
return array(
'tables' => array( 'category' ),
'fields' => array( 'cat_title','cat_pages' ),
'conds' => $this->conds,
'conds' => array( 'cat_pages > 0' ),
'options' => array( 'USE INDEX' => 'cat_title' ),
);
}

View file

@ -3,10 +3,34 @@
* @defgroup Watchlist Users watchlist handling
*/
/**
* Implements Special:EditWatchlist
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
* @ingroup SpecialPage
* @ingroup Watchlist
*/
/**
* Provides the UI through which users can perform editing
* operations on their watchlist
*
* @ingroup SpecialPage
* @ingroup Watchlist
* @author Rob Church <robchur@gmail.com>
*/

View file

@ -61,7 +61,8 @@ class SpecialEmailUser extends UnlistedSpecialPage {
),
'Subject' => array(
'type' => 'text',
'default' => wfMsgExt( 'defemailsubject', array( 'content', 'parsemag' ), $this->getUser()->getName() ),
'default' => $this->msg( 'defemailsubject',
$this->getUser()->getName() )->inContentLanguage()->text(),
'label-message' => 'emailsubject',
'maxlength' => 200,
'size' => 60,
@ -124,11 +125,11 @@ class SpecialEmailUser extends UnlistedSpecialPage {
$this->mTargetObj = $ret;
$form = new HTMLForm( $this->getFormFields(), $this->getContext() );
$form->addPreText( wfMsgExt( 'emailpagetext', 'parseinline' ) );
$form->setSubmitText( wfMsg( 'emailsend' ) );
$form->addPreText( $this->msg( 'emailpagetext' )->parse() );
$form->setSubmitTextMsg( 'emailsend' );
$form->setTitle( $this->getTitle() );
$form->setSubmitCallback( array( __CLASS__, 'submit' ) );
$form->setWrapperLegend( wfMsgExt( 'email-legend', 'parsemag' ) );
$form->setSubmitCallback( array( __CLASS__, 'uiSubmit' ) );
$form->setWrapperLegendMsg( 'email-legend' );
$form->loadData();
if( !wfRunHooks( 'EmailUserForm', array( &$form ) ) ) {
@ -224,14 +225,26 @@ class SpecialEmailUser extends UnlistedSpecialPage {
$string = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript, 'id' => 'askusername' ) ) .
Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) .
Xml::openElement( 'fieldset' ) .
Html::rawElement( 'legend', null, wfMessage( 'emailtarget' )->parse() ) .
Xml::inputLabel( wfMessage( 'emailusername' )->text(), 'target', 'emailusertarget', 30, $name ) . ' ' .
Xml::submitButton( wfMessage( 'emailusernamesubmit' )->text() ) .
Html::rawElement( 'legend', null, $this->msg( 'emailtarget' )->parse() ) .
Xml::inputLabel( $this->msg( 'emailusername' )->text(), 'target', 'emailusertarget', 30, $name ) . ' ' .
Xml::submitButton( $this->msg( 'emailusernamesubmit' )->text() ) .
Xml::closeElement( 'fieldset' ) .
Xml::closeElement( 'form' ) . "\n";
return $string;
}
/**
* Submit callback for an HTMLForm object, will simply call submit().
*
* @since 1.20
* @param $data array
* @param $form HTMLForm object
* @return Status|string|bool
*/
public static function uiSubmit( array $data, HTMLForm $form ) {
return self::submit( $data, $form->getContext() );
}
/**
* Really send a mail. Permissions should have been checked using
* getPermissionsError(). It is probably also a good
@ -240,25 +253,22 @@ class SpecialEmailUser extends UnlistedSpecialPage {
* @return Mixed: Status object, or potentially a String on error
* or maybe even true on success if anything uses the EmailUser hook.
*/
public static function submit( $data ) {
public static function submit( array $data, IContextSource $context ) {
global $wgUser, $wgUserEmailUseReplyTo;
$target = self::getTarget( $data['Target'] );
if( !$target instanceof User ) {
return wfMsgExt( $target . 'text', 'parse' );
return $context->msg( $target . 'text' )->parseAsBlock();
}
$to = new MailAddress( $target );
$from = new MailAddress( $wgUser );
$from = new MailAddress( $context->getUser() );
$subject = $data['Subject'];
$text = $data['Text'];
// Add a standard footer and trim up trailing newlines
$text = rtrim( $text ) . "\n\n-- \n";
$text .= wfMsgExt(
'emailuserfooter',
array( 'content', 'parsemag' ),
array( $from->name, $to->name )
);
$text .= $context->msg( 'emailuserfooter',
$from->name, $to->name )->inContentLanguage()->text();
$error = '';
if( !wfRunHooks( 'EmailUser', array( &$to, &$from, &$subject, &$text, &$error ) ) ) {
@ -302,11 +312,8 @@ class SpecialEmailUser extends UnlistedSpecialPage {
// unless they are emailing themselves, in which case one
// copy of the message is sufficient.
if ( $data['CCMe'] && $to != $from ) {
$cc_subject = wfMsg(
'emailccsubject',
$target->getName(),
$subject
);
$cc_subject = $context->msg( 'emailccsubject' )->rawParams(
$target->getName(), $subject )->text();
wfRunHooks( 'EmailUserCC', array( &$from, &$from, &$cc_subject, &$text ) );
$ccStatus = UserMailer::send( $from, $from, $cc_subject, $text );
$status->merge( $ccStatus );

View file

@ -1,7 +1,24 @@
<?php
/**
* @ingroup SpecialPage
* Implements Special:JavaScriptTest
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
* @ingroup SpecialPage
*/
/**

View file

@ -90,7 +90,8 @@ class SpecialMergeHistory extends SpecialPage {
$this->outputHeader();
if( $this->mTargetID && $this->mDestID && $this->mAction == 'submit' && $this->mMerge ) {
return $this->merge();
$this->merge();
return;
}
if ( !$this->mSubmitted ) {

View file

@ -174,7 +174,8 @@ class SpecialSearch extends SpecialPage {
$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 );
@ -201,7 +202,7 @@ class SpecialSearch extends SpecialPage {
return;
}
}
return $this->showResults( $term );
$this->showResults( $term );
}
/**

View file

@ -1,5 +1,7 @@
<?php
/**
* Implements Special:Unblock
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or

View file

@ -1159,6 +1159,7 @@ $2
هذا يحدث أحيانا عندما تستخدم خدمة بروكسي مجهول معيبة مبنية على الوب.'''",
'edit_form_incomplete' => "'''بعض أجزاء من نموذج التعديل لم تصل إلى الخادم؛ تأكد من أن تعديلاتك لم تمس وحاول مجددا.'''",
'editing' => 'تحرير $1',
'creating' => 'إنشاء $1',
'editingsection' => 'تحرير $1 (قسم)',
'editingcomment' => 'تعديل $1 (قسم جديد)',
'editconflict' => 'تضارب في التحرير: $1',

View file

@ -351,10 +351,13 @@ $1',
'internalerror' => 'ܦܘܕܐ ܓܘܝܐ',
'internalerror_info' => 'ܦܘܕܐ ܓܘܝܐ: $1',
'badtitle' => 'ܟܘܢܝܐ ܠܐ ܛܒܐ',
'perfcached' => 'ܓܠܝܬ̈ܐ ܗܠܝܢ ܐܣܢܝܢ ܐܢܘܢ ܘܡܬܡܨܝܢܬܐ ܐܝܬܝܗܝ ܕܠܐ ܢܗܘܢ ܚܘ̈ܕܬܐ. ܡܬܚܐ ܥܠܝܐ ܕ {{PLURAL:$1|ܚܕ ܦܠܛܐ|$1 ܦܠܛ̈ܐ}} ܐܝܬ ܒܐܣܢܐ.',
'perfcachedts' => 'ܓܠܝܬ̈ܐ ܗܠܝܢ ܐܣܢܝܢ ܐܢܘܢ ܘܚܘܕܬܐ ܐܚܪܝܐ ܗܘܐ ܒ $1. ܡܬܚܐ ܥܠܝܐ ܕ {{PLURAL:$4|ܚܕ ܦܠܛܐ|$4 ܦܠܛ̈ܐ}} ܐܝܬ ܒܐܣܢܐ.',
'viewsource' => 'ܚܙܝ ܡܒܘܥܐ',
'actionthrottled' => 'ܠܐ ܘܪܕ ܠܡܥܒܕ ܝܬܝܪ ܡܢ ܐܗܐ ܥܒܕܐ',
'viewsourcetext' => 'ܡܨܐ ܐܢܬ ܠܚܙܝܐ ܘܢܣܚܐ ܠܡܒܘܥ̈ܐ ܕܐܗܐ ܦܐܬܐ:',
'protectedinterface' => 'ܐܗܐ ܦܐܬܐ ܡܘܬܪܐ ܟܬܝܒܬܐ ܕܦܐܬܐ ܠܚܘܪܙܐ, ܘܐܝܠܗ ܢܛܪܬܐ ܠܡܘܢܥܐ ܚܪܒܐ.',
'viewsource-title' => 'ܚܙܝ ܡܒܘܥܐ ܕ $1',
'actionthrottled' => 'ܠܐ ܡܬܡܨܝܢܬܐ ܐܝܬܝܗܝ ܠܡܥܒܕ ܝܬܝܪ ܡܢ ܗܢܐ ܥܒܕܐ',
'viewsourcetext' => 'ܡܨܐ ܐܢܬ ܕܢܚܙܐ ܘܢܣܚܐ ܠܡܒܘ̈ܥܐ ܕܗܕܐ ܦܐܬܐ:',
'protectedinterface' => 'ܗܕܐ ܦܐܬܐ ܡܘܬܪܐ ܟܬܝܒܬܐ ܕܦܐܬܐ ܠܚܘܪܙܐ, ܘܐܝܬܝܗܝ ܢܛܪܬܐ ܠܡܘܢܥ ܚܘܒܠܐ.',
'editinginterface' => "''ܙܘܗܪܐ:''' ܐܢܬ ܥܒܕܬ ܫܚܠܦܬܐ ܒܦܐܬܐ ܡܬܦܠܚܬ ܠܡܘܬܘܪ̈ܐ ܦܐܬܘܬ̈ܐ ܟܬܝܒ̈ܐ ܠܚܘܪܙܐ.
ܟܠ ܫܘܚܠܦܐ ܒܐܗܐ ܦܐܬܐ ܒܕ ܥܒܕ ܟܪ ܥܠ ܡܚܙܝܬܐ ܦܐܬܐ ܕܡܦܠܚܢܐ ܠܡܦܠܚܢ̈ܐ ܐܚܪ̈ܝܢܐ.
ܠܬܘܪ̈ܓܡܐ، ܡܦܠܚ ܬܪܡܝܬܐ ܬܘܪܓܡܐ ܕܡܝܕܝܐܘܝܩܝ [//translatewiki.net/wiki/Main_Page?setlang=ar translatewiki.net].",
@ -926,7 +929,7 @@ $1',
ܗܫܐ ܐܝܬܝܗܝ ܨܘܝܒܐ ܠ [[$2]].',
'brokenredirects' => 'ܨܘܝܒ̈ܐ ܬܒܝܪ̈ܐ',
'brokenredirectstext' => 'ܨܘ̈ܝܒܐ ܗܠܝܢ ܡܛܝܢ ܠܕ̈ܦܐ ܕܠܝܬ:',
'brokenredirectstext' => 'ܨܘ̈ܝܒܐ ܗܠܝܢ ܡܛܝܢ ܠܕ̈ܦܐ ܕܠܝܬܠܗܘܢ ܐܝܬܘܬܐ:',
'brokenredirects-edit' => 'ܫܚܠܦ',
'brokenredirects-delete' => 'ܫܘܦ',

View file

@ -801,7 +801,7 @@ $1ৰ দ্বাৰা এই অৱৰোধ কৰা হৈছে ।
কোনো আসোঁৱাহপূৰ্ণ ৱেব-ভিত্তিক বেনামী প্ৰক্সী সেৱা ব্যৱহাৰ কৰিলে এনে হ’ব পাৰে ",
'edit_form_incomplete' => "'''এই সম্পাদনাৰ কিছু অংশ চাৰ্ভাৰলৈ নগ’ল; আপোনাৰ সম্পাদনা ঠিকে আছেনে পৰীক্ষা কৰি পুনৰ চেষ্টা কৰক ।'''",
'editing' => '$1 সম্পাদনা',
'creating' => 'সৃষ্টি কৰি থকা হৈছে $1',
'creating' => '$1 পৃষ্ঠাখন আপুনি সৃষ্টি কৰি আছে',
'editingsection' => '$1 (বিভাগ) সম্পাদনা কৰি থকা হৈছে',
'editingcomment' => '$1 (নতুন বিভাগ) সম্পাদনা কৰি থকা হৈছে',
'editconflict' => 'সম্পাদনা দ্বন্দ: $1',
@ -870,6 +870,7 @@ $1ৰ দ্বাৰা এই অৱৰোধ কৰা হৈছে ।
'edit-no-change' => 'আপোনাৰ সম্পাদনা আওকাণ কৰা হৈছে, কাৰণ লেখাত কোনো তফাৎ নাই',
'edit-already-exists' => "নতুন পৃষ্ঠা সৃষ্টি কৰা নহ'ল ।
পৃষ্ঠাখন ইতিমধ্যে আছেই ",
'defaultmessagetext' => 'সাধাৰণ বাৰ্তা পাঠ্য',
# Parser/template warnings
'expensive-parserfunction-warning' => "'''সতৰ্কবাণী:''' এই পৃষ্ঠাখনত অধিক এক্সপেনচিভ পাৰ্চাৰ ফাংচন কল আছে ।
@ -986,7 +987,7 @@ $3 এ আগবঢ়োৱা ইয়াৰ কাৰণ হ’ল ''$2''",
'revdelete-legend' => 'দৃষ্টিপাত সীমাবদ্ধ কৰক',
'revdelete-hide-text' => 'সংশোধিত পাঠ গোপন কৰক',
'revdelete-hide-image' => 'ফাইলৰ বিষয়বস্তু গোপন কৰক',
'revdelete-hide-name' => 'কাৰ্য্য আৰু লক্ষ্য গোপন কৰক',
'revdelete-hide-name' => 'কাৰ্য আৰু লক্ষ্য গোপন কৰক',
'revdelete-hide-comment' => 'সম্পাদনা মন্তব্য আতৰাই থওক',
'revdelete-hide-user' => 'সম্পাদকৰ সদস্যনাম/আই-পি ঠিকনা গোপন কৰক',
'revdelete-hide-restricted' => 'প্ৰশাসকবৃন্দৰ লগতে আনৰ পৰাও তথ্য ৰোধ কৰক',
@ -1007,7 +1008,7 @@ $1",
'revdel-restore-deleted' => 'বাতিল কৰা সংশোধনসমূহ',
'revdel-restore-visible' => 'দৃশ্যমান সংশোধনসমূহ',
'pagehist' => 'পৃষ্ঠা ইতিহাস',
'deletedhist' => 'মচি পেলোৱা ইতিহাস',
'deletedhist' => 'বিলোপ কৰাৰ ইতিহাস',
'revdelete-hide-current' => ' $2, $1 তাৰিখৰ এই আইটেমটো গোপন কৰাত সমস্যা হৈছে: এইটো বৰ্তমানৰ সংশোধনী
এইটোক গোপন কৰিব পৰা নাযাব ',
'revdelete-show-no-access' => '$2, $1 তাৰিখৰ এই আইটেমটো দেখুওৱাত সমস্যা হৈছে: এই আইটেমটো "সীমাবদ্ধ" হিছাপে চিহ্নিত
@ -1073,7 +1074,7 @@ $1",
'showhideselectedversions' => 'নিৰ্বাচিত সংশোধনসমূহ দেখুৱাওক/আঁৰ কৰক',
'editundo' => 'পূৰ্ববত কৰক',
'diff-multi' => '({{PLURAL:$2|এজন সদস্যৰ|$2জন সদস্যৰ}} দ্বাৰা {{PLURAL:$1|এটা মধ্যৱৰ্তী সংশোধন|$1-টা মধ্যৱৰ্তী সংশোধন}} দেখোৱা হোৱা নাই)',
'diff-multi-manyusers' => '({{PLURAL:$2|এজনতকৈ|$2-জনতকৈ}} অধিক সদস্যৰ দ্বাৰা {{PLURAL:$1|এটা মধ্যৱৰ্তী সংশোধন|$1-টা মধ্যৱৰ্তী সংশোধন}} দেখৱা হোৱা নাই)',
'diff-multi-manyusers' => '({{PLURAL:$2|এজনতকৈ|$2-জনতকৈ}} অধিক সদস্যৰ দ্বাৰা {{PLURAL:$1|এটা মধ্যৱৰ্তী সংশোধন|$1-টা মধ্যৱৰ্তী সংশোধন}} দেখুওৱা হোৱা নাই)',
# Search results
'searchresults' => 'অনুসন্ধানৰ ফলাফল',
@ -1591,7 +1592,7 @@ $1",
'filename-bad-prefix' => "আপুনি আপলোড কৰা ফাইলৰ নামটো '''\"\$1\"''' দি আৰম্ভ হৈছে, যিটো ডিজিটেল কেমেৰাই স্বয়ংক্ৰিয়ভাৱে দিয়ে আৰু সি ব্যাখ্যামূলক নহয় ।
অনুগ্ৰহ কৰি আপোনাৰ ফাইলটোৰ বাবে এটা ব্যাখ্যামূলক নাম বাছি লওক ",
'upload-success-subj' => "আপলোড সফল হ'ল",
'upload-success-msg' => '[$2] ৰ পৰা আপোনাৰ আপলোড সফল হৈছে । এইটো ইয়াত উপলব্দ্ধ: [[:{{ns:file}}:$1]]',
'upload-success-msg' => '[$2] ৰ পৰা আপোনাৰ আপলোড সফল হৈছে । এইটো ইয়াত উপলদ্ধ: [[:{{ns:file}}:$1]]',
'upload-failure-subj' => 'আপল’ডত সমস্যা হৈছে',
'upload-failure-msg' => '[$2] পৰা আপুনি কৰা আপল’ডত এটা সমস্যাই দেখা দিছে:
@ -1980,6 +1981,9 @@ https://www.mediawiki.org/wiki/Manual:Image_Authorization চাওক।",
'allpages-bad-ns' => '{{SITENAME}} ত কোনো "$1" নামস্থান নাই ।',
'allpages-hide-redirects' => 'পুনঃনিৰ্দেশ লুকুৱাওক',
# SpecialCachedPage
'cachedspecial-refresh-now' => 'শেহতীয়া পাঠ্য',
# Special:Categories
'categories' => 'শ্ৰেণী',
'categoriespagetext' => 'এই {{PLURAL:$1|বিষয়শ্ৰেণীত|বিষয়শ্ৰেণীসমূহত}} পৃষ্ঠা বা মিডিয়া ফাইল আছে
@ -3742,4 +3746,15 @@ You can also [[Special:EditWatchlist/raw|edit the raw list]].',
'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|সহস্ৰাব্দ|সহস্ৰাব্দ}}',
);

View file

@ -765,6 +765,7 @@ Les páxines personalizaes .css y .js usen un títulu en minúscules, p. ex. {{n
'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.'''",

View file

@ -3169,6 +3169,7 @@ $4-এ নিশ্চিতকরণ কোডটি মেয়াদোত
'feedback-error1' => 'ত্রুটি: এপিআই থেকে অজানা ফলাফল এসেছে',
'feedback-error2' => 'ত্রুটি: সম্পাদনা ব্যর্থ',
'feedback-error3' => 'ত্রুটি: এপিআই থেকে কোন সাড়া নেই',
'feedback-close' => 'সম্পন্ন',
# API errors
'api-error-badaccess-groups' => 'আপনার এই উইকিতে ফাইল আপলোডের অনুমতি নেই।',
@ -3206,4 +3207,15 @@ $4-এ নিশ্চিতকরণ কোডটি মেয়াদোত
'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|সহস্রাব্দ|সহস্রাব্দ}}',
);

View file

@ -1023,6 +1023,7 @@ Zde je pro přehled zobrazen nejnovější záznam z knihy zablokování:',
'note' => "'''Poznámka:'''&nbsp;",
'previewnote' => "'''Pamatujte, že toto je pouze náhled.'''
Změny zatím nebyly uloženy!",
'continue-editing' => 'Pokračovat v editaci',
'previewconflict' => 'Tento náhled ukazuje text tak, jak bude vypadat po uložení stránky.',
'session_fail_preview' => "'''Váš požadavek se nepodařilo zpracovat kvůli ztrátě dat z relace.
Zkuste to prosím znovu.
@ -3823,6 +3824,9 @@ MediaWiki je distribuována v naději, že bude užitečná, avšak BEZ JAKÉKOL
'version-software' => 'Nainstalovaný software',
'version-software-product' => 'Název',
'version-software-version' => 'Verze',
'version-entrypoints' => 'URL vstupních bodů',
'version-entrypoints-header-entrypoint' => 'Vstupní bod',
'version-entrypoints-header-url' => 'URL',
# Special:FilePath
'filepath' => 'Cesta k souboru',

View file

@ -859,6 +859,7 @@ Loggen over den seneste blokering ses nedenfor:',
'note' => "'''Bemærk:'''",
'previewnote' => "'''Husk at dette er kun en forhåndsvisning.'''
Dine ændringer er endnu ikke blevet gemt!",
'continue-editing' => 'Fortsæt med at redigere',
'previewconflict' => 'Denne forhåndsvisning er resultatet af den redigérbare tekst ovenfor, sådan vil det komme til at se ud hvis du vælger at gemme teksten.',
'session_fail_preview' => "'''Din ændring kunne ikke gemmes, da dine sessionsdata er gået tabt.
Prøv venligst igen. Hvis problemet fortsætter, log af og log igen.'''",

View file

@ -1410,7 +1410,7 @@ Stelle sicher, dass die Versionsgeschichte einer Seite historisch korrekt ist.',
'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',

View file

@ -811,6 +811,7 @@ Nejnowšy zapisk blokěrowańskego protokola pódawa se dołojce ako referenca:'
'note' => "'''Pokazka:'''",
'previewnote' => "'''Wobmysli, až to jo jano pśeglěd.'''
Twóje změny hyšći njejsu składowane!",
'continue-editing' => 'Dalej wobźěłaś',
'previewconflict' => 'Toś ten pśeglěd wótbłyšćujo tekst górjejcnego póla. Bok buźo tak wuglědaś, jolic jen něnto składujoš.',
'session_fail_preview' => "'''Wódaj! Twójo wobźěłanje njejo se mógało składowaś, dokulaž su daty twójogo pósejźenja se zgubili. Pšosym wopytaj hyšći raz. Jolic až to pón pśecej hyšći njejźo, wopytaj se wótzjawiś a zasej pśizjawiś.'''",
'session_fail_preview_html' => "'''Wódaj! Twójo wobźěłanje njejo se mógało składowaś, dokulaž su daty twójogo pósejźenja se zgubili.'''

View file

@ -1479,6 +1479,10 @@ These arguments have been omitted.",
'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.

View file

@ -41,6 +41,7 @@
* @author Hercule
* @author Icvav
* @author Imre
* @author Invadinado
* @author Jatrobat
* @author Jens Liebenau
* @author Jurock
@ -365,15 +366,15 @@ $messages = array(
'tog-previewontop' => 'Mostrar previsualización antes del cuadro de edición',
'tog-previewonfirst' => 'Mostrar previsualización en la primera edición',
'tog-nocache' => 'Desactivar la caché de páginas del navegador',
'tog-enotifwatchlistpages' => 'Enviarme un correo electrónico cuando una página en mi lista de seguimiento sea modificada',
'tog-enotifusertalkpages' => 'Enviarme un correo electrónico cuando mi página de discusión sea modificada',
'tog-enotifwatchlistpages' => 'Enviarme un correo electrónico cuando se modifique una página en mi lista de seguimiento',
'tog-enotifusertalkpages' => 'Enviarme un correo electrónico cuando se modifique mi página de discusión',
'tog-enotifminoredits' => 'Notificarme también los cambios menores de páginas',
'tog-enotifrevealaddr' => 'Revelar mi dirección de correo electrónico en los correos de notificación',
'tog-shownumberswatching' => 'Mostrar el número de usuarios que la vigilan',
'tog-oldsig' => 'Firma actual:',
'tog-fancysig' => 'Tratar firma como wikitexto (sin un enlace automático)',
'tog-externaleditor' => 'Utilizar editor externo por defecto (sólo para expertos pues necesitas ajustes especiales en tu ordenador. [//www.mediawiki.org/wiki/Manual:External_editors Más información.])',
'tog-externaldiff' => 'Utilizar diff externo por defecto (sólo para expertos pues necesitas ajustes especiales en tu ordenador. [//www.mediawiki.org/wiki/Manual:External_editors Más información.])',
'tog-externaleditor' => 'Utilizar editor externo por defecto (sólo para expertos, pues necesitas ajustes especiales en tu ordenador; [//www.mediawiki.org/wiki/Manual:External_editors más información])',
'tog-externaldiff' => 'Utilizar diff externo por defecto (sólo para expertos, pues necesitas ajustes especiales en tu ordenador; [//www.mediawiki.org/wiki/Manual:External_editors más información])',
'tog-showjumplinks' => 'Habilitar enlaces de accesibilidad «saltar a»',
'tog-uselivepreview' => 'Usar live preview (JavaScript) (Experimental)',
'tog-forceeditsummary' => 'Alertar al grabar sin resumen de edición.',
@ -461,7 +462,7 @@ $messages = array(
'category-empty' => "''La categoría no contiene actualmente ningún artículo o archivo multimedia.''",
'hidden-categories' => '{{PLURAL:$1|Categoría escondida|Categorías escondidas}}',
'hidden-category-category' => 'Categorías ocultas',
'category-subcat-count' => '{{PLURAL:$2|Esta categoría comprende solamente la siguiente categoría.|Esta categoría incluye {{PLURAL:$1|la siguiente categorías|las siguientes $1 subcategorías}}, de un total de $2.}}',
'category-subcat-count' => '{{PLURAL:$2|Esta categoría solo contiene la siguiente subcategoría.|Esta categoría contiene {{PLURAL:$1|la siguiente subcategoría|las siguientes $1 subcategorías}}, de un total de $2.}}',
'category-subcat-count-limited' => 'Esta categoría contiene {{PLURAL:$1|la siguiente subcategoría|las siguientes $1 subcategorías}}.',
'category-article-count' => '{{PLURAL:$2|Esta categoría incluye solamente la siguiente página.|{{PLURAL:$1|La siguiente página página pertenece|Las siguientes $1 páginas pertenecen}} a esta categoría, de un total de $2.}}',
'category-article-count-limited' => '{{PLURAL:$1|La siguiente página pertenece|Las siguientes $1 páginas pertenecen}} a esta categoría.',
@ -469,12 +470,12 @@ $messages = array(
'category-file-count-limited' => '{{PLURAL:$1|El siguiente fichero pertenece|Los siguientes $1 ficheros pertenecen}} a esta categoría.',
'listingcontinuesabbrev' => 'cont.',
'index-category' => 'Páginas indexadas',
'noindex-category' => 'Páginas no indizadas',
'noindex-category' => 'Páginas no indexadas',
'broken-file-category' => 'Páginas con enlaces rotos a archivos',
'about' => 'Acerca de',
'article' => 'Artículo',
'newwindow' => '(Se abre en una ventana nueva)',
'newwindow' => '(se abre en una ventana nueva)',
'cancel' => 'Cancelar',
'moredotdotdot' => 'Más...',
'mypage' => 'Mi página',
@ -834,7 +835,7 @@ Puedes ignorar este mensaje si esta cuenta fue creada por error.',
'login-throttled' => 'Has intentado demasiadas veces iniciar sesión. Por favor espera antes de intentarlo nuevamente.',
'login-abort-generic' => 'Tu inicio de sesión no fue exitoso - Cancelado',
'loginlanguagelabel' => 'Idioma: $1',
'suspicious-userlogout' => 'Tu solicitud de desconexión ha sido denegada debido a que parece que ésta ha sido enviada desde un navegador defectuoso o un proxy caché.',
'suspicious-userlogout' => 'Tu solicitud de desconexión ha sido denegada, pues parece haber sido enviada desde un navegador defectuoso o un proxy caché.',
# E-mail sending
'php-mail-error-unknown' => 'Error desconocido en la función mail() de PHP',
@ -942,7 +943,7 @@ Contraseña temporal: $2',
Tu dirección IP se almacenará en el historial de ediciones de la página.",
'anonpreviewwarning' => "''No has iniciado sesión con una cuenta de usuario. Al guardar los cambios se almacenará tu dirección IP en el historial de edición de la página.''",
'missingsummary' => "'''Atención:''' No has escrito un resumen de edición. Si haces clic nuevamente en «{{int:savearticle}}» tu edición se grabará sin él.",
'missingcommenttext' => 'Por favor introduce texto debajo.',
'missingcommenttext' => 'Por favor, introduce un texto debajo.',
'missingcommentheader' => "'''Recordatorio:''' No has escrito un título para este comentario. Si haces clic nuevamente en \"{{int:savearticle}}\" tu edición se grabará sin él.",
'summary-preview' => 'Previsualización del resumen:',
'subject-preview' => 'Previsualización del tema/título:',
@ -1026,6 +1027,7 @@ La última entrada del registro de bloqueos se proporciona debajo para mayor ref
'note' => "'''Nota:'''",
'previewnote' => "'''¡Recuerda que esto es solo una previsualización.'''
¡Tus cambios aún no se ha guardado!",
'continue-editing' => 'Continuar editando',
'previewconflict' => 'Esta previsualización refleja el texto en el área de edición superior como aparecerá una vez guardados los cambios.',
'session_fail_preview' => "'''Lo sentimos, no pudimos procesar la edición debido a una pérdida de los datos de sesión.'''
Por favor, inténtalo de nuevo.
@ -1096,7 +1098,7 @@ El registro de borrado y traslados para esta página están provistos aquí por
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.',

View file

@ -953,6 +953,7 @@ Allpool on toodud viimane blokeerimislogi sissekanne:',
'note' => "'''Meeldetuletus:'''",
'previewnote' => "'''Ära unusta, et see on kõigest eelvaade!'''
Sinu muudatused pole veel salvestatud!",
'continue-editing' => 'Jätka redigeerimist',
'previewconflict' => 'See eelvaade näitab, kuidas ülemises toimetuskastis olev tekst hakkab välja nägema, kui otsustate salvestada.',
'session_fail_preview' => "'''Vabandust! Meil ei õnnestunud seansiandmete kaotuse tõttu sinu muudatust töödelda.'''
Palun proovi uuesti.

View file

@ -1129,6 +1129,7 @@ $2
'note' => "'''نکته:'''",
'previewnote' => "'''به یاد داشته باشید که این فقط پیش‌نمایش است.'''
تغییرات شما هنوز ذخیره نشده‌است!",
'continue-editing' => 'ادامهٔ ویرایش',
'previewconflict' => 'این پیش‌نمایش منعکس‌کنندهٔ متن ناحیهٔ ویرایش متن بالایی است، به شکلی که اگر متن را ذخیره کنید نمایش خواهد یافت.',
'session_fail_preview' => "'''شرمنده! به علت از دست رفتن اطلاعات نشست کاربری نمی‌توانیم ویرایش شما را پردازش کنیم.'''
لطفاً دوباره سعی کنید.
@ -4091,7 +4092,7 @@ $5
'logentry-newusers-newusers' => '$1 یک حساب کاربری ایجاد کرد',
'logentry-newusers-create' => '$1 یک حساب کاربری ایجاد کرد',
'logentry-newusers-create2' => '$1 یک حساب کاربری ایجاد کرد $3',
'logentry-newusers-autocreate' => 'کاروری حساب $1 بساتن به شکل خودکار',
'logentry-newusers-autocreate' => 'حساب $1 به شکل خودکار ساخته شد',
'newuserlog-byemail' => 'گذرواژه با پست الکترونیکی ارسال شد',
# Feedback

View file

@ -689,6 +689,9 @@ $2',
'customjsprotected' => 'Sinulla ei ole oikeutta muuttaa tätä JavaScript-sivua, koska se sisältää toisen käyttäjän henkilökohtaisia asetuksia.',
'ns-specialprotected' => 'Toimintosivuja ei voi muokata.',
'titleprotected' => "Käyttäjä [[User:$1|$1]] on asettanut tämän sivun luontikieltoon: ''$2''.",
'filereadonlyerror' => 'Tiedostoa "$1" ei voi muuttaa, koska jaettu mediavarasto "$2" on "vain luku" -tilassa.
Lukituksen asettanut ylläpitäjä on antanut seuraavan syyn toimenpiteelle: "$3".',
# Virus scanner
'virus-badscanner' => "Virheellinen asetus: Tuntematon virustutka: ''$1''",
@ -765,6 +768,7 @@ Tästä johtuen tästä IP-osoitteesta ei voi tällä hetkellä luoda uusia tunn
'emailconfirmlink' => 'Varmenna sähköpostiosoite',
'invalidemailaddress' => 'Sähköpostiosoitetta ei voida hyväksyä, koska se ei ole oikeassa muodossa. Ole hyvä ja anna oikea sähköpostiosoite tai jätä kenttä tyhjäksi.',
'cannotchangeemail' => 'Tunnuksien sähköpostiosoitteita ei voi muuttaa tässä wikissä.',
'emaildisabled' => 'Tältä sivustolta ei voi lähettää sähköpostia.',
'accountcreated' => 'Käyttäjätunnus luotiin',
'accountcreatedtext' => 'Käyttäjän $1 käyttäjätunnus luotiin.',
'createaccount-title' => 'Tunnuksen luominen {{GRAMMAR:illative|{{SITENAME}}}}',
@ -961,7 +965,8 @@ Alla on viimeisin estolokin tapahtuma:',
'userinvalidcssjstitle' => "'''Varoitus:''' Tyyliä nimeltä ”$1” ei ole olemassa. Muista, että käyttäjän määrittelemät .css- ja .js-sivut alkavat pienellä alkukirjaimella, esim. {{ns:user}}:Matti Meikäläinen/vector.css eikä {{ns:user}}:Matti Meikäläinen/Vector.css.",
'updated' => '(Päivitetty)',
'note' => "'''Huomautus:'''",
'previewnote' => "'''Tämä on vasta sivun esikatselu. Sivua ei ole vielä tallennettu!'''",
'previewnote' => "'''Tämä on vasta sivun esikatselu. Tekemiäsi muokkauksia ei ole vielä tallennettu!'''",
'continue-editing' => 'Jatka muokkaamista',
'previewconflict' => 'Tämä esikatselu näyttää miltä muokkausalueella oleva teksti näyttää tallennettuna.',
'session_fail_preview' => "'''Muokkaustasi ei voitu tallentaa, koska istuntosi tiedot ovat kadonneet.''' Yritä uudelleen. Jos ongelma ei katoa, yritä [[Special:UserLogout|kirjautua ulos]] ja takaisin sisään.",
'session_fail_preview_html' => "'''Muokkaustasi ei voitu tallentaa, koska istuntosi tiedot ovat kadonneet.'''
@ -972,6 +977,7 @@ Yritä uudelleen. Jos ongelma ei katoa, yritä [[Special:UserLogout|kirjautua ul
'token_suffix_mismatch' => "'''Muokkauksesi on hylätty, koska asiakasohjelmasi ei osaa käsitellä välimerkkejä muokkaustarkisteessa. Syynä voi olla viallinen välityspalvelin.'''",
'edit_form_incomplete' => "'''Osa muokkauslomakkeesta ei saavuttanut palvelinta. Tarkista, että muokkauksesi ovat vahingoittumattomia ja yritä uudelleen.'''",
'editing' => 'Muokataan sivua $1',
'creating' => 'Sivun $1 luonti',
'editingsection' => 'Muokataan osiota sivusta $1',
'editingcomment' => 'Muokataan uutta osiota sivulla $1',
'editconflict' => 'Päällekkäinen muokkaus: $1',
@ -987,7 +993,7 @@ Sinun täytyy yhdistää muutoksesi olemassa olevaan tekstiin.
'yourdiff' => 'Eroavaisuudet',
'copyrightwarning' => "'''Muutoksesi astuvat voimaan välittömästi.''' Kaikki {{GRAMMAR:illative|{{SITENAME}}}} tehtävät tuotokset katsotaan julkaistuksi $2 -lisenssin mukaisesti ($1). Jos et halua, että kirjoitustasi muokataan armottomasti ja uudelleenkäytetään vapaasti, älä tallenna kirjoitustasi. Tallentamalla muutoksesi lupaat, että kirjoitit tekstisi itse, tai kopioit sen jostain vapaasta lähteestä. '''ÄLÄ KÄYTÄ TEKIJÄNOIKEUDEN ALAISTA MATERIAALIA ILMAN LUPAA!'''",
'copyrightwarning2' => "Huomaa, että kuka tahansa voi muokata, muuttaa ja poistaa kaikkia sivustolle tekemiäsi lisäyksiä ja muutoksia. Muokkaamalla sivustoa luovutat sivuston käyttäjille tämän oikeuden ja takaat, että lisäämäsi aineisto on joko itse kirjoittamaasi tai peräisin jostain vapaasta lähteestä. Lisätietoja sivulla $1. '''TEKIJÄNOIKEUDEN ALAISEN MATERIAALIN KÄYTTÄMINEN ILMAN LUPAA ON EHDOTTOMASTI KIELLETTYÄ!'''",
'longpageerror' => "'''Sivun koko on $1 binäärikilotavua. Sivua ei voida tallentaa, koska enimmäiskoko on $2 binäärikilotavua.'''",
'longpageerror' => "'''Virhe: Lähettämäsi tekstin pituus on {{PLURAL:$1|kilotavu|$1 kilotavua}}. Tekstiä ei voida tallentaa, koska se on pitempi kuin sallittu enimmäispituus {{PLURAL:$2|yksi kilotavu|$2 kilotavua}}.'''",
'readonlywarning' => "'''Varoitus: Tietokanta on lukittu huoltoa varten, joten et pysty tallentamaan muokkauksiasi juuri nyt.'''
Saattaa olla paras leikata ja liimata tekstisi omaan tekstitiedostoosi ja tallentaa se tänne myöhemmin.
@ -1026,6 +1032,7 @@ Se on ilmeisesti poistettu.',
'edit-no-change' => 'Muokkauksesi sivuutettiin, koska tekstiin ei tehty mitään muutoksia.',
'edit-already-exists' => 'Uuden sivun luominen ei onnistunut.
Se on jo olemassa.',
'defaultmessagetext' => 'Viestin oletusteksti',
# Parser/template warnings
'expensive-parserfunction-warning' => 'Tällä sivulla on liian monta hitaiden laajennusfunktioiden kutsua.
@ -1177,8 +1184,8 @@ Sinulla ei ole oikeutta siihen.',
# Suppression log
'suppressionlog' => 'Häivytysloki',
'suppressionlogtext' => 'Alla on lista uusimmista poistoista ja muokkausestoista, jotka sisältävät ylläpitäjiltä piilotettua materiaalia.
[[Special:BlockList|Muokkausestolistassa]] on tämänhetkiset muokkausestot.',
'suppressionlogtext' => 'Alla on luettelo poistoista ja muokkausestoista, jotka sisältävät ylläpitäjiltä piilotettua materiaalia.
[[Special:BlockList|Estolistassa]] on lueteltu voimassa olevat muokkauskiellot ja muokkausestot.',
# History merging
'mergehistory' => 'Yhdistä muutoshistoriat',
@ -1240,7 +1247,7 @@ Uuden ja vanhan sivun muutoksien pitää muodostaa jatkumo ne eivät saa men
$1 {{int:pipe-separator}} $2',
'searchmenu-legend' => 'Hakuasetukset',
'searchmenu-exists' => "'''Sivu [[:$1]] löytyy tästä wikistä.'''",
'searchmenu-exists' => "'''Tässä wikissä on sivu nimellä [[:$1]].'''",
'searchmenu-new' => "'''Luo sivu ''[[:$1]]'' tähän wikiin.'''",
'searchhelp-url' => 'Help:Sisällys',
'searchmenu-prefix' => '[[Special:PrefixIndex/$1|Selaa sivuja tällä etuliitteellä]]',
@ -1598,6 +1605,7 @@ Tässä satunnaisesti tuotettu arvo, jota voit käyttää: $1',
'newsectionsummary' => '/* $1 */ uusi osio',
'rc-enhanced-expand' => 'Näytä yksityiskohdat (JavaScript)',
'rc-enhanced-hide' => 'Piilota yksityiskohdat',
'rc-old-title' => 'alun perin luotu nimellä "$1"',
# Recent changes linked
'recentchangeslinked' => 'Linkitettyjen sivujen muutokset',
@ -1762,6 +1770,7 @@ $1',
'backend-fail-closetemp' => 'Väliaikaista tiedostoa ei voitu sulkea.',
'backend-fail-read' => 'Tiedostoa $1 ei voitu lukea.',
'backend-fail-create' => 'Tiedostoa $1 ei voitu luoda.',
'backend-fail-connect' => 'Varastojärjestelmään "$1" ei saada yhteyttä.',
# Lock manager
'lockmanager-notlocked' => 'Kohteen $1 lukitusta ei voitu poistaa, koska se ei ole lukittu.',
@ -1879,6 +1888,10 @@ Seuraava lista näyttää {{PLURAL:$1|ensimmäisen linkittävän sivun|$1 ensimm
Katso [$2 tiedoston kuvaussivulta] lisätietoja.',
'sharedupload-desc-here' => 'Tämä tiedosto on jaettu kohteesta $1 ja muut projektit saattavat käyttää sitä.
Tiedot [$2 tiedoston kuvaussivulta] näkyvät alla.',
'sharedupload-desc-edit' => 'Tämä tiedosto on jaettu kohteesta $1 ja muut projektit saattavat käyttää sitä.
Voit tarvittaessa muokata [$2 tiedoston kuvaussivua] kohteessa.',
'sharedupload-desc-create' => 'Tämä tiedosto on jaettu kohteesta $1 ja muut projektit saattavat käyttää sitä.
Voit tarvittaessa muokata [$2 tiedoston kuvaussivua] kohteessa.',
'filepage-nofile' => 'Tämän nimistä tiedostoa ei ole olemassa.',
'filepage-nofile-link' => 'Tämän nimistä tiedostoa ei ole olemassa, mutta voit [$1 tallentaa sen].',
'uploadnewversion-linktext' => 'Tallenna uusi versio tästä tiedostosta',
@ -2008,6 +2021,8 @@ Jokaisella rivillä on linkit ensimmäiseen ja toiseen ohjaukseen sekä toisen o
'wantedpages' => 'Halutut sivut',
'wantedpages-badtitle' => 'Virheellinen otsikko tuloksissa: $1',
'wantedfiles' => 'Halutut tiedostot',
'wantedfiletext-cat' => 'Seuraavia tiedostoja käytetään, mutta niitä ei ole olemassa. Ulkopuolissa mediavarastoissa olevat tiedostot voivat näkyä tällä listalla, vaikka ne ovat olemassa. Tällaiset väärät merkinnät on <del>yliviivattu</del>. Lisäksi sellaiset sivut, joihin on sisällytetty tiedostoja, jotka eivät ole olemassa, on luetteloitu [[:$1|täällä]].',
'wantedfiletext-nocat' => 'Seuraavia tiedostoja käytetään, mutta niitä ei ole olemassa. Ulkopuolissa mediavarastoissa olevat tiedostot voivat näkyä tällä listalla, vaikka ne ovat olemassa. Tällaiset väärät merkinnät on <del>yliviivattu</del.>',
'wantedtemplates' => 'Halutut mallineet',
'mostlinked' => 'Viitatuimmat sivut',
'mostlinkedcategories' => 'Viitatuimmat luokat',
@ -2085,6 +2100,12 @@ Voit rajoittaa listaa valitsemalla lokityypin, käyttäjän tai sivun johon muut
'allpagesprefix' => 'Katkaisuhaku',
'allpagesbadtitle' => 'Annettu otsikko oli kelvoton tai siinä oli wikien välinen etuliite.',
'allpages-bad-ns' => '{{GRAMMAR:inessive|{{SITENAME}}}} ei ole nimiavaruutta ”$1”.',
'allpages-hide-redirects' => 'Piilota ohjaussivut',
# SpecialCachedPage
'cachedspecial-viewing-cached-ttl' => 'Katselet arkistoitua versiota tästä sivusta, joka voi olla jopa $1 vanha.',
'cachedspecial-viewing-cached-ts' => 'Katselet arkistoitua versiota tästä sivusta, joka ei välttämättä ole sivun viimeisin versio.',
'cachedspecial-refresh-now' => 'Näytä uusin versio.',
# Special:Categories
'categories' => 'Luokat',
@ -2392,7 +2413,7 @@ Voit palauttaa versiota valikoivasti valitsemalla vain niiden versioiden valinta
'undeletedrevisions' => '{{PLURAL:$1|Yksi versio|$1 versiota}} palautettiin',
'undeletedrevisions-files' => '{{PLURAL:$1|Yksi versio|$1 versiota}} ja {{PLURAL:$2|yksi tiedosto|$2 tiedostoa}} palautettiin',
'undeletedfiles' => '{{PLURAL:$1|1 tiedosto|$1 tiedostoa}} palautettiin',
'cannotundelete' => 'Palauttaminen epäonnistui.',
'cannotundelete' => 'Palauttaminen epäonnistui; joku muu on voinut jo palauttaa sivun.',
'undeletedpage' => "'''$1 on palautettu.'''
[[Special:Log/delete|Poistolokista]] löydät listan viimeisimmistä poistoista ja palautuksista.",
@ -2505,7 +2526,7 @@ Alla on viimeisin estolokin tapahtuma:',
'badipaddress' => 'IP-osoite on väärin muotoiltu.',
'blockipsuccesssub' => 'Esto onnistui',
'blockipsuccesstext' => 'Käyttäjä tai IP-osoite [[Special:Contributions/$1|$1]] on estetty.<br />
Nykyiset estot löytyvät [[Special:BlockList|estolistalta]].',
Voimassa olevat estot näkyvät [[Special:BlockList|estolistasta]].',
'ipb-blockingself' => 'Olet estämässä itseäsi. Oletko varma, että haluat tehdä niin?',
'ipb-confirmhideuser' => 'Olet estämässä käyttäjää ”piilota käyttäjä” -toiminnon kanssa. Tämä piilottaa käyttäjän nimen kaikissa luetteloissa ja lokitapahtumissa. Oletko varma, että haluat tehdä näin?',
'ipb-edit-dropdown' => 'Muokkaa estosyitä',
@ -2558,7 +2579,7 @@ Alla on ote estolokista.',
Alla on ote häivytyslokista.',
'blocklogentry' => 'esti käyttäjän tai IP-osoitteen [[$1]]. Eston kesto $2 $3',
'reblock-logentry' => 'muutti käyttäjän tai IP-osoitteen [[$1]] eston asetuksia. Eston kesto $2 $3',
'blocklogtext' => 'Tämä on loki muokkausestoista ja niiden purkamisista. Automaattisesti estettyjä IP-osoitteita ei kirjata. Tutustu [[Special:BlockList|estolistaan]] nähdäksesi listan tällä hetkellä voimassa olevista estoista.',
'blocklogtext' => 'Tämä on loki muokkausestoista ja niiden purkamisista. Automaattisesti estettyjä IP-osoitteita ei kirjata. Tutustu [[Special:BlockList|estolistaan]] nähdäksesi luettelon tällä hetkellä voimassa olevista estoista.',
'unblocklogentry' => 'poisti käyttäjältä $1 muokkauseston',
'block-log-flags-anononly' => 'vain kirjautumattomat käyttäjät estetty',
'block-log-flags-nocreate' => 'tunnusten luonti estetty',
@ -2797,7 +2818,7 @@ Tallenna tiedot koneellesi ja tuo ne tällä sivulla.',
# JavaScriptTest
'javascripttest' => 'JavaScriptin testaus',
'javascripttest-disabled' => 'Tämä toiminto ei ole käytössä.',
'javascripttest-disabled' => 'Tämä toiminto ei ole käytössä tässä wikissä.',
'javascripttest-title' => 'Suoritetaan $1-testejä.',
'javascripttest-pagetext-noframework' => 'Tämä sivu on varattu JavaScript-testien suorittamiseen.',
'javascripttest-pagetext-unknownframework' => 'Tuntematon testausalusta $1.',
@ -3637,6 +3658,9 @@ Sinun olisi pitänyt saada [{{SERVER}}{{SCRIPTPATH}}/COPYING kopio GNU General P
'version-software' => 'Asennettu ohjelmisto',
'version-software-product' => 'Tuote',
'version-software-version' => 'Versio',
'version-entrypoints' => 'Aloituskohtien URL-osoitteet',
'version-entrypoints-header-entrypoint' => 'Aloituskohta',
'version-entrypoints-header-url' => 'URL',
# Special:FilePath
'filepath' => 'Tiedoston osoite',
@ -3795,6 +3819,7 @@ Muussa tapauksessa voit käyttää alla olevaa helpompaa lomaketta. Kommenttisi
'api-error-duplicate-archive-popup-title' => 'Tiedostolla on {{PLURAL:$1|poistettu kaksoiskappale|poistettuja kaksoiskappaleita}}',
'api-error-duplicate-popup-title' => 'Tiedoston {{PLURAL:$1|kaksoiskappale|kaksoiskappaleet}}',
'api-error-empty-file' => 'Määrittämäsi tiedosto on tyhjä.',
'api-error-emptypage' => 'Ei ole sallittua luoda uutta, tyhjää sivua.',
'api-error-fetchfileerror' => 'Sisäinen virhe: jotakin meni pieleen tiedoston haussa.',
'api-error-file-too-large' => 'Määrittämäsi tiedosto on liian iso.',
'api-error-filename-tooshort' => 'Tiedoston nimi on liian lyhyt.',
@ -3823,4 +3848,15 @@ Muussa tapauksessa voit käyttää alla olevaa helpompaa lomaketta. Kommenttisi
'api-error-uploaddisabled' => 'Tiedostojen tallentaminen ei ole käytössä.',
'api-error-verification-error' => 'Tiedosto voi olla vioittunut, tai sillä saattaa olla väärä tiedostopääte.',
# Durations
'duration-seconds' => '$1 {{PLURAL:$1|sekunti|sekuntia}}',
'duration-minutes' => '$1 {{PLURAL:$1|minuutti|minuuttia}}',
'duration-hours' => '$1 {{PLURAL:$1|tunti|tuntia}}',
'duration-days' => '$1 {{PLURAL:$1|päivä|päivää}}',
'duration-weeks' => '$1 {{PLURAL:$1|viikko|viikkoa}}',
'duration-years' => '$1 {{PLURAL:$1|vuosi|vuotta}}',
'duration-decades' => '$1 {{PLURAL:$1|vuosikymmen|vuosikymmentä}}',
'duration-centuries' => '$1 {{PLURAL:$1|vuosisata|vuosisataa}}',
'duration-millennia' => '$1 {{PLURAL:$1|vuosituhat|vuosituhatta}}',
);

View file

@ -1031,6 +1031,7 @@ La dernière entrée du registre des blocages est indiquée ci-dessous à titre
'note' => "'''Note :'''",
'previewnote' => "'''Rappelez-vous que ce nest quune prévisualisation.'''
Vos modifications nont pas encore été enregistrées !",
'continue-editing' => 'Continuer la modification',
'previewconflict' => 'Cette prévisualisation montre le texte de la boîte supérieure de modification tel quil apparaîtra si vous choisissez de le publier.',
'session_fail_preview' => "'''Nous ne pouvons enregistrer votre modification à cause dune perte dinformations concernant votre session.'''
Veuillez réessayer.

View file

@ -915,6 +915,7 @@ Lembre que as páxinas .css e .js personalizadas utilizan un título en minúscu
'updated' => '(Actualizado)',
'note' => "'''Nota:'''",
'previewnote' => "'''Lembre que esta é só unha vista previa e que aínda non gardou os seus cambios!'''",
'continue-editing' => 'Continuar editando',
'previewconflict' => 'Esta vista previa mostra o texto na área superior tal e como aparecerá se escolle gardar.',
'session_fail_preview' => "'''O sistema non pode procesar a súa edición porque se perderon os datos de inicio da sesión.
Por favor, inténteo de novo.

View file

@ -1042,6 +1042,7 @@ $2
'note' => "'''הערה:'''",
'previewnote' => "'''זכרו שזו רק תצוגה מקדימה.'''
השינויים שלכם טרם נשמרו!",
'continue-editing' => 'להמשך העריכה',
'previewconflict' => 'תצוגה מקדימה זו מציגה כיצד ייראה הטקסט בחלון העריכה העליון, אם תבחרו לשמור אותו.',
'session_fail_preview' => "'''לא ניתן לבצע את עריכתכם עקב אובדן מידע הכניסה.'''
אנא נסו שוב.

View file

@ -991,6 +991,7 @@ Stoga je uređivanje odbačeno da se spriječi uništavanje teksta stranice.
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',

Some files were not shown because too many files have changed in this diff Show more