images/[0-9a-f]
images/archive
images/deleted
+images/lockdir
images/temp
images/thumb
## Extension:EasyTimeline
* (bug 35685) api.php URL and other entry point URLs are now listed on
Special:Version
* Edit notices can now be translated.
-* jQuery upgraded to 1.8.1
+* jQuery upgraded to 1.8.2.
* jQuery UI upgraded to 1.8.23.
* QUnit upgraded from v1.2.0 to v1.10.0.
* (bug 37604) jquery.cookie upgraded to 2011 version.
* (bug 38904) prop=revisions&rvstart=... no longer blows up when continuing.
* (bug 39032) ApiQuery generates help in constructor.
* (bug 11142) Improve file extension blacklist error reporting in API upload.
-* (bug 39665) Cache AllowedGenerator array so it doesn't autoload all query classes
- on every request.
+* (bug 39665) List of query generators is now not built using reflection, instead it is
+ defined in code.
=== Languages updated in 1.20 ===
= MediaWiki release notes =
-Security reminder: MediaWiki does not require PHP's register_globals
-setting since version 1.2.0. If you have it on, turn it '''off''' if you can.
+Security reminder: MediaWiki does not require PHP's register_globals. If you
+have it on, turn it '''off''' if you can.
== MediaWiki 1.21 ==
=== Configuration changes in 1.21 ===
* (bug 29374) $wgVectorUseSimpleSearch is now enabled by default.
-* Deprecated $wgAllowRealName is removed. Use $wgHiddenPrefs[] = 'realname' instead
+* Deprecated $wgAllowRealName is removed. Use $wgHiddenPrefs[] = 'realname'
+ instead.
=== New features in 1.21 ===
+* (bug 34876) jquery.makeCollapsible has been improved in performance.
+* Added ContentHandler facility to allow extensions to support other content than wikitext.
+ See docs/contenthandler.txt for details.
+* New feature was developed for showing high-DPI thumbnails for high-DPI mobile
+ and desktop displays (configurable with $wgResponsiveImages).
+* Added new backend to represent and store information about sites and site
+ specific configuration.
+* jQuery UI upgraded from 1.8.23 to 1.8.24.
+* Added separate fa_sha1 field to filearchive table. This allows sha1
+ searches with the api in miser mode for deleted files.
+* Add initial and programmatic sorting for tablesorter.
+* Add the event "sortEnd.tablesorter", triggered after sorting has completed.
=== Bug fixes in 1.21 ===
* (bug 40353) SpecialDoubleRedirect should support interwiki redirects.
* (bug 40352) fixDoubleRedirects.php should support interwiki redirects.
* (bug 9237) SpecialBrokenRedirect should not list interwiki redirects.
-* (bug 34960) Drop unused fields rc_moved_to_ns and rc_moved_to_title from recentchanges table.
+* (bug 34960) Drop unused fields rc_moved_to_ns and rc_moved_to_title from
+ recentchanges table.
* (bug 32951) Do not register internal externals with absolute protocol,
when server has relative protocol.
+* (bug 39005) When purging proxies listed in $wgSquidServers using HTTP PURGE
+ method requests, we now send a Host header by default, for Varnish
+ compatibility. This also works with Squid in reverse-proxy mode. If you wish
+ to support Squid configured in forward-proxy mode, set
+ $wgSquidPurgeUseHostHeader to false.
=== API changes in 1.21 ===
+* prop=revisions can now report the contentmodel and contentformat, see docs/contenthandler.txt
+* action=edit and action=parse now support contentmodel and contentformat parameters to control the interpretation of
+ page content; See docs/contenthandler.txt for details.
* (bug 35693) ApiQueryImageInfo now suppresses errors when unserializing metadata.
+* (bug 40111) Disable minor edit for page/section creation by API
=== Languages updated in 1.21 ===
MediaWiki supports over 350 languages. Many localisations are updated
regularly. Below only new and removed languages are listed, as well as
changes to languages because of Bugzilla reports.
-
=== Other changes in 1.21 ===
== Compatibility ==
-MediaWiki 1.21 requires PHP 5.3.2. PHP 4 is no longer supported.
+MediaWiki 1.21 requires PHP 5.3.2 or later.
MySQL is the recommended DBMS. PostgreSQL or SQLite can also be used, but
support for them is somewhat less mature. There is experimental support for IBM
== Upgrading ==
1.21 has several database changes since 1.20, and will not work without schema
-updates.
+updates. Note that due to changes to some very large tables like the revision
+table, the schema update may take quite long (minutes on a medium sized site,
+many hours on a large site).
If upgrading from before 1.11, and you are using a wiki as a commons
repository, make sure that it is updated as well. Otherwise, errors may arise
--- /dev/null
+The ContentHandler facility adds support for arbitrary content types on wiki pages, instead of relying on wikitext
+for everything. It was introduced in MediaWiki 1.21.
+
+Each kind of content ("content model") supported by MediaWiki is identified by unique name. The content model determines
+how a page's content is rendered, compared, stored, edited, and so on.
+
+Built-in content types are:
+
+* wikitext - wikitext, as usual
+* javascript - user provided javascript code
+* css - user provided css code
+* text - plain text
+
+In PHP, use the corresponding CONTENT_MODEL_XXX constant.
+
+A page's content model is available using the Title::getContentModel() method. A page's default model is determined by
+ContentHandler::getDefaultModelFor($title) as follows:
+
+* The global setting $wgNamespaceContentModels specifies a content model for the given namespace.
+* The hook ContentHandlerDefaultModelFor may be used to override the page's default model.
+* Pages in NS_MEDIAWIKI and NS_USER default to the CSS or JavaScript model if they end in .js or .css, respectively.
+ Pages in NS_MEDIAWIKI default to the wikitext model otherwise.
+* The hook TitleIsCssOrJsPage may be used to force a page to use the CSS or JavaScript model.
+ This is a compatibility feature. The ContentHandlerDefaultModelFor hook should be used instead if possible.
+* The hook TitleIsWikitextPage may be used to force a page to use the wikitext model.
+ This is a compatibility feature. The ContentHandlerDefaultModelFor hook should be used instead if possible.
+* Otherwise, the wikitext model is used.
+
+Note that is currently no mechanism to convert a page from one content model to another, and there is no guarantee that
+revisions of a page will all have the same content model. Use Revision::getContentModel() to find it.
+
+
+== Architecture ==
+
+Two class hierarchies are used to provide the functionality associated with the different content models:
+
+* Content interface (and AbstractContent base class) define functionality that acts on the concrete content of a page, and
+* ContentHandler base class provides functionality specific to a content model, but not acting on concrete content.
+
+The most important function of ContentHandler is to act as a factory for the appropriate implementation of Content. These
+Content objects are to be used by MediaWiki everywhere, instead of passing page content around as text. All manipulation
+and analysis of page content must be done via the appropriate methods of the Content object.
+
+For each content model, a subclass of ContentHandler has to be registered with $wgContentHandlers. The ContentHandler
+object for a given content model can be obtained using ContentHandler::getForModelID( $id ). Also Title, WikiPage and
+Revision now have getContentHandler() methods for convenience.
+
+ContentHandler objects are singletons that provide functionality specific to the content type, but not directly acting
+on the content of some page. ContentHandler::makeEmptyContent() and ContentHandler::unserializeContent() can be used to
+create a Content object of the appropriate type. However, it is recommended to instead use WikiPage::getContent() resp.
+Revision::getContent() to get a page's content as a Content object. These two methods should be the ONLY way in which
+page content is accessed.
+
+Another important function of ContentHandler objects is to define custom action handlers for a content model, see
+ContentHandler::getActionOverrides(). This is similar to what WikiPage::getActionOverrides() was already doing.
+
+
+== Serialization ==
+
+With the ContentHandler facility, page content no longer has to be text based. Objects implementing the Content interface
+are used to represent and handle the content internally. For storage and data exchange, each content model supports
+at least one serialization format via ContentHandler::serializeContent( $content ). The list of supported formats for
+a given content model can be accessed using ContentHandler::getSupportedFormats().
+
+Content serialization formats are identified using MIME type like strings. The following formats are built in:
+
+* text/x-wiki - wikitext
+* text/javascript - for js pages
+* text/css - for css pages
+* text/plain - for future use, e.g. with plain text messages.
+* text/html - for future use, e.g. with plain html messages.
+* application/vnd.php.serialized - for future use with the api and for extensions
+* application/json - for future use with the api, and for use by extensions
+* application/xml - for future use with the api, and for use by extensions
+
+In PHP, use the corresponding CONTENT_FORMAT_XXX constant.
+
+Note that when using the API to access page content, especially action=edit, action=parse and action=query&prop=revisions,
+the model and format of the content should always be handled explicitly. Without that information, interpretation of
+the provided content is not reliable. The same applies to XML dumps generated via maintenance/dumpBackup.php or
+Special:Export.
+
+Also note that the API will provide encapsulated, serialized content - so if the API was called with format=json, and
+contentformat is also json (or rather, application/json), the page content is represented as a string containing an
+escaped json structure. Extensions that use JSON to serialize some types of page content may provide specialized API
+modules that allow access to that content in a more natural form.
+
+
+== Compatibility ==
+
+The ContentHandler facility is introduced in a way that should allow all existing code to keep functioning at least
+for pages that contain wikitext or other text based content. However, a number of functions and hooks have been
+deprecated in favor of new versions that are aware of the page's content model, and will now generate warnings when
+used.
+
+Most importantly, the following functions have been deprecated:
+
+* Revisions::getText() and Revisions::getRawText() is deprecated in favor Revisions::getContent()
+* WikiPage::getText() is deprecated in favor WikiPage::getContent()
+
+Also, the old Article::getContent() (which returns text) is superceded by Article::getContentObject(). However, both
+methods should be avoided since they do not provide clean access to the page's actual content. For instance, they may
+return a system message for non-existing pages. Use WikiPage::getContent() instead.
+
+Code that relies on a textual representation of the page content should eventually be rewritten. However,
+ContentHandler::getContentText() provides a stop-gap that can be used to get text for a page. Its behavior is controlled
+by $wgContentHandlerTextFallback; per default it will return the text for text based content, and null for any other
+content.
+
+For rendering page content, Content::getParserOutput() should be used instead of accessing the parser directly.
+ContentHandler::makeParserOptions() can be used to construct appropriate options.
+
+
+Besides some functions, some hooks have also been replaced by new versions (see hooks.txt for details).
+These hooks will now trigger a warning when used:
+
+* ArticleAfterFetchContent was replaced by ArticleAfterFetchContentObject
+* ArticleInsertComplete was replaced by PageContentInsertComplete
+* ArticleSave was replaced by PageContentSave
+* ArticleSaveComplete was replaced by PageContentSaveComplete
+* ArticleViewCustom was replaced by ArticleContentViewCustom (also consider a custom implementation of the view action)
+* EditFilterMerged was replaced by EditFilterMergedContent
+* EditPageGetDiffText was replaced by EditPageGetDiffContent
+* EditPageGetPreviewText was replaced by EditPageGetPreviewContent
+* ShowRawCssJs was deprecated in favor of custom rendering implemented in the respective ContentHandler object.
+
+
+== Database Storage ==
+
+Page content is stored in the database using the same mechanism as before. Non-text content is serialized first. The
+appropriate serialization and deserialization is handled by the Revision class.
+
+Each revision's content model and serialization format is stored in the revision table (resp. in the archive table, if
+the revision was deleted). The page's (current) content model (that is, the content model of the latest revision) is also
+stored in the page table.
+
+Note however that the content model and format is only stored if it differs from the page's default, as determined by
+ContentHandler::getDefaultModelFor( $title ). The default values are represented as NULL in the database, to preserve
+space.
+
+Storage of content model and format can be disabled altogether by setting $wgContentHandlerUseDB = false. In that case,
+the page's default model (and the model's default format) will be used everywhere. Attempts to store a revision of a page
+using a model or format different from the default will result in an error.
+
+
+== Globals ==
+
+There are some new globals that can be used to control the behavior of the ContentHandler facility:
+
+* $wgContentHandlers associates content model IDs with the names of the appropriate ContentHandler subclasses.
+
+* $wgNamespaceContentModels maps namespace IDs to a content model that should be the default for that namespace.
+
+* $wgContentHandlerUseDB determines whether each revision's content model should be stored in the database.
+ Defaults is true.
+
+* $wgContentHandlerTextFallback determines how the compatibility method ContentHandler::getContentText() will behave for
+ non-text content:
+ 'ignore' causes null to be returned for non-text content (default).
+ 'serialize' causes the serialized form of any non-text content to be returned (scary).
+ 'fail' causes an exception to be thrown for non-text content (strict).
+
+
+== Caveats ==
+
+There are some changes in behavior that might be surprising to users:
+
+* Javascript and CSS pages are no longer parsed as wikitext (though pre-save transform is still applied). Most
+importantly, this means that links, including categorization links, contained in the code will not work.
+
+* With $wgContentHandlerUseDB = false, pages can not be moved in a way that would change the
+default model. E.g. [[MediaWiki:foo.js]] can not be moved to [[MediaWiki:foo bar]], but can still be moved to
+[[User:John/foo.js]]. Also, in this mode, changing the default content model for a page (e.g. by changing
+$wgNamespaceContentModels) may cause it to become inaccessible.
+
+* action=edit will fail for pages with non-text content, unless the respective ContentHandler implementation has
+provided a specialized handler for the edit action. This is true for the API as well.
+
+* action=raw will fail for all non-text content. This seems better than serving content in other formats to an
+unsuspecting recipient. This will also cause client-side diffs to fail.
+
+* File pages provide their own action overrides that do not combine gracefully with any custom handlers defined by a
+ContentHandler. If for example a File page used a content model with a custom revert action, this would be overridden by
+WikiFilePage's handler for the revert action.
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+ This is an XML Schema description of the format
+ output by MediaWiki's Special:Export system.
+
+ Version 0.2 adds optional basic file upload info support,
+ which is used by our OAI export/import submodule.
+
+ Version 0.3 adds some site configuration information such
+ as a list of defined namespaces.
+
+ Version 0.4 adds per-revision delete flags, log exports,
+ discussion threading data, a per-page redirect flag, and
+ per-namespace capitalization.
+
+ Version 0.5 adds byte count per revision.
+
+ Version 0.6 adds a separate namespace tag, and resolves the
+ redirect target and adds a separate sha1 tag for each revision.
+
+ Version 0.7 adds a unique identity constraint for both page and
+ revision identifiers. See also bug 4220.
+ Fix type for <ns> from "positiveInteger" to "nonNegativeInteger" to allow 0
+ Moves <logitem> to its right location.
+ Add parentid to revision.
+ Fix type for <id> within <contributor> to "nonNegativeInteger"
+
+ Version 0.8 adds support for a <model> and a <format> tag for
+ each revision. See contenthandler.txt.
+
+ The canonical URL to the schema document is:
+ http://www.mediawiki.org/xml/export-0.8.xsd
+
+ Use the namespace:
+ http://www.mediawiki.org/xml/export-0.8/
+-->
+<schema xmlns="http://www.w3.org/2001/XMLSchema"
+ xmlns:mw="http://www.mediawiki.org/xml/export-0.8/"
+ targetNamespace="http://www.mediawiki.org/xml/export-0.8/"
+ elementFormDefault="qualified">
+
+ <annotation>
+ <documentation xml:lang="en">
+ MediaWiki's page export format
+ </documentation>
+ </annotation>
+
+ <!-- Need this to reference xml:lang -->
+ <import namespace="http://www.w3.org/XML/1998/namespace"
+ schemaLocation="http://www.w3.org/2001/xml.xsd" />
+
+ <!-- Our root element -->
+ <element name="mediawiki" type="mw:MediaWikiType">
+ <!-- Page ID contraint, see bug 4220 -->
+ <unique name="PageIDConstraint">
+ <selector xpath="mw:page" />
+ <field xpath="mw:id" />
+ </unique>
+ <!-- Revision ID contraint, see bug 4220 -->
+ <unique name="RevIDConstraint">
+ <selector xpath="mw:page/mw:revision" />
+ <field xpath="mw:id" />
+ </unique>
+ </element>
+
+ <complexType name="MediaWikiType">
+ <sequence>
+ <element name="siteinfo" type="mw:SiteInfoType"
+ minOccurs="0" maxOccurs="1" />
+ <element name="page" type="mw:PageType"
+ minOccurs="0" maxOccurs="unbounded" />
+ <element name="logitem" type="mw:LogItemType"
+ minOccurs="0" maxOccurs="unbounded" />
+ </sequence>
+ <attribute name="version" type="string" use="required" />
+ <attribute ref="xml:lang" use="required" />
+ </complexType>
+
+ <complexType name="SiteInfoType">
+ <sequence>
+ <element name="sitename" type="string" minOccurs="0" />
+ <element name="base" type="anyURI" minOccurs="0" />
+ <element name="generator" type="string" minOccurs="0" />
+ <element name="case" type="mw:CaseType" minOccurs="0" />
+ <element name="namespaces" type="mw:NamespacesType" minOccurs="0" />
+ </sequence>
+ </complexType>
+
+ <simpleType name="CaseType">
+ <restriction base="NMTOKEN">
+ <!-- Cannot have two titles differing only by case of first letter. -->
+ <!-- Default behavior through 1.5, $wgCapitalLinks = true -->
+ <enumeration value="first-letter" />
+
+ <!-- Complete title is case-sensitive -->
+ <!-- Behavior when $wgCapitalLinks = false -->
+ <enumeration value="case-sensitive" />
+
+ <!-- Cannot have non-case senstitive titles eg [[FOO]] == [[Foo]] -->
+ <!-- Not yet implemented as of MediaWiki 1.18 -->
+ <enumeration value="case-insensitive" />
+ </restriction>
+ </simpleType>
+
+ <simpleType name="DeletedFlagType">
+ <restriction base="NMTOKEN">
+ <enumeration value="deleted" />
+ </restriction>
+ </simpleType>
+
+ <complexType name="NamespacesType">
+ <sequence>
+ <element name="namespace" type="mw:NamespaceType"
+ minOccurs="0" maxOccurs="unbounded" />
+ </sequence>
+ </complexType>
+
+ <complexType name="NamespaceType">
+ <simpleContent>
+ <extension base="string">
+ <attribute name="key" type="integer" />
+ <attribute name="case" type="mw:CaseType" />
+ </extension>
+ </simpleContent>
+ </complexType>
+
+ <complexType name="RedirectType">
+ <simpleContent>
+ <extension base="string">
+ <attribute name="title" type="string" />
+ </extension>
+ </simpleContent>
+ </complexType>
+
+ <simpleType name="ContentModelType">
+ <restriction base="string">
+ <pattern value="[a-zA-Z][-+./a-zA-Z0-9]*"/>
+ </restriction>
+ </simpleType>
+
+ <simpleType name="ContentFormatType">
+ <restriction base="string">
+ <pattern value='[a-zA-Z][-+.a-zA-Z0-9]*\/[a-zA-Z][-+.a-zA-Z0-9]*'/>
+ </restriction>
+ </simpleType>
+
+ <complexType name="PageType">
+ <sequence>
+ <!-- Title in text form. (Using spaces, not underscores; with namespace ) -->
+ <element name="title" type="string" />
+
+ <!-- Namespace in canonical form -->
+ <element name="ns" type="nonNegativeInteger" />
+
+ <!-- optional page ID number -->
+ <element name="id" type="positiveInteger" />
+
+ <!-- flag if the current revision is a redirect -->
+ <element name="redirect" type="mw:RedirectType" minOccurs="0" maxOccurs="1" />
+
+ <!-- comma-separated list of string tokens, if present -->
+ <element name="restrictions" type="string" minOccurs="0" />
+
+ <!-- Zero or more sets of revision or upload data -->
+ <choice minOccurs="0" maxOccurs="unbounded">
+ <element name="revision" type="mw:RevisionType" />
+ <element name="upload" type="mw:UploadType" />
+ </choice>
+
+ <!-- Zero or One sets of discussion threading data -->
+ <element name="discussionthreadinginfo" minOccurs="0" maxOccurs="1" type="mw:DiscussionThreadingInfo" />
+ </sequence>
+ </complexType>
+
+ <complexType name="RevisionType">
+ <sequence>
+ <element name="id" type="positiveInteger" />
+ <element name="parentid" type="positiveInteger" minOccurs="0" />
+ <element name="timestamp" type="dateTime" />
+ <element name="contributor" type="mw:ContributorType" />
+ <element name="minor" minOccurs="0" maxOccurs="1" />
+ <element name="comment" type="mw:CommentType" minOccurs="0" maxOccurs="1" />
+ <element name="text" type="mw:TextType" />
+ <element name="sha1" type="string" />
+ <element name="model" type="mw:ContentModelType" />
+ <element name="format" type="mw:ContentFormatType" />
+ </sequence>
+ </complexType>
+
+ <complexType name="LogItemType">
+ <sequence>
+ <element name="id" type="positiveInteger" />
+ <element name="timestamp" type="dateTime" />
+ <element name="contributor" type="mw:ContributorType" />
+ <element name="comment" type="mw:CommentType" minOccurs="0" />
+ <element name="type" type="string" />
+ <element name="action" type="string" />
+ <element name="text" type="mw:LogTextType" minOccurs="0" maxOccurs="1" />
+ <element name="logtitle" type="string" minOccurs="0" maxOccurs="1" />
+ <element name="params" type="mw:LogParamsType" minOccurs="0" maxOccurs="1" />
+ </sequence>
+ </complexType>
+
+ <complexType name="CommentType">
+ <simpleContent>
+ <extension base="string">
+ <!-- This allows deleted=deleted on non-empty elements, but XSD is not omnipotent -->
+ <attribute name="deleted" use="optional" type="mw:DeletedFlagType" />
+ </extension>
+ </simpleContent>
+ </complexType>
+
+ <complexType name="TextType">
+ <simpleContent>
+ <extension base="string">
+ <attribute ref="xml:space" use="optional" default="preserve" />
+ <!-- This allows deleted=deleted on non-empty elements, but XSD is not omnipotent -->
+ <attribute name="deleted" use="optional" type="mw:DeletedFlagType" />
+ <!-- This isn't a good idea; we should be using "ID" instead of "NMTOKEN" -->
+ <!-- However, "NMTOKEN" is strictest definition that is both compatible with existing -->
+ <!-- usage ([0-9]+) and with the "ID" type. -->
+ <attribute name="id" type="NMTOKEN" />
+ <attribute name="bytes" use="optional" type="nonNegativeInteger" />
+ </extension>
+ </simpleContent>
+ </complexType>
+
+ <complexType name="LogTextType">
+ <simpleContent>
+ <extension base="string">
+ <!-- This allows deleted=deleted on non-empty elements, but XSD is not omnipotent -->
+ <attribute name="deleted" use="optional" type="mw:DeletedFlagType" />
+ </extension>
+ </simpleContent>
+ </complexType>
+
+ <complexType name="LogParamsType">
+ <simpleContent>
+ <extension base="string">
+ <attribute ref="xml:space" use="optional" default="preserve" />
+ </extension>
+ </simpleContent>
+ </complexType>
+
+ <complexType name="ContributorType">
+ <sequence>
+ <element name="username" type="string" minOccurs="0" />
+ <element name="id" type="nonNegativeInteger" minOccurs="0" />
+
+ <element name="ip" type="string" minOccurs="0" />
+ </sequence>
+ <!-- This allows deleted=deleted on non-empty elements, but XSD is not omnipotent -->
+ <attribute name="deleted" use="optional" type="mw:DeletedFlagType" />
+ </complexType>
+
+ <complexType name="UploadType">
+ <sequence>
+ <!-- Revision-style data... -->
+ <element name="timestamp" type="dateTime" />
+ <element name="contributor" type="mw:ContributorType" />
+ <element name="comment" type="string" minOccurs="0" />
+
+ <!-- Filename. (Using underscores, not spaces. No 'File:' namespace marker.) -->
+ <element name="filename" type="string" />
+
+ <!-- URI at which this resource can be obtained -->
+ <element name="src" type="anyURI" />
+
+ <element name="size" type="positiveInteger" />
+
+ <!-- TODO: add other metadata fields -->
+ </sequence>
+ </complexType>
+
+ <!-- Discussion threading data for LiquidThreads -->
+ <complexType name="DiscussionThreadingInfo">
+ <sequence>
+ <element name="ThreadSubject" type="string" />
+ <element name="ThreadParent" type="positiveInteger" />
+ <element name="ThreadAncestor" type="positiveInteger" />
+ <element name="ThreadPage" type="string" />
+ <element name="ThreadID" type="positiveInteger" />
+ <element name="ThreadAuthor" type="string" />
+ <element name="ThreadEditStatus" type="string" />
+ <element name="ThreadType" type="string" />
+ </sequence>
+ </complexType>
+
+</schema>
$user: the User object that was created. (Parameter added in 1.7)
$byEmail: true when account was created "by email" (added in 1.12)
+'AfterFinalPageOutput': At the end of OutputPage::output() but before
+final ob_end_flush() which will send the buffered output to the client.
+This allows for last-minute modification of the output within the buffer
+by using ob_get_clean().
+
'AfterImportPage': When a page import is completed
$title: Title under which the revisions were imported
$origTitle: Title provided by the XML file
used to retrieve this type of tokens.
'ArticleAfterFetchContent': after fetching content of an article from
+the database. DEPRECATED, use ArticleAfterFetchContentObject instead.
+$article: the article (object) being loaded from the database
+&$content: the content (string) of the article
+
+'ArticleAfterFetchContentObject': after fetching content of an article from
the database
$article: the article (object) being loaded from the database
-$content: the content (string) of the article
+&$content: the content of the article, as a Content object
'ArticleConfirmDelete': before writing the confirmation form for article
deletion
$user: the user that deleted the article
$reason: the reason the article was deleted
$id: id of the article that was deleted
+$content: the Content of the deleted page
+$logEntry: the ManualLogEntry used to record the deletion
'ArticleEditUpdateNewTalk': before updating user_newtalk when a user talk page
was changed
$title: title (object) used to create the article object
$article: article (object) that will be returned
-'ArticleInsertComplete': After a new article is created
+'ArticleInsertComplete': After a new article is created. DEPRECATED, use PageContentInsertComplete
$article: WikiPage created
$user: User creating the article
$text: New content
$isMinor: Whether or not the edit was marked as minor
$isWatch: (No longer used)
$section: (No longer used)
-$flags: Flags passed to WikiPage::doEdit()
+$flags: Flags passed to WikiPage::doEditContent()
+$revision: New Revision of the article
+
+'PageContentInsertComplete': After a new article is created
+$article: WikiPage created
+$user: User creating the article
+$content: New content as a Content object
+$summary: Edit summary/comment
+$isMinor: Whether or not the edit was marked as minor
+$isWatch: (No longer used)
+$section: (No longer used)
+$flags: Flags passed to WikiPage::doEditContent()
$revision: New Revision of the article
'ArticleMergeComplete': after merging to article using Special:Mergehistory
$revision: the revision the page was reverted back to
$current: the reverted revision
-'ArticleSave': before an article is saved
+'ArticleSave': before an article is saved. DEPRECATED, use PageContentSave instead
$article: the WikiPage (object) being saved
$user: the user (object) saving the article
$text: the new article text
$iswatch: watch flag
$section: section #
-'ArticleSaveComplete': After an article has been updated
+'PageContentSave': before an article is saved.
+$article: the WikiPage (object) being saved
+$user: the user (object) saving the article
+$content: the new article content, as a Content object
+$summary: the article summary (comment)
+$isminor: minor flag
+$iswatch: watch flag
+$section: section #
+
+'ArticleSaveComplete': After an article has been updated. DEPRECATED, use PageContentSaveComplete instead.
$article: WikiPage modified
$user: User performing the modification
$text: New content
$isMinor: Whether or not the edit was marked as minor
$isWatch: (No longer used)
$section: (No longer used)
-$flags: Flags passed to WikiPage::doEdit()
+$flags: Flags passed to WikiPage::doEditContent()
$revision: New Revision of the article
-$status: Status object about to be returned by doEdit()
+$status: Status object about to be returned by doEditContent()
+$baseRevId: the rev ID (or false) this edit was based on
+
+'PageContentSaveComplete': After an article has been updated
+$article: WikiPage modified
+$user: User performing the modification
+$content: New content, as a Content object
+$summary: Edit summary/comment
+$isMinor: Whether or not the edit was marked as minor
+$isWatch: (No longer used)
+$section: (No longer used)
+$flags: Flags passed to WikiPage::doEditContent()
+$revision: New Revision of the article
+$status: Status object about to be returned by doEditContent()
$baseRevId: the rev ID (or false) this edit was based on
'ArticleUndelete': When one or more revisions of an article are restored
follwed an redirect
$article: target article (object)
-'ArticleViewCustom': allows to output the text of the article in a different format than wikitext
+'ArticleViewCustom': allows to output the text of the article in a different format than wikitext.
+DEPRECATED, use ArticleContentViewCustom instead.
+Note that it is preferrable to implement proper handing for a custom data type using the ContentHandler facility.
$text: text of the page
$title: title of the page
$output: reference to $wgOut
+'ArticleContentViewCustom': allows to output the text of the article in a different format than wikitext.
+Note that it is preferrable to implement proper handing for a custom data type using the ContentHandler facility.
+$content: content of the page, as a Content object
+$title: title of the page
+$output: reference to $wgOut
+
'AuthPluginAutoCreate': Called when creating a local account for an user logged
in from an external authentication method
$user: User object created locally
'ConfirmEmailComplete': Called after a user's email has been confirmed successfully
$user: user (object) whose email is being confirmed
+'ContentHandlerDefaultModelFor': Called when the default content model is determiend
+for a given title. May be used to assign a different model for that title.
+$title: the Title in question
+&$model: the model name. Use with CONTENT_MODEL_XXX constants.
+
+'ContentHandlerForModelID': Called when a ContentHandler is requested for a given
+cointent model name, but no entry for that model exists in $wgContentHandlers.
+$modeName: the requested content model name
+&$handler: set this to a ContentHandler object, if desired.
+
'ContribsPager::getQueryInfo': Before the contributions query is about to run
&$pager: Pager object for contributions
&$queryInfo: The query for the contribs Pager
&$error: Error message to return
$summary: Edit summary for page
-'EditFilterMerged': Post-section-merge edit filter
+'EditFilterMerged': Post-section-merge edit filter.
+DEPRECATED, use EditFilterMergedContent instead.
$editor: EditPage instance (object)
$text: content of the edit box
&$error: error message to return
$summary: Edit summary for page
+'EditFilterMergedContent': Post-section-merge edit filter
+$editor: EditPage instance (object)
+$content: content of the edit box, as a Content object
+&$error: error message to return
+$summary: Edit summary for page
+
'EditFormPreloadText': Allows population of the edit form when creating
new pages
&$text: Text to preload with
$editPage: EditPage object
'EditPage::attemptSave': called before an article is
-saved, that is before WikiPage::doEdit() is called
+saved, that is before WikiPage::doEditContent() is called
$editpage_Obj: the current EditPage object
'EditPage::importFormData': allow extensions to read additional data
&$msg: localization message name, overridable. Default is either 'copyrightwarning' or 'copyrightwarning2'
'EditPageGetDiffText': Allow modifying the wikitext that will be used in
-"Show changes"
+"Show changes". DEPRECATED. Use EditPageGetDiffContent instead.
+Note that it is preferrable to implement diff handling for different data types using the ContentHandler facility.
+$editPage: EditPage object
+&$newtext: wikitext that will be used as "your version"
+
+'EditPageGetDiffContent': Allow modifying the wikitext that will be used in
+"Show changes".
+Note that it is preferrable to implement diff handling for different data types using the ContentHandler facility.
$editPage: EditPage object
&$newtext: wikitext that will be used as "your version"
-'EditPageGetPreviewText': Allow modifying the wikitext that will be previewed
+'EditPageGetPreviewText': Allow modifying the wikitext that will be previewed.
+DEPRECATED. Use EditPageGetPreviewContent instead.
+Note that it is preferrable to implement previews for different data types using the COntentHandler facility.
$editPage: EditPage object
&$toparse: wikitext that will be parsed
+'EditPageGetPreviewContent': Allow modifying the wikitext that will be previewed.
+Note that it is preferrable to implement previews for different data types using the COntentHandler facility.
+$editPage: EditPage object
+&$content: Content object to be previewed (may be replaced by hook function)
+
'EditPageNoSuchSection': When a section edit request is given for an non-existent section
&$editpage: The current EditPage object
&$res: the HTML of the error text
$revisionInfo: Array of information
Return false to stop further processing of the tag
+'InfoAction': When building information to display on the action=info page
+$context: IContextSource object
+&$pageInfo: Array of information
+
'InitializeArticleMaybeRedirect': MediaWiki check to see if title is a redirect
$title: Title object ($wgTitle)
$request: WebRequest
'ShowMissingArticle': Called when generating the output for a non-existent page
$article: The article object corresponding to the page
-'ShowRawCssJs': Customise the output of raw CSS and JavaScript in page views
+'ShowRawCssJs': Customise the output of raw CSS and JavaScript in page views.
+DEPRECATED, use the ContentHandler facility to handle CSS and JavaScript!
$text: Text being shown
$title: Title of the custom script/stylesheet page
$output: Current OutputPage object
&$opts: Options to use for the query
&$join: Join conditions
+'WikiPageDeletionUpdates': manipulate the list of DataUpdates to be applied when
+ a page is deleted. Called in WikiPage::getDeletionUpdates().
+ Note that updates specific to a content model should be provided by the
+ respective Content's getDeletionUpdates() method.
+$page: the WikiPage
+$content: the Content to generate updates for
+&$updates: the array of DataUpdate objects. Hook function may want to add to it.
+
'wfShellWikiCmd': Called when generating a shell-escaped command line
string to run a MediaWiki cli script.
&$script: MediaWiki cli script path
expriry: $wgRevisionCacheExpiry
Sessions:
- controlled by: $wgSessionsInMemcached
+ controlled by: $wgSessionsInObjectCache
key: $wgBDname:session:$id
ex: wikidb:session:38d7c5b8d3bfc51egf40c69bc40f8be3
stores: $SESSION, useful when using a multi-sever wiki
* must throw subclasses of ErrorPageError
*
* @param $user User: the user to check, or null to use the context user
- * @throws ErrorPageError
+ * @throws UserBlockedError|ReadOnlyError|PermissionsError
* @return bool True on success
*/
protected function checkCanExecute( User $user ) {
* forms, they probably won't have any data, but some (eg rollback) may do
* @param $data Array values that would normally be in the GET request
* @param $captureErrors Bool whether to catch exceptions and just return false
+ * @throws ErrorPageError
* @return Bool whether execution was successful
*/
public function execute( array $data = null, $captureErrors = true ) {
public $mParserOptions;
/**
- * Content of the revision we are working on
+ * Text of the revision we are working on
* @var string $mContent
*/
- var $mContent; // !<
+ var $mContent; // !< #BC cruft
+
+ /**
+ * Content of the revision we are working on
+ * @var Content
+ * @since 1.21
+ */
+ var $mContentObject; // !<
/**
* Is the content ($mContent) already loaded?
* This function has side effects! Do not use this function if you
* only want the real revision text if any.
*
+ * @deprecated in 1.21; use WikiPage::getContent() instead
+ *
* @return string Return the text of this revision
*/
public function getContent() {
+ ContentHandler::deprecated( __METHOD__, '1.21' );
+ $content = $this->getContentObject();
+ return ContentHandler::getContentText( $content );
+ }
+
+ /**
+ * Returns a Content object representing the pages effective display content,
+ * not necessarily the revision's content!
+ *
+ * Note that getContent/loadContent do not follow redirects anymore.
+ * If you need to fetch redirectable content easily, try
+ * the shortcut in WikiPage::getRedirectTarget()
+ *
+ * This function has side effects! Do not use this function if you
+ * only want the real revision text if any.
+ *
+ * @return Content Return the content of this revision
+ *
+ * @since 1.21
+ */
+ protected function getContentObject() {
wfProfileIn( __METHOD__ );
if ( $this->mPage->getID() === 0 ) {
if ( $text === false ) {
$text = '';
}
+
+ $content = ContentHandler::makeContent( $text, $this->getTitle() );
} else {
$message = $this->getContext()->getUser()->isLoggedIn() ? 'noarticletext' : 'noarticletextanon';
- $text = wfMessage( $message )->text();
+ $content = new MessageContent( $message, null, 'parsemag' );
}
wfProfileOut( __METHOD__ );
- return $text;
+ return $content;
} else {
- $this->fetchContent();
+ $this->fetchContentObject();
wfProfileOut( __METHOD__ );
- return $this->mContent;
+ return $this->mContentObject;
}
}
* Get text of an article from database
* Does *NOT* follow redirects.
*
+ * @protected
+ * @note this is really internal functionality that should really NOT be used by other functions. For accessing
+ * article content, use the WikiPage class, especially WikiBase::getContent(). However, a lot of legacy code
+ * uses this method to retrieve page text from the database, so the function has to remain public for now.
+ *
* @return mixed string containing article contents, or false if null
+ * @deprecated in 1.21, use WikiPage::getContent() instead
*/
- function fetchContent() {
- if ( $this->mContentLoaded ) {
+ function fetchContent() { #BC cruft!
+ ContentHandler::deprecated( __METHOD__, '1.21' );
+
+ if ( $this->mContentLoaded && $this->mContent ) {
return $this->mContent;
}
wfProfileIn( __METHOD__ );
+ $content = $this->fetchContentObject();
+
+ $this->mContent = ContentHandler::getContentText( $content ); #@todo: get rid of mContent everywhere!
+ ContentHandler::runLegacyHooks( 'ArticleAfterFetchContent', array( &$this, &$this->mContent ) );
+
+ wfProfileOut( __METHOD__ );
+
+ return $this->mContent;
+ }
+
+
+ /**
+ * Get text content object
+ * Does *NOT* follow redirects.
+ * TODO: when is this null?
+ *
+ * @note code that wants to retrieve page content from the database should use WikiPage::getContent().
+ *
+ * @return Content|null
+ *
+ * @since 1.21
+ */
+ protected function fetchContentObject() {
+ if ( $this->mContentLoaded ) {
+ return $this->mContentObject;
+ }
+
+ wfProfileIn( __METHOD__ );
+
$this->mContentLoaded = true;
+ $this->mContent = null;
$oldid = $this->getOldID();
# Pre-fill content with error message so that if something
# fails we'll have something telling us what we intended.
- $this->mContent = wfMessage( 'missing-revision', $oldid )->plain();
+ //XXX: this isn't page content but a UI message. horrible.
+ $this->mContentObject = new MessageContent( 'missing-revision', array( $oldid ), array() ) ;
if ( $oldid ) {
# $this->mRevision might already be fetched by getOldIDFromRequest()
}
$this->mRevision = $this->mPage->getRevision();
+
if ( !$this->mRevision ) {
wfDebug( __METHOD__ . " failed to retrieve current page, rev_id " . $this->mPage->getLatest() . "\n" );
wfProfileOut( __METHOD__ );
// @todo FIXME: Horrible, horrible! This content-loading interface just plain sucks.
// We should instead work with the Revision object when we need it...
- $this->mContent = $this->mRevision->getText( Revision::FOR_THIS_USER, $this->getContext()->getUser() ); // Loads if user is allowed
+ $this->mContentObject = $this->mRevision->getContent( Revision::FOR_THIS_USER, $this->getContext()->getUser() ); // Loads if user is allowed
$this->mRevIdFetched = $this->mRevision->getId();
- wfRunHooks( 'ArticleAfterFetchContent', array( &$this, &$this->mContent ) );
+ wfRunHooks( 'ArticleAfterFetchContentObject', array( &$this, &$this->mContentObject ) );
wfProfileOut( __METHOD__ );
- return $this->mContent;
+ return $this->mContentObject;
}
/**
* @return Revision|null
*/
public function getRevisionFetched() {
- $this->fetchContent();
+ $this->fetchContentObject();
return $this->mRevision;
}
break;
case 3:
# This will set $this->mRevision if needed
- $this->fetchContent();
+ $this->fetchContentObject();
# Are we looking at an old revision
if ( $oldid && $this->mRevision ) {
wfDebug( __METHOD__ . ": showing CSS/JS source\n" );
$this->showCssOrJsPage();
$outputDone = true;
- } elseif( !wfRunHooks( 'ArticleViewCustom', array( $this->mContent, $this->getTitle(), $outputPage ) ) ) {
+ } elseif( !wfRunHooks( 'ArticleContentViewCustom',
+ array( $this->fetchContentObject(), $this->getTitle(), $outputPage ) ) ) {
+
+ # Allow extensions do their own custom view for certain pages
+ $outputDone = true;
+ } elseif( !ContentHandler::runLegacyHooks( 'ArticleViewCustom',
+ array( $this->fetchContentObject(), $this->getTitle(), $outputPage ) ) ) {
+
# Allow extensions do their own custom view for certain pages
$outputDone = true;
} else {
- $text = $this->getContent();
- $rt = Title::newFromRedirectArray( $text );
+ $content = $this->getContentObject();
+ $rt = $content->getRedirectChain();
if ( $rt ) {
wfDebug( __METHOD__ . ": showing redirect=no page\n" );
# Viewing a redirect page (e.g. with parameter redirect=no)
$outputPage->addHTML( $this->viewRedirect( $rt ) );
# Parse just to get categories, displaytitle, etc.
- $this->mParserOutput = $wgParser->parse( $text, $this->getTitle(), $parserOptions );
+ $this->mParserOutput = $content->getParserOutput( $this->getTitle(), $oldid, $parserOptions, false );
$outputPage->addParserOutputNoText( $this->mParserOutput );
$outputDone = true;
}
# Run the parse, protected by a pool counter
wfDebug( __METHOD__ . ": doing uncached parse\n" );
+ // @todo: shouldn't we be passing $this->getPage() to PoolWorkArticleView instead of plain $this?
$poolArticleView = new PoolWorkArticleView( $this, $parserOptions,
- $this->getRevIdFetched(), $useParserCache, $this->getContent() );
+ $this->getRevIdFetched(), $useParserCache, $this->getContentObject(), $this->getContext() );
if ( !$poolArticleView->execute() ) {
$error = $poolArticleView->getError();
/**
* Show a diff page according to current request variables. For use within
* Article::view() only, other callers should use the DifferenceEngine class.
+ *
+ * @todo: make protected
*/
public function showDiffPage() {
$request = $this->getContext()->getRequest();
$unhide = $request->getInt( 'unhide' ) == 1;
$oldid = $this->getOldID();
- $de = new DifferenceEngine( $this->getContext(), $oldid, $diff, $rcid, $purge, $unhide );
+ $rev = $this->getRevisionFetched();
+
+ if ( !$rev ) {
+ $this->getContext()->getOutput()->setPageTitle( wfMessage( 'errorpagetitle' ) );
+ $this->getContext()->getOutput()->addWikiMsg( 'difference-missing-revision', $oldid, 1 );
+ return;
+ }
+
+ $contentHandler = $rev->getContentHandler();
+ $de = $contentHandler->createDifferenceEngine( $this->getContext(), $oldid, $diff, $rcid, $purge, $unhide );
+
// DifferenceEngine directly fetched the revision:
$this->mRevIdFetched = $de->mNewid;
$de->showDiffPage( $diffOnly );
*
* This is hooked by SyntaxHighlight_GeSHi to do syntax highlighting of these
* page views.
+ *
+ * @param bool $showCacheHint whether to show a message telling the user to clear the browser cache (default: true).
*/
- protected function showCssOrJsPage() {
- $dir = $this->getContext()->getLanguage()->getDir();
- $lang = $this->getContext()->getLanguage()->getCode();
-
+ protected function showCssOrJsPage( $showCacheHint = true ) {
$outputPage = $this->getContext()->getOutput();
- $outputPage->wrapWikiMsg( "<div id='mw-clearyourcache' lang='$lang' dir='$dir' class='mw-content-$dir'>\n$1\n</div>",
- 'clearyourcache' );
+
+ if ( $showCacheHint ) {
+ $dir = $this->getContext()->getLanguage()->getDir();
+ $lang = $this->getContext()->getLanguage()->getCode();
+
+ $outputPage->wrapWikiMsg( "<div id='mw-clearyourcache' lang='$lang' dir='$dir' class='mw-content-$dir'>\n$1\n</div>",
+ 'clearyourcache' );
+ }
// Give hooks a chance to customise the output
- if ( wfRunHooks( 'ShowRawCssJs', array( $this->mContent, $this->getTitle(), $outputPage ) ) ) {
- // Wrap the whole lot in a <pre> and don't parse
- $m = array();
- preg_match( '!\.(css|js)$!u', $this->getTitle()->getText(), $m );
- $outputPage->addHTML( "<pre class=\"mw-code mw-{$m[1]}\" dir=\"ltr\">\n" );
- $outputPage->addHTML( htmlspecialchars( $this->mContent ) );
- $outputPage->addHTML( "\n</pre>\n" );
+ if ( ContentHandler::runLegacyHooks( 'ShowRawCssJs', array( $this->fetchContentObject(), $this->getTitle(), $outputPage ) ) ) {
+ $po = $this->mContentObject->getParserOutput( $this->getTitle() );
+ $outputPage->addHTML( $po->getText() );
}
}
// Generate deletion reason
$hasHistory = false;
if ( !$reason ) {
- $reason = $this->generateReason( $hasHistory );
+ try {
+ $reason = $this->generateReason( $hasHistory );
+ } catch ( MWException $e ) {
+ # if a page is horribly broken, we still want to be able to delete it. so be lenient about errors here.
+ wfDebug("Error while building auto delete summary: $e");
+ $reason = '';
+ }
}
// If the page has a history, insert a warning
* @return ParserOutput or false if the given revsion ID is not found
*/
public function getParserOutput( $oldid = null, User $user = null ) {
+ //XXX: bypasses mParserOptions and thus setParserOptions()
+
if ( $user === null ) {
$parserOptions = $this->getParserOptions();
} else {
return $this->mPage->getParserOutput( $parserOptions, $oldid );
}
+ /**
+ * Override the ParserOptions used to render the primary article wikitext.
+ *
+ * @param ParserOptions $options
+ * @throws MWException if the parser options where already initialized.
+ */
+ public function setParserOptions( ParserOptions $options ) {
+ if ( $this->mParserOptions ) {
+ throw new MWException( "can't change parser options after they have already been set" );
+ }
+
+ // clone, so if $options is modified later, it doesn't confuse the parser cache.
+ $this->mParserOptions = clone $options;
+ }
+
/**
* Get parser options suitable for rendering the primary article wikitext
* @return ParserOptions
* @return bool
*/
public function updateRestrictions( $limit = array(), $reason = '', &$cascade = 0, $expiry = array() ) {
- return $this->mPage->updateRestrictions( $limit, $reason, $cascade, $expiry );
+ return $this->mPage->doUpdateRestrictions(
+ $limit,
+ $expiry,
+ $cascade,
+ $reason,
+ $this->getContext()->getUser()
+ );
}
/**
* @return mixed
*/
public function generateReason( &$hasHistory ) {
- return $this->mPage->getAutoDeleteReason( $hasHistory );
+ $title = $this->mPage->getTitle();
+ $handler = ContentHandler::getForTitle( $title );
+ return $handler->getAutoDeleteReason( $title, $hasHistory );
}
// ****** B/C functions for static methods ( __callStatic is PHP>=5.3 ) ****** //
* @param $newtext
* @param $flags
* @return string
+ * @deprecated since 1.21, use ContentHandler::getAutosummary() instead
*/
public static function getAutosummary( $oldtext, $newtext, $flags ) {
return WikiPage::getAutosummary( $oldtext, $newtext, $flags );
'UnlistedSpecialPage' => 'includes/SpecialPage.php',
'UploadSourceAdapter' => 'includes/Import.php',
'UppercaseCollation' => 'includes/Collation.php',
+ 'Uri' => 'includes/Uri.php',
'User' => 'includes/User.php',
'UserArray' => 'includes/UserArray.php',
'UserArrayFromResult' => 'includes/UserArray.php',
'ZipDirectoryReader' => 'includes/ZipDirectoryReader.php',
'ZipDirectoryReaderError' => 'includes/ZipDirectoryReader.php',
+ # content handler
+ 'AbstractContent' => 'includes/content/AbstractContent.php',
+ 'ContentHandler' => 'includes/content/ContentHandler.php',
+ 'Content' => 'includes/content/Content.php',
+ 'CssContentHandler' => 'includes/content/CssContentHandler.php',
+ 'CssContent' => 'includes/content/CssContent.php',
+ 'JavaScriptContentHandler' => 'includes/content/JavaScriptContentHandler.php',
+ 'JavaScriptContent' => 'includes/content/JavaScriptContent.php',
+ 'MessageContent' => 'includes/content/MessageContent.php',
+ 'TextContentHandler' => 'includes/content/TextContentHandler.php',
+ 'TextContent' => 'includes/content/TextContent.php',
+ 'WikitextContentHandler' => 'includes/content/WikitextContentHandler.php',
+ 'WikitextContent' => 'includes/content/WikitextContent.php',
+
# includes/actions
'CachedAction' => 'includes/actions/CachedAction.php',
'CreditsAction' => 'includes/actions/CreditsAction.php',
'ApiFormatDump' => 'includes/api/ApiFormatDump.php',
'ApiFormatFeedWrapper' => 'includes/api/ApiFormatBase.php',
'ApiFormatJson' => 'includes/api/ApiFormatJson.php',
+ 'ApiFormatNone' => 'includes/api/ApiFormatNone.php',
'ApiFormatPhp' => 'includes/api/ApiFormatPhp.php',
'ApiFormatRaw' => 'includes/api/ApiFormatRaw.php',
'ApiFormatTxt' => 'includes/api/ApiFormatTxt.php',
'ApiQueryLangLinks' => 'includes/api/ApiQueryLangLinks.php',
'ApiQueryLinks' => 'includes/api/ApiQueryLinks.php',
'ApiQueryLogEvents' => 'includes/api/ApiQueryLogEvents.php',
+ 'ApiQueryORM' => 'includes/api/ApiQueryORM.php',
'ApiQueryPageProps' => 'includes/api/ApiQueryPageProps.php',
'ApiQueryProtectedTitles' => 'includes/api/ApiQueryProtectedTitles.php',
'ApiQueryQueryPage' => 'includes/api/ApiQueryQueryPage.php',
'EmaillingJob' => 'includes/job/EmaillingJob.php',
'EnotifNotifyJob' => 'includes/job/EnotifNotifyJob.php',
'Job' => 'includes/job/Job.php',
+ 'JobQueue' => 'includes/job/JobQueue.php',
+ 'JobQueueDB' => 'includes/job/JobQueueDB.php',
+ 'JobQueueGroup' => 'includes/job/JobQueueGroup.php',
'RefreshLinksJob' => 'includes/job/RefreshLinksJob.php',
'RefreshLinksJob2' => 'includes/job/RefreshLinksJob.php',
'UploadFromUrlJob' => 'includes/job/UploadFromUrlJob.php',
'SqliteSearchResultSet' => 'includes/search/SearchSqlite.php',
'SqlSearchResultSet' => 'includes/search/SearchEngine.php',
+ # includes/site
+ 'MediaWikiSite' => 'includes/site/MediaWikiSite.php',
+ 'Site' => 'includes/site/Site.php',
+ 'SiteArray' => 'includes/site/SiteArray.php',
+ 'SiteList' => 'includes/site/SiteList.php',
+ 'SiteObject' => 'includes/site/SiteObject.php',
+ 'Sites' => 'includes/site/Sites.php',
+ 'SitesTable' => 'includes/site/SitesTable.php',
+
# includes/specials
'ActiveUsersPager' => 'includes/specials/SpecialActiveusers.php',
'AllmessagesTablePager' => 'includes/specials/SpecialAllmessages.php',
'FixExtLinksProtocolRelative' => 'maintenance/fixExtLinksProtocolRelative.php',
'PopulateCategory' => 'maintenance/populateCategory.php',
'PopulateImageSha1' => 'maintenance/populateImageSha1.php',
+ 'PopulateFilearchiveSha1' => 'maintenance/populateFilearchiveSha1.php',
'PopulateLogSearch' => 'maintenance/populateLogSearch.php',
'PopulateLogUsertext' => 'maintenance/populateLogUsertext.php',
'PopulateParentId' => 'maintenance/populateParentId.php',
'TestFileIterator' => 'tests/testHelpers.inc',
'TestRecorder' => 'tests/testHelpers.inc',
+ # tests/phpunit
+ 'DummyContentHandlerForTesting' => 'tests/phpunit/includes/ContentHandlerTest.php',
+ 'DummyContentForTesting' => 'tests/phpunit/includes/ContentHandlerTest.php',
+ 'JavascriptContentTest' => 'tests/phpunit/includes/JavascriptContentTest.php',
+ 'RevisionStorageTest' => 'tests/phpunit/includes/RevisionStorageTest.php',
+ 'TextContentTest' => 'tests/phpunit/includes/TextContentTest.php',
+ 'WikiPageTest' => 'tests/phpunit/includes/WikiPageTest.php',
+
# tests/phpunit/includes
'GenericArrayObjectTest' => 'tests/phpunit/includes/libs/GenericArrayObjectTest.php',
# tests/phpunit/includes/db
'ORMRowTest' => 'tests/phpunit/includes/db/ORMRowTest.php',
+ # tests/phpunit/includes/site
+ 'SiteObjectTest' => 'tests/phpunit/includes/site/SiteObjectTest.php',
+ 'TestSites' => 'tests/phpunit/includes/site/TestSites.php',
+
# tests/parser
'ParserTest' => 'tests/parser/parserTest.inc',
'ParserTestParserHook' => 'tests/parser/parserTestsParserHook.php',
*
* @param $cond Array: A condition, which must not contain other conditions
* @param $user User The user to check the condition against
+ * @throws MWException
* @return bool Whether the condition is true for the user
*/
private static function checkCondition( $cond, User $user ) {
/**
* Get the field name prefix for a given table
* @param $table String
+ * @throws MWException
* @return null|string
*/
protected function getPrefix( $table ) {
* Get the SQL condition array for selecting backlinks, with a join
* on the page table.
* @param $table String
+ * @throws MWException
* @return array|null
*/
protected function getConditions( $table ) {
* Partition a DB result with backlinks in it into batches
* @param $res ResultWrapper database result
* @param $batchSize integer
+ * @throws MWException
* @return array @see
*/
protected function partitionResult( $res, $batchSize ) {
* 3) An autoblock on the given IP
* @param $vagueTarget User|String also search for blocks affecting this target. Doesn't
* make any sense to use TYPE_AUTO / TYPE_ID here. Leave blank to skip IP lookups.
+ * @throws MWException
* @return Bool whether a relevant block was found
*/
protected function newLoad( $vagueTarget = null ) {
/**
* Delete the row from the IP blocks table.
*
+ * @throws MWException
* @return Boolean
*/
public function delete() {
/**
* Get the IP address at the start of the range in Hex form
+ * @throws MWException
* @return String IP in Hex form
*/
public function getRangeStart() {
/**
* Get the IP address at the start of the range in Hex form
+ * @throws MWException
* @return String IP in Hex form
*/
public function getRangeEnd() {
/**
* Set up all member variables using a database query.
+ * @throws MWException
* @return bool True on success, false on failure.
*/
protected function initialize() {
*
* @param Title $title: The title (usually $this->title)
* @param String $section: Which section
+ * @throws MWException
* @return Title
*/
private function addFragmentToTitle( $title, $section ) {
/**
* @param $fileName string
+ * @throws MWException
*/
function __construct( $fileName ) {
$this->fileName = $fileName;
protected function read( $length, $pos ) {
if ( fseek( $this->handle, $pos ) == -1 ) {
// This can easily happen if the internal pointers are incorrect
- throw new MWException(
+ throw new MWException(
'Seek failed, file "' . $this->fileName . '" may be corrupted.' );
}
/**
* Unpack an unsigned integer and throw an exception if it needs more than 31 bits
* @param $s
- * @return
+ * @throws MWException
+ * @return mixed
*/
protected function unpack31( $s ) {
$data = unpack( 'V', $s );
if ( $data[1] > 0x7fffffff ) {
- throw new MWException(
+ throw new MWException(
'Error in CDB file "' . $this->fileName . '", integer too big.' );
}
return $data[1];
/**
* Clean up the temp file and throw an exception
- *
+ *
* @param $msg string
* @throws MWException
*/
* @param $log_id int: log_id of the change to add the tags to
* @param $params String: params to put in the ct_params field of tabel 'change_tag'
*
+ * @throws MWException
* @return bool: false if no changes are made, otherwise true
*
* @exception MWException when $rc_id, $rev_id and $log_id are all null
* @param $conds String|Array: conditions used in query, see DatabaseBase::select
* @param $join_conds Array: join conditions, see DatabaseBase::select
* @param $options Array: options, see Database::select
- * @param $filter_tag String: tag to select on
- *
- * @exception MWException when unable to determine appropriate JOIN condition for tagging
+ * @param bool|string $filter_tag Tag to select on
*
+ * @throws MWException When unable to determine appropriate JOIN condition for tagging
*/
static function modifyDisplayQuery( &$tables, &$fields, &$conds,
&$join_conds, &$options, $filter_tag = false ) {
/**
* Unified CJK blocks.
*
- * The same definition of a CJK block must be used for both Collation and
- * generateCollationData.php. These blocks are omitted from the first
- * letter data, as an optimisation measure and because the default UCA table
- * is pretty useless for sorting Chinese text anyway. Japanese and Korean
+ * The same definition of a CJK block must be used for both Collation and
+ * generateCollationData.php. These blocks are omitted from the first
+ * letter data, as an optimisation measure and because the default UCA table
+ * is pretty useless for sorting Chinese text anyway. Japanese and Korean
* blocks are not included here, because they are smaller and more useful.
*/
static $cjkBlocks = array(
function __construct( $locale ) {
if ( !extension_loaded( 'intl' ) ) {
- throw new MWException( 'An ICU collation was requested, ' .
+ throw new MWException( 'An ICU collation was requested, ' .
'but the intl extension is not available.' );
}
$this->locale = $locale;
// Check for CJK
$firstChar = mb_substr( $string, 0, 1, 'UTF-8' );
- if ( ord( $firstChar ) > 0x7f
- && self::isCjk( utf8ToCodepoint( $firstChar ) ) )
+ if ( ord( $firstChar ) > 0x7f
+ && self::isCjk( utf8ToCodepoint( $firstChar ) ) )
{
return $firstChar;
}
// Sort the letters.
//
// It's impossible to have the precompiled data file properly sorted,
- // because the sort order changes depending on ICU version. If the
- // array is not properly sorted, the binary search will return random
- // results.
+ // because the sort order changes depending on ICU version. If the
+ // array is not properly sorted, the binary search will return random
+ // results.
//
// We also take this opportunity to remove primary collisions.
$letterMap = array();
}
/**
- * Do a binary search, and return the index of the largest item that sorts
+ * Do a binary search, and return the index of the largest item that sorts
* less than or equal to the target value.
*
* @param $valueCallback array A function to call to get the value with
* sorts before all items.
*/
function findLowerBound( $valueCallback, $valueCount, $comparisonCallback, $target ) {
+ if ( $valueCount === 0 ) {
+ return false;
+ }
+
$min = 0;
- $max = $valueCount - 1;
+ $max = $valueCount;
do {
$mid = $min + ( ( $max - $min ) >> 1 );
$item = call_user_func( $valueCallback, $mid );
}
} while ( $min < $max - 1 );
- if ( $min == 0 && $max == 0 && $comparison > 0 ) {
- // Before the first item
- return false;
- } else {
- return $min;
+ if ( $min == 0 ) {
+ $item = call_user_func( $valueCallback, $min );
+ $comparison = call_user_func( $comparisonCallback, $target, $item );
+ if ( $comparison < 0 ) {
+ // Before the first item
+ return false;
+ }
}
+ return $min;
}
static function isCjk( $codepoint ) {
* insert
* Insert a new element at the start of the array.
*
+ * @throws MWException
* @return string
*/
public function edit( $ops ) {
* Finds the source byte region which you would want to delete, if $pathName
* was to be deleted. Includes the leading spaces and tabs, the trailing line
* break, and any comments in between.
+ * @param $pathName
+ * @throws MWException
* @return array
*/
function findDeletionRegion( $pathName ) {
* or semicolon.
*
* The end position is the past-the-end (end + 1) value as per convention.
+ * @param $pathName
+ * @throws MWException
* @return array
*/
function findValueRegion( $pathName ) {
/**
* Sets a cookie. Used before a request to set up any individual
- * cookies. Used internally after a request to parse the
+ * cookies. Used internally after a request to parse the
* Set-Cookie headers.
*
* @param $value String: the value of the cookie
* @param $attr Array: possible key/values:
- * expires A date string
- * path The path this cookie is used on
- * domain Domain this cookie is used on
+ * expires A date string
+ * path The path this cookie is used on
+ * domain Domain this cookie is used on
+ * @throws MWException
*/
public function set( $value, $attr ) {
$this->value = $value;
// We hash the random state with more salt to avoid the state from leaking
// out and being used to predict the /randomness/ that follows.
if ( strlen( $buffer ) < $bytes ) {
- wfDebug( __METHOD__ . ": Falling back to using a pseudo random state to generate randomness.\n" );
+ wfDebug( __METHOD__ . ": Falling back to using a pseudo random state to generate randomness.\n" );
}
while ( strlen( $buffer ) < $bytes ) {
wfProfileIn( __METHOD__ . '-fallback' );
*
* @static
* @param $updates array a list of DataUpdate instances
+ * @throws Exception|null
*/
public static function runUpdates( $updates ) {
if ( empty( $updates ) ) return; # nothing to do
'image/x-djvu' => 'DjVuHandler', // compat
);
+/**
+ * Plugins for page content model handling.
+ * Each entry in the array maps a model id to a class name.
+ *
+ * @since 1.21
+ */
+$wgContentHandlers = array(
+ CONTENT_MODEL_WIKITEXT => 'WikitextContentHandler', // the usual case
+ CONTENT_MODEL_JAVASCRIPT => 'JavaScriptContentHandler', // dumb version, no syntax highlighting
+ CONTENT_MODEL_CSS => 'CssContentHandler', // dumb version, no syntax highlighting
+ CONTENT_MODEL_TEXT => 'TextContentHandler', // plain text, for use by extensions etc
+);
+
/**
* Resizing can be done using PHP's internal image libraries or using
* ImageMagick or another third-party converter, e.g. GraphicMagick.
*/
$wgDirectoryMode = 0777;
+/**
+ * Generate and use thumbnails suitable for screens with 1.5 and 2.0 pixel densities.
+ *
+ * This means a 320x240 use of an image on the wiki will also generate 480x360 and 640x480
+ * thumbnails, output via data-src-1-5 and data-src-2-0. Runtime JavaScript switches the
+ * images in after loading the original low-resolution versions depending on the reported
+ * window.devicePixelRatio.
+ */
+$wgResponsiveImages = true;
+
/**
* @name DJVU settings
* @{
* $wgSharedTables may be customized with a list of tables to share in the shared
* datbase. However it is advised to limit what tables you do share as many of
* MediaWiki's tables may have side effects if you try to share them.
- * EXPERIMENTAL
*
* $wgSharedPrefix is the table prefix for the shared database. It defaults to
* $wgDBprefix.
+ *
+ * @deprecated In new code, use the $wiki parameter to wfGetLB() to access
+ * remote databases. Using wfGetLB() allows the shared database to reside on
+ * separate servers to the wiki's own database, with suitable configuration
+ * of $wgLBFactoryConf.
*/
$wgSharedDB = null;
/**
* Deprecated alias for $wgSessionsInObjectCache.
*
- * @deprecated Use $wgSessionsInObjectCache
+ * @deprecated since 1.20; Use $wgSessionsInObjectCache
*/
$wgSessionsInMemcached = false;
/** Maximum number of titles to purge in any one client operation */
$wgMaxSquidPurgeTitles = 400;
+/**
+ * Whether to use a Host header in purge requests sent to the proxy servers
+ * configured in $wgSquidServers. Set this to false to support Squid
+ * configured in forward-proxy mode.
+ *
+ * If this is set to true, a Host header will be sent, and only the path
+ * component of the URL will appear on the request line, as if the request
+ * were a non-proxy HTTP 1.1 request. Varnish only supports this style of
+ * request. Squid supports this style of request only if reverse-proxy mode
+ * (http_port ... accel) is enabled.
+ *
+ * If this is set to false, no Host header will be sent, and the absolute URL
+ * will be sent in the request line, as is the standard for an HTTP proxy
+ * request in both HTTP 1.0 and 1.1. This style of request is not supported
+ * by Varnish, but is supported by Squid in either configuration (forward or
+ * reverse).
+ *
+ * @since 1.21
+ */
+$wgSquidPurgeUseHostHeader = true;
+
/**
* Routing configuration for HTCP multicast purging. Add elements here to
* enable HTCP and determine which purges are sent where. If set to an empty
* setting is ignored. If $wgHTCPMulticastRouting is not set and this setting
* is, it is used to populate $wgHTCPMulticastRouting.
*
- * @deprecated in favor of $wgHTCPMulticastRouting
+ * @deprecated since 1.20 in favor of $wgHTCPMulticastRouting
*/
$wgHTCPMulticastAddress = false;
/**
* HTCP multicast port.
- * @deprecated in favor of $wgHTCPMulticastRouting
+ * @deprecated since 1.20 in favor of $wgHTCPMulticastRouting
* @see $wgHTCPMulticastAddress
*/
$wgHTCPPort = 4827;
*/
$wgProxyList = array();
-/** deprecated */
+/**
+ * @deprecated since 1.14
+ */
$wgProxyKey = false;
/** @} */ # end of proxy scanner settings
*/
$wgJobTypesExcludedFromDefaultQueue = array();
+/**
+ * Map of job types to configuration arrays.
+ * These settings should be global to all wikis.
+ */
+$wgJobTypeConf = array(
+ 'default' => array( 'class' => 'JobQueueDB' ),
+);
+
/**
* Additional functions to be performed with updateSpecialPages.
* Expensive Querypages are already updated.
$wgAPIMetaModules = array();
$wgAPIPropModules = array();
$wgAPIListModules = array();
+$wgAPIGeneratorModules = array();
/**
* Maximum amount of rows to scan in a DB query in the API
$wgDBtestuser = ''; //db user that has permission to create and drop the test databases only
$wgDBtestpassword = '';
+/**
+ * Associative array mapping namespace IDs to the name of the content model pages in that namespace should have by
+ * default (use the CONTENT_MODEL_XXX constants). If no special content type is defined for a given namespace,
+ * pages in that namespace will use the CONTENT_MODEL_WIKITEXT (except for the special case of JS and CS pages).
+ *
+ * @since 1.21
+ */
+$wgNamespaceContentModels = array();
+
+/**
+ * How to react if a plain text version of a non-text Content object is requested using ContentHandler::getContentText():
+ *
+ * * 'ignore': return null
+ * * 'fail': throw an MWException
+ * * 'serialize': serialize to default format
+ *
+ * @since 1.21
+ */
+$wgContentHandlerTextFallback = 'ignore';
+
+/**
+ * Set to false to disable use of the database fields introduced by the ContentHandler facility.
+ * This way, the ContentHandler facility can be used without any additional information in the database.
+ * A page's content model is then derived solely from the page's title. This however means that changing
+ * a page's default model (e.g. using $wgNamespaceContentModels) will break the page and/or make the content
+ * inaccessible. This also means that pages can not be moved to a title that would default to a different
+ * content model.
+ *
+ * Overall, with $wgContentHandlerUseDB = false, no database updates are needed, but content handling
+ * is less robust and less flexible.
+ *
+ * @since 1.21
+ */
+$wgContentHandlerUseDB = true;
+
/**
* Whether the user must enter their password to change their e-mail address
*
*/
$wgRequirePasswordforEmailChange = true;
+/**
+ * Register handlers for specific types of sites.
+ *
+ * @since 1.20
+ */
+$wgSiteTypes = array();
+$wgSiteTypes['mediawiki'] = 'MediaWikiSite';
+
/**
* For really cool vim folding this needs to be at the end:
* vim: foldmarker=@{,@} foldmethod=marker
$update->doUpdate();
if ( $doCommit && $dbw->trxLevel() ) {
- $dbw->commit( __METHOD__ );
+ $dbw->commit( __METHOD__, 'flush' );
}
} catch ( MWException $e ) {
// We don't want exceptions thrown during deferred updates to
define( 'DBO_DEBUG', 1 );
define( 'DBO_NOBUFFER', 2 );
define( 'DBO_IGNORE', 4 );
-define( 'DBO_TRX', 8 );
+define( 'DBO_TRX', 8 ); // automatically start transaction on first query
define( 'DBO_DEFAULT', 16 );
define( 'DBO_PERSISTENT', 32 );
define( 'DBO_SYSDBA', 64 ); //for oracle maintenance
/**@{
* RecentChange type identifiers
*/
-define( 'RC_EDIT', 0);
-define( 'RC_NEW', 1);
-define( 'RC_MOVE', 2); // obsolete
-define( 'RC_LOG', 3);
-define( 'RC_MOVE_OVER_REDIRECT', 4); // obsolete
+define( 'RC_EDIT', 0 );
+define( 'RC_NEW', 1 );
+define( 'RC_MOVE', 2 ); // obsolete
+define( 'RC_LOG', 3 );
+define( 'RC_MOVE_OVER_REDIRECT', 4 ); // obsolete
+define( 'RC_EXTERNAL', 5 );
/**@}*/
/**@{
define( 'MW_SUPPORTS_EDITFILTERMERGED', 1 );
define( 'MW_SUPPORTS_PARSERFIRSTCALLINIT', 1 );
define( 'MW_SUPPORTS_LOCALISATIONCACHE', 1 );
+define( 'MW_SUPPORTS_CONTENTHANDLER', 1 );
/**@}*/
/** Support for $wgResourceModules */
define( 'APCOND_ISBOT', 9 );
/**@}*/
-/**
+/** @{
* Protocol constants for wfExpandUrl()
*/
define( 'PROTO_HTTP', 'http://' );
define( 'PROTO_CURRENT', null );
define( 'PROTO_CANONICAL', 1 );
define( 'PROTO_INTERNAL', 2 );
+/**@}*/
+
+/**@{
+ * Content model ids, used by Content and ContentHandler.
+ * These IDs will be exposed in the API and XML dumps.
+ *
+ * Extensions that define their own content model IDs should take
+ * care to avoid conflicts. Using the extension name as a prefix is recommended,
+ * for example 'myextension-somecontent'.
+ */
+define( 'CONTENT_MODEL_WIKITEXT', 'wikitext' );
+define( 'CONTENT_MODEL_JAVASCRIPT', 'javascript' );
+define( 'CONTENT_MODEL_CSS', 'css' );
+define( 'CONTENT_MODEL_TEXT', 'text' );
+/**@}*/
+
+/**@{
+ * Content formats, used by Content and ContentHandler.
+ * These should be MIME types, and will be exposed in the API and XML dumps.
+ *
+ * Extensions are free to use the below formats, or define their own.
+ * It is recommended to stick with the conventions for MIME types.
+ */
+define( 'CONTENT_FORMAT_WIKITEXT', 'text/x-wiki' ); // wikitext
+define( 'CONTENT_FORMAT_JAVASCRIPT', 'text/javascript' ); // for js pages
+define( 'CONTENT_FORMAT_CSS', 'text/css' ); // for css pages
+define( 'CONTENT_FORMAT_TEXT', 'text/plain' ); // for future use, e.g. with some plain-html messages.
+define( 'CONTENT_FORMAT_HTML', 'text/html' ); // for future use, e.g. with some plain-html messages.
+define( 'CONTENT_FORMAT_SERIALIZED', 'application/vnd.php.serialized' ); // for future use with the api and for extensions
+define( 'CONTENT_FORMAT_JSON', 'application/json' ); // for future use with the api, and for use by extensions
+define( 'CONTENT_FORMAT_XML', 'application/xml' ); // for future use with the api, and for use by extensions
+/**@}*/
*/
const AS_IMAGE_REDIRECT_LOGGED = 234;
+ /**
+ * Status: can't parse content
+ */
+ const AS_PARSE_ERROR = 240;
+
/**
* HTML id and name for the beginning of the edit form.
*/
var $textbox1 = '', $textbox2 = '', $summary = '', $nosummary = false;
var $edittime = '', $section = '', $sectiontitle = '', $starttime = '';
var $oldid = 0, $editintro = '', $scrolltop = null, $bot = true;
+ var $contentModel = null, $contentFormat = null;
# Placeholders for text injection by hooks (must be HTML)
# extensions should take care to _append_ to the present value
public $editFormTextBottom = '';
public $editFormTextAfterContent = '';
public $previewTextAfterContent = '';
- public $mPreloadText = '';
+ public $mPreloadContent = null;
/* $didSave should be set to true whenever an article was succesfully altered. */
public $didSave = false;
public $suppressIntro = false;
+ /**
+ * Set to true to allow editing of non-text content types.
+ *
+ * @var bool
+ */
+ public $allowNonTextContent = false;
+
/**
* @param $article Article
*/
public function __construct( Article $article ) {
$this->mArticle = $article;
$this->mTitle = $article->getTitle();
+
+ $this->contentModel = $this->mTitle->getContentModel();
+
+ $handler = ContentHandler::getForModelID( $this->contentModel );
+ $this->contentFormat = $handler->getDefaultFormat();
}
/**
/**
* Get the context title object.
- * If not set, $wgTitle will be returned. This behavior might changed in
+ * If not set, $wgTitle will be returned. This behavior might change in
* the future to return $this->mTitle instead.
*
* @return Title object
$this->isCssSubpage = $this->mTitle->isCssSubpage();
$this->isJsSubpage = $this->mTitle->isJsSubpage();
$this->isWrongCaseCssJsPage = $this->isWrongCaseCssJsPage();
- $this->isNew = !$this->mTitle->exists() || $this->section == 'new';
# Show applicable editing introductions
if ( $this->formtype == 'initial' || $this->firsttime ) {
wfProfileOut( __METHOD__ );
return;
}
-
- if ( !$this->mTitle->getArticleID() ) {
+ if ( !$this->mTitle->getArticleID() )
wfRunHooks( 'EditFormPreloadText', array( &$this->textbox1, &$this->mTitle ) );
- }
- else {
+ else
wfRunHooks( 'EditFormInitialText', array( $this ) );
- }
-
}
$this->showEditForm();
* @since 1.19
* @param $permErrors Array of permissions errors, as returned by
* Title::getUserPermissionsErrors().
+ * @throws PermissionsError
*/
protected function displayPermissionsError( array $permErrors ) {
global $wgRequest, $wgOut;
return;
}
- $content = $this->getContent();
+ $content = $this->getContentObject();
# Use the normal message if there's nothing to display
- if ( $this->firsttime && $content === '' ) {
+ if ( $this->firsttime && $content->isEmpty() ) {
$action = $this->mTitle->exists() ? 'edit' :
( $this->mTitle->isTalkPage() ? 'createtalk' : 'createpage' );
throw new PermissionsError( $action, $permErrors );
# If the user made changes, preserve them when showing the markup
# (This happens when a user is blocked during edit, for instance)
if ( !$this->firsttime ) {
- $content = $this->textbox1;
+ $text = $this->textbox1;
$wgOut->addWikiMsg( 'viewyourtext' );
} else {
+ $text = $this->toEditText( $content );
$wgOut->addWikiMsg( 'viewsourcetext' );
}
- $this->showTextbox( $content, 'wpTextbox1', array( 'readonly' ) );
+ $this->showTextbox( $text, 'wpTextbox1', array( 'readonly' ) );
$wgOut->addHTML( Html::rawElement( 'div', array( 'class' => 'templatesUsed' ),
Linker::formatTemplates( $this->getTemplates() ) ) );
* @param $request WebRequest
*/
function importFormData( &$request ) {
- global $wgLang, $wgUser;
+ global $wgContLang, $wgUser;
wfProfileIn( __METHOD__ );
# Section edit can come from either the form or a link
$this->section = $request->getVal( 'wpSection', $request->getVal( 'section' ) );
+ $this->isNew = !$this->mTitle->exists() || $this->section == 'new';
if ( $request->wasPosted() ) {
# These fields need to be checked for encoding.
// modified by subclasses
wfProfileIn( get_class( $this ) . "::importContentFormData" );
$textbox1 = $this->importContentFormData( $request );
- if ( isset( $textbox1 ) ) {
+ if ( isset( $textbox1 ) )
$this->textbox1 = $textbox1;
- }
-
wfProfileOut( get_class( $this ) . "::importContentFormData" );
}
# Truncate for whole multibyte characters
- $this->summary = $wgLang->truncate( $request->getText( 'wpSummary' ), 255 );
+ $this->summary = $wgContLang->truncate( $request->getText( 'wpSummary' ), 255 );
# If the summary consists of a heading, e.g. '==Foobar==', extract the title from the
# header syntax, e.g. 'Foobar'. This is mainly an issue when we are using wpSummary for
# currently doing double duty as both edit summary and section title. Right now this
# is just to allow API edits to work around this limitation, but this should be
# incorporated into the actual edit form when EditPage is rewritten (Bugs 18654, 26312).
- $this->sectiontitle = $wgLang->truncate( $request->getText( 'wpSectionTitle' ), 255 );
+ $this->sectiontitle = $wgContLang->truncate( $request->getText( 'wpSectionTitle' ), 255 );
$this->sectiontitle = preg_replace( '/^\s*=+\s*(.*?)\s*=+\s*$/', '$1', $this->sectiontitle );
$this->edittime = $request->getVal( 'wpEdittime' );
}
}
+ $this->oldid = $request->getInt( 'oldid' );
+
$this->bot = $request->getBool( 'bot', true );
$this->nosummary = $request->getBool( 'nosummary' );
- $this->oldid = $request->getInt( 'oldid' );
+ $content_handler = ContentHandler::getForTitle( $this->mTitle );
+ $this->contentModel = $request->getText( 'model', $content_handler->getModelID() ); #may be overridden by revision
+ $this->contentFormat = $request->getText( 'format', $content_handler->getDefaultFormat() ); #may be overridden by revision
+
+ #TODO: check if the desired model is allowed in this namespace, and if a transition from the page's current model to the new model is allowed
+ #TODO: check if the desired content model supports the given content format!
$this->live = $request->getCheck( 'live' );
$this->editintro = $request->getText( 'editintro',
function initialiseForm() {
global $wgUser;
$this->edittime = $this->mArticle->getTimestamp();
- $this->textbox1 = $this->getContent( false );
+
+ $content = $this->getContentObject( false ); #TODO: track content object?!
+ if ( $content === false ) {
+ return false;
+ }
+ $this->textbox1 = $this->toEditText( $content );
+
// activate checkboxes if user wants them to be always active
# Sort out the "watch" checkbox
if ( $wgUser->getOption( 'watchdefault' ) ) {
* @param $def_text string
* @return mixed string on success, $def_text for invalid sections
* @private
+ * @deprecated since 1.21, get WikiPage::getContent() instead.
+ */
+ function getContent( $def_text = false ) {
+ ContentHandler::deprecated( __METHOD__, '1.21' );
+
+ if ( $def_text !== null && $def_text !== false && $def_text !== '' ) {
+ $def_content = $this->toEditContent( $def_text );
+ } else {
+ $def_content = false;
+ }
+
+ $content = $this->getContentObject( $def_content );
+
+ // Note: EditPage should only be used with text based content anyway.
+ return $this->toEditText( $content );
+ }
+
+ /**
+ * @param Content|false $def_content The default value to return
+ *
+ * @return mixed Content on success, $def_content for invalid sections
+ *
+ * @since 1.21
*/
- function getContent( $def_text = '' ) {
- global $wgOut, $wgRequest, $wgParser;
+ protected function getContentObject( $def_content = null ) {
+ global $wgOut, $wgRequest;
wfProfileIn( __METHOD__ );
- $text = false;
+ $content = false;
// For message page not locally set, use the i18n message.
// For other non-existent articles, use preload text if any.
if ( !$this->mTitle->exists() || $this->section == 'new' ) {
if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI && $this->section != 'new' ) {
# If this is a system message, get the default text.
- $text = $this->mTitle->getDefaultMessageText();
+ $msg = $this->mTitle->getDefaultMessageText();
+
+ $content = $this->toEditContent( $msg );
}
- if ( $text === false ) {
+ if ( $content === false ) {
# If requested, preload some text.
$preload = $wgRequest->getVal( 'preload',
// Custom preload text for new sections
$this->section === 'new' ? 'MediaWiki:addsection-preload' : '' );
- $text = $this->getPreloadedText( $preload );
+
+ $content = $this->getPreloadedContent( $preload );
}
// For existing pages, get text based on "undo" or section parameters.
} else {
if ( $this->section != '' ) {
// Get section edit text (returns $def_text for invalid sections)
- $text = $wgParser->getSection( $this->getOriginalContent(), $this->section, $def_text );
+ $orig = $this->getOriginalContent();
+ $content = $orig ? $orig->getSection( $this->section ) : null;
+
+ if ( !$content ) $content = $def_content;
} else {
$undoafter = $wgRequest->getInt( 'undoafter' );
$undo = $wgRequest->getInt( 'undo' );
# Sanity check, make sure it's the right page,
# the revisions exist and they were not deleted.
- # Otherwise, $text will be left as-is.
+ # Otherwise, $content will be left as-is.
if ( !is_null( $undorev ) && !is_null( $oldrev ) &&
$undorev->getPage() == $oldrev->getPage() &&
$undorev->getPage() == $this->mTitle->getArticleID() &&
!$undorev->isDeleted( Revision::DELETED_TEXT ) &&
!$oldrev->isDeleted( Revision::DELETED_TEXT ) ) {
- $text = $this->mArticle->getUndoText( $undorev, $oldrev );
- if ( $text === false ) {
+ $content = $this->mArticle->getUndoContent( $undorev, $oldrev );
+
+ if ( $content === false ) {
# Warn the user that something went wrong
$undoMsg = 'failure';
} else {
wfMessage( 'undo-' . $undoMsg )->plain() . '</div>', true, /* interface */true );
}
- if ( $text === false ) {
- $text = $this->getOriginalContent();
+ if ( $content === false ) {
+ $content = $this->getOriginalContent();
}
}
}
wfProfileOut( __METHOD__ );
- return $text;
+ return $content;
}
/**
*/
private function getOriginalContent() {
if ( $this->section == 'new' ) {
- return $this->getCurrentText();
+ return $this->getCurrentContent();
}
$revision = $this->mArticle->getRevisionFetched();
if ( $revision === null ) {
- return '';
+ if ( !$this->contentModel ) $this->contentModel = $this->getTitle()->getContentModel();
+ $handler = ContentHandler::getForModelID( $this->contentModel );
+
+ return $handler->makeEmptyContent();
}
- return $this->mArticle->getContent();
+ $content = $revision->getContent();
+ return $content;
}
/**
- * Get the actual text of the page. This is basically similar to
- * WikiPage::getRawText() except that when the page doesn't exist an empty
- * string is returned instead of false.
+ * Get the current content of the page. This is basically similar to
+ * WikiPage::getContent( Revision::RAW ) except that when the page doesn't exist an empty
+ * content object is returned instead of null.
*
- * @since 1.19
- * @return string
+ * @since 1.21
+ * @return Content
*/
- private function getCurrentText() {
- $text = $this->mArticle->getRawText();
- if ( $text === false ) {
- return '';
+ protected function getCurrentContent() {
+ $rev = $this->mArticle->getRevision();
+ $content = $rev ? $rev->getContent( Revision::RAW ) : null;
+
+ if ( $content === false || $content === null ) {
+ if ( !$this->contentModel ) $this->contentModel = $this->getTitle()->getContentModel();
+ $handler = ContentHandler::getForModelID( $this->contentModel );
+
+ return $handler->makeEmptyContent();
} else {
- return $text;
+ # nasty side-effect, but needed for consistency
+ $this->contentModel = $rev->getContentModel();
+ $this->contentFormat = $rev->getContentFormat();
+
+ return $content;
}
}
+
/**
* Use this method before edit() to preload some text into the edit box
*
* @param $text string
+ * @deprecated since 1.21, use setPreloadedContent() instead.
*/
public function setPreloadedText( $text ) {
- $this->mPreloadText = $text;
+ ContentHandler::deprecated( __METHOD__, "1.21" );
+
+ $content = $this->toEditContent( $text );
+
+ $this->setPreloadedContent( $content );
+ }
+
+ /**
+ * Use this method before edit() to preload some content into the edit box
+ *
+ * @param $content Content
+ *
+ * @since 1.21
+ */
+ public function setPreloadedContent( Content $content ) {
+ $this->mPreloadContent = $content;
}
/**
* an earlier setPreloadText() or by loading the given page.
*
* @param $preload String: representing the title to preload from.
+ *
* @return String
+ *
+ * @deprecated since 1.21, use getPreloadedContent() instead
*/
protected function getPreloadedText( $preload ) {
- global $wgUser, $wgParser;
+ ContentHandler::deprecated( __METHOD__, "1.21" );
- if ( !empty( $this->mPreloadText ) ) {
- return $this->mPreloadText;
+ $content = $this->getPreloadedContent( $preload );
+ $text = $this->toEditText( $content );
+
+ return $text;
+ }
+
+ /**
+ * Get the contents to be preloaded into the box, either set by
+ * an earlier setPreloadText() or by loading the given page.
+ *
+ * @param $preload String: representing the title to preload from.
+ *
+ * @return Content
+ *
+ * @since 1.21
+ */
+ protected function getPreloadedContent( $preload ) {
+ global $wgUser;
+
+ if ( !empty( $this->mPreloadContent ) ) {
+ return $this->mPreloadContent;
}
+ $handler = ContentHandler::getForTitle( $this->getTitle() );
+
if ( $preload === '' ) {
- return '';
+ return $handler->makeEmptyContent();
}
$title = Title::newFromText( $preload );
# Check for existence to avoid getting MediaWiki:Noarticletext
- if ( $title === null || !$title->exists() || !$title->userCan( 'read', $wgUser ) ) {
- return '';
+ if ( $title === null || !$title->exists() || !$title->userCan( 'read' ) ) {
+ return $handler->makeEmptyContent();
}
$page = WikiPage::factory( $title );
if ( $page->isRedirect() ) {
$title = $page->getRedirectTarget();
# Same as before
- if ( $title === null || !$title->exists() || !$title->userCan( 'read', $wgUser ) ) {
- return '';
+ if ( $title === null || !$title->exists() || !$title->userCan( 'read' ) ) {
+ return $handler->makeEmptyContent();
}
$page = WikiPage::factory( $title );
}
$parserOptions = ParserOptions::newFromUser( $wgUser );
- return $wgParser->getPreloadText( $page->getRawText(), $title, $parserOptions );
+ $content = $page->getContent( Revision::RAW );
+
+ return $content->preloadTransform( $title, $parserOptions );
}
/**
/**
* Attempt submission
+ * @throws UserBlockedError|ReadOnlyError|ThrottledError|PermissionsError
* @return bool false if output is done, true if the rest of the form should be displayed
*/
function attemptSave() {
case self::AS_HOOK_ERROR:
return false;
+ case self::AS_PARSE_ERROR:
+ $wgOut->addWikiText( '<div class="error">' . $status->getWikiText() . '</div>');
+ return true;
+
case self::AS_SUCCESS_NEW_ARTICLE:
$query = $resultDetails['redirect'] ? 'redirect=no' : '';
$anchor = isset ( $resultDetails['sectionanchor'] ) ? $resultDetails['sectionanchor'] : '';
return $status;
}
+ try {
+ # Construct Content object
+ $textbox_content = $this->toEditContent( $this->textbox1 );
+ } catch (MWContentSerializationException $ex) {
+ $status->fatal( 'content-failed-to-parse', $this->contentModel, $this->contentFormat, $ex->getMessage() );
+ $status->value = self::AS_PARSE_ERROR;
+ wfProfileOut( __METHOD__ );
+ return $status;
+ }
+
# Check image redirect
if ( $this->mTitle->getNamespace() == NS_FILE &&
- Title::newFromRedirect( $this->textbox1 ) instanceof Title &&
+ $textbox_content->isRedirect() &&
!$wgUser->isAllowed( 'upload' ) ) {
$code = $wgUser->isAnon() ? self::AS_IMAGE_REDIRECT_ANON : self::AS_IMAGE_REDIRECT_LOGGED;
$status->setResult( false, $code );
if ( $new ) {
// Late check for create permission, just in case *PARANOIA*
- if ( !$this->mTitle->userCan( 'create', $wgUser ) ) {
+ if ( !$this->mTitle->userCan( 'create' ) ) {
$status->fatal( 'nocreatetext' );
$status->value = self::AS_NO_CREATE_PERMISSION;
wfDebug( __METHOD__ . ": no create permission\n" );
return $status;
}
- $text = $this->textbox1;
+ $content = $textbox_content;
+
$result['sectionanchor'] = '';
if ( $this->section == 'new' ) {
if ( $this->sectiontitle !== '' ) {
// Insert the section title above the content.
- $text = wfMessage( 'newsectionheaderdefaultlevel' )->rawParams( $this->sectiontitle )
- ->inContentLanguage()->text() . "\n\n" . $text;
+ $content = $content->addSectionHeader( $this->sectiontitle );
// Jump to the new section
$result['sectionanchor'] = $wgParser->guessLegacySectionNameFromWikiText( $this->sectiontitle );
// passed.
if ( $this->summary === '' ) {
$cleanSectionTitle = $wgParser->stripSectionName( $this->sectiontitle );
- $this->summary = wfMessage( 'newsectionsummary' )
- ->rawParams( $cleanSectionTitle )->inContentLanguage()->text();
+ $this->summary = wfMessage( 'newsectionsummary', $cleanSectionTitle )
+ ->inContentLanguage()->text() ;
}
} elseif ( $this->summary !== '' ) {
// Insert the section title above the content.
- $text = wfMessage( 'newsectionheaderdefaultlevel' )->rawParams( $this->summary )
- ->inContentLanguage()->text() . "\n\n" . $text;
+ $content = $content->addSectionHeader( $this->summary );
// Jump to the new section
$result['sectionanchor'] = $wgParser->guessLegacySectionNameFromWikiText( $this->summary );
// Create a link to the new section from the edit summary.
$cleanSummary = $wgParser->stripSectionName( $this->summary );
- $this->summary = wfMessage( 'newsectionsummary' )
- ->rawParams( $cleanSummary )->inContentLanguage()->text();
+ $this->summary = wfMessage( 'newsectionsummary', $cleanSummary )
+ ->inContentLanguage()->text();
}
}
$status->value = self::AS_SUCCESS_NEW_ARTICLE;
- } else {
+ } else { # not $new
# Article exists. Check for edit conflict.
+
+ $this->mArticle->clear(); # Force reload of dates, etc.
$timestamp = $this->mArticle->getTimestamp();
+
wfDebug( "timestamp: {$timestamp}, edittime: {$this->edittime}\n" );
if ( $timestamp != $this->edittime ) {
$this->isConflict = false;
wfDebug( __METHOD__ . ": conflict suppressed; new section\n" );
}
- } elseif ( $this->section == '' && Revision::userWasLastToEdit( DB_MASTER, $this->mTitle->getArticleID(), $wgUser->getId(), $this->edittime ) ) {
+ } elseif ( $this->section == '' && Revision::userWasLastToEdit( DB_MASTER, $this->mTitle->getArticleID(),
+ $wgUser->getId(), $this->edittime ) ) {
# Suppress edit conflict with self, except for section edits where merging is required.
wfDebug( __METHOD__ . ": Suppressing edit conflict, same user.\n" );
$this->isConflict = false;
$sectionTitle = $this->summary;
}
+ $content = null;
+
if ( $this->isConflict ) {
- wfDebug( __METHOD__ . ": conflict! getting section '$this->section' for time '$this->edittime' (article time '{$timestamp}')\n" );
- $text = $this->mArticle->replaceSection( $this->section, $this->textbox1, $sectionTitle, $this->edittime );
+ wfDebug( __METHOD__ . ": conflict! getting section '{$this->section}' for time '{$this->edittime}'"
+ . " (article time '{$timestamp}')\n" );
+
+ $content = $this->mArticle->replaceSectionContent( $this->section, $textbox_content, $sectionTitle, $this->edittime );
} else {
- wfDebug( __METHOD__ . ": getting section '$this->section'\n" );
- $text = $this->mArticle->replaceSection( $this->section, $this->textbox1, $sectionTitle );
+ wfDebug( __METHOD__ . ": getting section '{$this->section}'\n" );
+ $content = $this->mArticle->replaceSectionContent( $this->section, $textbox_content, $sectionTitle );
}
- if ( is_null( $text ) ) {
+
+ if ( is_null( $content ) ) {
wfDebug( __METHOD__ . ": activating conflict; section replace failed.\n" );
$this->isConflict = true;
- $text = $this->textbox1; // do not try to merge here!
+ $content = $textbox_content; // do not try to merge here!
} elseif ( $this->isConflict ) {
# Attempt merge
- if ( $this->mergeChangesInto( $text ) ) {
+ if ( $this->mergeChangesIntoContent( $textbox_content ) ) {
// Successful merge! Maybe we should tell the user the good news?
$this->isConflict = false;
+ $content = $textbox_content;
wfDebug( __METHOD__ . ": Suppressing edit conflict, successful merge.\n" );
} else {
$this->section = '';
- $this->textbox1 = $text;
+ #$this->textbox1 = $text; #redundant, nothing to do here?
wfDebug( __METHOD__ . ": Keeping edit conflict, failed merge.\n" );
}
}
}
// Run post-section-merge edit filter
- if ( !wfRunHooks( 'EditFilterMerged', array( $this, $text, &$this->hookError, $this->summary ) ) ) {
+ $hook_args = array( $this, $content, &$this->hookError, $this->summary );
+
+ if ( !ContentHandler::runLegacyHooks( 'EditFilterMerged', $hook_args )
+ || !wfRunHooks( 'EditFilterMergedContent', $hook_args ) ) {
# Error messages etc. could be handled within the hook...
$status->fatal( 'hookaborted' );
$status->value = self::AS_HOOK_ERROR;
# Handle the user preference to force summaries here, but not for null edits
if ( $this->section != 'new' && !$this->allowBlankSummary
- && $this->getOriginalContent() != $text
- && !Title::newFromRedirect( $text ) ) # check if it's not a redirect
+ && !$content->equals( $this->getOriginalContent() )
+ && !$content->isRedirect() ) # check if it's not a redirect
{
if ( md5( $this->summary ) == $this->autoSumm ) {
$this->missingSummary = true;
// passed.
if ( $this->summary === '' ) {
$cleanSectionTitle = $wgParser->stripSectionName( $this->sectiontitle );
- $this->summary = wfMessage( 'newsectionsummary' )
- ->rawParams( $cleanSectionTitle )->inContentLanguage()->text();
+ $this->summary = wfMessage( 'newsectionsummary', $cleanSectionTitle )
+ ->inContentLanguage()->text();
}
} elseif ( $this->summary !== '' ) {
$sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $this->summary );
# This is a new section, so create a link to the new section
# in the revision summary.
$cleanSummary = $wgParser->stripSectionName( $this->summary );
- $this->summary = wfMessage( 'newsectionsummary' )
- ->rawParams( $cleanSummary )->inContentLanguage()->text();
+ $this->summary = wfMessage( 'newsectionsummary', $cleanSummary )
+ ->inContentLanguage()->text();
}
} elseif ( $this->section != '' ) {
# Try to get a section anchor from the section source, redirect to edited section if header found
// merged the section into full text. Clear the section field
// so that later submission of conflict forms won't try to
// replace that into a duplicated mess.
- $this->textbox1 = $text;
+ $this->textbox1 = $this->toEditText( $content );
$this->section = '';
$status->value = self::AS_SUCCESS_UPDATE;
}
// Check for length errors again now that the section is merged in
- $this->kblength = (int)( strlen( $text ) / 1024 );
+ $this->kblength = (int)( strlen( $this->toEditText( $content ) ) / 1024 );
if ( $this->kblength > $wgMaxArticleSize ) {
$this->tooBig = true;
$status->setResult( false, self::AS_MAX_ARTICLE_SIZE_EXCEEDED );
( ( $this->minoredit && !$this->isNew ) ? EDIT_MINOR : 0 ) |
( $bot ? EDIT_FORCE_BOT : 0 );
- $doEditStatus = $this->mArticle->doEdit( $text, $this->summary, $flags );
+ $doEditStatus = $this->mArticle->doEditContent( $content, $this->summary, $flags,
+ false, null, $this->contentFormat );
if ( $doEditStatus->isOK() ) {
- $result['redirect'] = Title::newFromRedirect( $text ) !== null;
- $this->updateWatchlist();
+ $result['redirect'] = $content->isRedirect();
+ $this->commitWatch();
wfProfileOut( __METHOD__ );
return $status;
} else {
}
/**
- * Register the change of watch status
+ * Commit the change of watch status
*/
- protected function updateWatchlist() {
+ protected function commitWatch() {
global $wgUser;
-
if ( $wgUser->isLoggedIn() && $this->watchthis != $wgUser->isWatched( $this->mTitle ) ) {
- $fname = __METHOD__;
- $title = $this->mTitle;
- $watch = $this->watchthis;
-
- // Do this in its own transaction to reduce contention...
$dbw = wfGetDB( DB_MASTER );
- $dbw->onTransactionIdle( function() use ( $dbw, $title, $watch, $wgUser, $fname ) {
- $dbw->begin( $fname );
- if ( $watch ) {
- WatchAction::doWatch( $title, $wgUser );
- } else {
- WatchAction::doUnwatch( $title, $wgUser );
- }
- $dbw->commit( $fname );
- } );
+ $dbw->begin( __METHOD__ );
+ if ( $this->watchthis ) {
+ WatchAction::doWatch( $this->mTitle, $wgUser );
+ } else {
+ WatchAction::doUnwatch( $this->mTitle, $wgUser );
+ }
+ $dbw->commit( __METHOD__ );
}
}
* @param $editText string
*
* @return bool
+ * @deprecated since 1.21, use mergeChangesIntoContent() instead
*/
- function mergeChangesInto( &$editText ) {
+ function mergeChangesInto( &$editText ){
+ ContentHandler::deprecated( __METHOD__, "1.21" );
+
+ $editContent = $this->toEditContent( $editText );
+
+ $ok = $this->mergeChangesIntoContent( $editContent );
+
+ if ( $ok ) {
+ $editText = $this->toEditText( $editContent );
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * @private
+ * @todo document
+ *
+ * @parma $editText string
+ *
+ * @return bool
+ * @since since 1.WD
+ */
+ private function mergeChangesIntoContent( &$editContent ){
wfProfileIn( __METHOD__ );
$db = wfGetDB( DB_MASTER );
wfProfileOut( __METHOD__ );
return false;
}
- $baseText = $baseRevision->getText();
+ $baseContent = $baseRevision->getContent();
// The current state, we want to merge updates into it
$currentRevision = Revision::loadFromTitle( $db, $this->mTitle );
wfProfileOut( __METHOD__ );
return false;
}
- $currentText = $currentRevision->getText();
+ $currentContent = $currentRevision->getContent();
+
+ $handler = ContentHandler::getForModelID( $baseContent->getModel() );
- $result = '';
- if ( wfMerge( $baseText, $editText, $currentText, $result ) ) {
- $editText = $result;
+ $result = $handler->merge3( $baseContent, $editContent, $currentContent );
+
+ if ( $result ) {
+ $editContent = $result;
wfProfileOut( __METHOD__ );
return true;
} else {
$wgOut->addModules( 'mediawiki.action.edit' );
if ( $wgUser->getOption( 'uselivepreview', false ) ) {
- $wgOut->addModules( 'mediawiki.action.edit.preview' );
+ $wgOut->addModules( 'mediawiki.legacy.preview' );
}
// Bug #19334: textarea jumps when editing articles in IE8
$wgOut->addStyle( 'common/IE80Fixes.css', 'screen', 'IE 8' );
}
}
+ /**
+ * Gets an editable textual representation of the given Content object.
+ * The textual representation can be turned by into a Content object by the
+ * toEditContent() method.
+ *
+ * If the given Content object is not of a type that can be edited using the text base EditPage,
+ * an exception will be raised. Set $this->allowNonTextContent to true to allow editing of non-textual
+ * content.
+ *
+ * @param Content $content
+ * @return String the editable text form of the content.
+ *
+ * @throws MWException if $content is not an instance of TextContent and $this->allowNonTextContent is not true.
+ */
+ protected function toEditText( Content $content ) {
+ if ( !$this->allowNonTextContent && !( $content instanceof TextContent ) ) {
+ throw new MWException( "This content model can not be edited as text: "
+ . ContentHandler::getLocalizedName( $content->getModel() ) );
+ }
+
+ return $content->serialize( $this->contentFormat );
+ }
+
+ /**
+ * Turns the given text into a Content object by unserializing it.
+ *
+ * If the resulting Content object is not of a type that can be edited using the text base EditPage,
+ * an exception will be raised. Set $this->allowNonTextContent to true to allow editing of non-textual
+ * content.
+ *
+ * @param String|null|false $text Text to unserialize
+ * @return Content The content object created from $text. If $text was false or null, false resp. null will be
+ * returned instead.
+ *
+ * @throws MWException if unserializing the text results in a Content object that is not an instance of TextContent
+ * and $this->allowNonTextContent is not true.
+ */
+ protected function toEditContent( $text ) {
+ if ( $text === false || $text === null ) {
+ return $text;
+ }
+
+ $content = ContentHandler::makeContent( $text, $this->getTitle(),
+ $this->contentModel, $this->contentFormat );
+
+ if ( !$this->allowNonTextContent && !( $content instanceof TextContent ) ) {
+ throw new MWException( "This content model can not be edited as text: "
+ . ContentHandler::getLocalizedName( $content->getModel() ) );
+ }
+
+ return $content;
+ }
+
/**
* Send the edit form and related headers to $wgOut
* @param $formCallback Callback that takes an OutputPage parameter; will be called
}
}
+ //@todo: add EditForm plugin interface and use it here!
+ // search for textarea1 and textares2, and allow EditForm to override all uses.
$wgOut->addHTML( Html::openElement( 'form', array( 'id' => self::EDITFORM_ID, 'name' => self::EDITFORM_ID,
'method' => 'post', 'action' => $this->getActionURL( $this->getContextTitle() ),
'enctype' => 'multipart/form-data' ) ) );
$wgOut->addHTML( Html::hidden( 'oldid', $this->oldid ) );
+ $wgOut->addHTML( Html::hidden( 'format', $this->contentFormat ) );
+ $wgOut->addHTML( Html::hidden( 'model', $this->contentModel ) );
+
if ( $this->section == 'new' ) {
$this->showSummaryInput( true, $this->summary );
$wgOut->addHTML( $this->getSummaryPreview( true, $this->summary ) );
// resolved between page source edits and custom ui edits using the
// custom edit ui.
$this->textbox2 = $this->textbox1;
- $this->textbox1 = $this->getCurrentText();
+
+ $content = $this->getCurrentContent();
+ $this->textbox1 = $this->toEditText( $content );
$this->showTextbox1();
} else {
Linker::formatHiddenCategories( $this->mArticle->getHiddenCategories() ) ) );
if ( $this->isConflict ) {
- $this->showConflict();
+ try {
+ $this->showConflict();
+ } catch ( MWContentSerializationException $ex ) {
+ // this can't really happen, but be nice if it does.
+ $msg = wfMessage( 'content-failed-to-parse', $this->contentModel, $this->contentFormat, $ex->getMessage() );
+ $wgOut->addWikiText( '<div class="error">' . $msg->text() . '</div>');
+ }
}
$wgOut->addHTML( $this->editFormTextBottom . "\n</form>\n" );
if ( $this->section != '' && $this->section != 'new' ) {
if ( !$this->summary && !$this->preview && !$this->diff ) {
- $sectionTitle = self::extractSectionTitle( $this->textbox1 );
+ $sectionTitle = self::extractSectionTitle( $this->textbox1 ); //FIXME: use Content object
if ( $sectionTitle !== false ) {
$this->summary = "/* $sectionTitle */ ";
}
if ( $revision ) {
// Let sysop know that this will make private content public if saved
- if ( !$revision->userCan( Revision::DELETED_TEXT, $wgUser ) ) {
+ if ( !$revision->userCan( Revision::DELETED_TEXT ) ) {
$wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", 'rev-deleted-text-permission' );
} elseif ( $revision->isDeleted( Revision::DELETED_TEXT ) ) {
$wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", 'rev-deleted-text-view' );
$wgOut->wrapWikiMsg( "<div class='error' id='mw-userinvalidcssjstitle'>\n$1\n</div>", array( 'userinvalidcssjstitle', $this->mTitle->getSkinFromCssJsSubpage() ) );
}
if ( $this->formtype !== 'preview' ) {
- if ( $this->isCssSubpage ) {
+ if ( $this->isCssSubpage )
$wgOut->wrapWikiMsg( "<div id='mw-usercssyoucanpreview'>\n$1\n</div>", array( 'usercssyoucanpreview' ) );
- }
-
- if ( $this->isJsSubpage ) {
+ if ( $this->isJsSubpage )
$wgOut->wrapWikiMsg( "<div id='mw-userjsyoucanpreview'>\n$1\n</div>", array( 'userjsyoucanpreview' ) );
- }
}
}
}
* @return String
*/
protected function getSummaryPreview( $isSubjectPreview, $summary = "" ) {
- if ( !$summary || ( !$this->preview && !$this->diff ) ) {
+ if ( !$summary || ( !$this->preview && !$this->diff ) )
return "";
- }
global $wgParser;
- if ( $isSubjectPreview ) {
+ if ( $isSubjectPreview )
$summary = wfMessage( 'newsectionsummary', $wgParser->stripSectionName( $summary ) )
->inContentLanguage()->text();
- }
$message = $isSubjectPreview ? 'subject-preview' : 'summary-preview';
HTML
);
- if ( !$this->checkUnicodeCompliantBrowser() ) {
+ if ( !$this->checkUnicodeCompliantBrowser() )
$wgOut->addHTML( Html::hidden( 'safemode', '1' ) );
- }
}
protected function showFormAfterText() {
$this->showTextbox( $this->textbox2, 'wpTextbox2', array( 'tabindex' => 6, 'readonly' ) );
}
- protected function showTextbox( $content, $name, $customAttribs = array() ) {
+ protected function showTextbox( $text, $name, $customAttribs = array() ) {
global $wgOut, $wgUser;
- $wikitext = $this->safeUnicodeOutput( $content );
+ $wikitext = $this->safeUnicodeOutput( $text );
if ( strval( $wikitext ) !== '' ) {
// Ensure there's a newline at the end, otherwise adding lines
// is awkward.
protected function displayPreviewArea( $previewOutput, $isOnTop = false ) {
global $wgOut;
$classes = array();
- if ( $isOnTop ) {
+ if ( $isOnTop )
$classes[] = 'ontop';
- }
$attribs = array( 'id' => 'wikiPreview', 'class' => implode( ' ', $classes ) );
- if ( $this->formtype != 'preview' ) {
+ if ( $this->formtype != 'preview' )
$attribs['style'] = 'display: none;';
- }
$wgOut->addHTML( Xml::openElement( 'div', $attribs ) );
$wgOut->addHTML( '</div>' );
if ( $this->formtype == 'diff' ) {
- $this->showDiff();
+ try {
+ $this->showDiff();
+ } catch ( MWContentSerializationException $ex ) {
+ $msg = wfMessage( 'content-failed-to-parse', $this->contentModel, $this->contentFormat, $ex->getMessage() );
+ $wgOut->addWikiText( '<div class="error">' . $msg->text() . '</div>');
+ }
}
}
$oldtext = $this->mTitle->getDefaultMessageText();
if( $oldtext !== false ) {
$oldtitlemsg = 'defaultmessagetext';
+ $oldContent = $this->toEditContent( $oldtext );
+ } else {
+ $oldContent = null;
}
} else {
- $oldtext = $this->mArticle->getRawText();
+ $oldContent = $this->getOriginalContent();
}
- $newtext = $this->mArticle->replaceSection(
- $this->section, $this->textbox1, $this->summary, $this->edittime );
- wfRunHooks( 'EditPageGetDiffText', array( $this, &$newtext ) );
+ $textboxContent = $this->toEditContent( $this->textbox1 );
+
+ $newContent = $this->mArticle->replaceSectionContent(
+ $this->section, $textboxContent,
+ $this->summary, $this->edittime );
+
+ ContentHandler::runLegacyHooks( 'EditPageGetDiffText', array( $this, &$newContent ) );
+ wfRunHooks( 'EditPageGetDiffContent', array( $this, &$newContent ) );
$popts = ParserOptions::newFromUserAndLang( $wgUser, $wgContLang );
- $newtext = $wgParser->preSaveTransform( $newtext, $this->mTitle, $wgUser, $popts );
+ $newContent = $newContent->preSaveTransform( $this->mTitle, $wgUser, $popts );
- if ( $oldtext !== false || $newtext != '' ) {
+ if ( ( $oldContent && !$oldContent->isEmpty() ) || ( $newContent && !$newContent->isEmpty() ) ) {
$oldtitle = wfMessage( $oldtitlemsg )->parse();
$newtitle = wfMessage( 'yourtext' )->parse();
- $de = new DifferenceEngine( $this->mArticle->getContext() );
- $de->setText( $oldtext, $newtext );
+ $de = $oldContent->getContentHandler()->createDifferenceEngine( $this->mArticle->getContext() );
+ $de->setContent( $oldContent, $newContent );
+
$difftext = $de->getDiff( $oldtitle, $newtitle );
$de->showDiffStyle();
} else {
if ( wfRunHooks( 'EditPageBeforeConflictDiff', array( &$this, &$wgOut ) ) ) {
$wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourdiff" );
- $de = new DifferenceEngine( $this->mArticle->getContext() );
- $de->setText( $this->textbox2, $this->textbox1 );
+ $content1 = $this->toEditContent( $this->textbox1 );
+ $content2 = $this->toEditContent( $this->textbox2 );
+
+ $handler = ContentHandler::getForModelID( $this->contentModel );
+ $de = $handler->createDifferenceEngine( $this->mArticle->getContext() );
+ $de->setContent( $content2, $content1 );
$de->showDiff(
wfMessage( 'yourtext' )->parse(),
wfMessage( 'storedversion' )->text()
);
// Quick paranoid permission checks...
if ( is_object( $data ) ) {
- if ( $data->log_deleted & LogPage::DELETED_USER ) {
+ if ( $data->log_deleted & LogPage::DELETED_USER )
$data->user_name = wfMessage( 'rev-deleted-user' )->escaped();
- }
-
- if ( $data->log_deleted & LogPage::DELETED_COMMENT ) {
+ if ( $data->log_deleted & LogPage::DELETED_COMMENT )
$data->log_comment = wfMessage( 'rev-deleted-comment' )->escaped();
- }
}
return $data;
}
/**
* Get the rendered text for previewing.
+ * @throws MWException
* @return string
*/
function getPreviewText() {
return $parsedNote;
}
- if ( $this->mTriedSave && !$this->mTokenOk ) {
- if ( $this->mTokenOkExceptSuffix ) {
- $note = wfMessage( 'token_suffix_mismatch' )->plain();
- } else {
- $note = wfMessage( 'session_fail_preview' )->plain();
- }
- } elseif ( $this->incompleteForm ) {
- $note = wfMessage( 'edit_form_incomplete' )->plain();
- } else {
- $note = wfMessage( 'previewnote' )->plain() .
- ' [[#' . self::EDITFORM_ID . '|' . $wgLang->getArrow() . ' ' . wfMessage( 'continue-editing' )->text() . ']]';
- }
+ $note = '';
- $parserOptions = $this->mArticle->makeParserOptions( $this->mArticle->getContext() );
+ try {
+ $content = $this->toEditContent( $this->textbox1 );
- $parserOptions->setEditSection( false );
- $parserOptions->setIsPreview( true );
- $parserOptions->setIsSectionPreview( !is_null( $this->section ) && $this->section !== '' );
+ if ( $this->mTriedSave && !$this->mTokenOk ) {
+ if ( $this->mTokenOkExceptSuffix ) {
+ $note = wfMessage( 'token_suffix_mismatch' )->plain() ;
- # don't parse non-wikitext pages, show message about preview
- if ( $this->mTitle->isCssJsSubpage() || !$this->mTitle->isWikitextPage() ) {
- if ( $this->mTitle->isCssJsSubpage() ) {
- $level = 'user';
- } elseif ( $this->mTitle->isCssOrJsPage() ) {
- $level = 'site';
- } else {
- $level = false;
- }
-
- # Used messages to make sure grep find them:
- # Messages: usercsspreview, userjspreview, sitecsspreview, sitejspreview
- $class = 'mw-code';
- if ( $level ) {
- if ( preg_match( "/\\.css$/", $this->mTitle->getText() ) ) {
- $previewtext = "<div id='mw-{$level}csspreview'>\n" . wfMessage( "{$level}csspreview" )->text() . "\n</div>";
- $class .= " mw-css";
- } elseif ( preg_match( "/\\.js$/", $this->mTitle->getText() ) ) {
- $previewtext = "<div id='mw-{$level}jspreview'>\n" . wfMessage( "{$level}jspreview" )->text() . "\n</div>";
- $class .= " mw-js";
} else {
- throw new MWException( 'A CSS/JS (sub)page but which is not css nor js!' );
+ $note = wfMessage( 'session_fail_preview' )->plain() ;
}
- $parserOutput = $wgParser->parse( $previewtext, $this->mTitle, $parserOptions );
- $previewHTML = $parserOutput->getText();
+ } elseif ( $this->incompleteForm ) {
+ $note = wfMessage( 'edit_form_incomplete' )->plain() ;
} else {
- $previewHTML = '';
+ $note = wfMessage( 'previewnote' )->plain() .
+ ' [[#' . self::EDITFORM_ID . '|' . $wgLang->getArrow() . ' ' . wfMessage( 'continue-editing' )->text() . ']]';
}
- $previewHTML .= "<pre class=\"$class\" dir=\"ltr\">\n" . htmlspecialchars( $this->textbox1 ) . "\n</pre>\n";
- } else {
- $toparse = $this->textbox1;
+ $parserOptions = $this->mArticle->makeParserOptions( $this->mArticle->getContext() );
+ $parserOptions->setEditSection( false );
+ $parserOptions->setIsPreview( true );
+ $parserOptions->setIsSectionPreview( !is_null($this->section) && $this->section !== '' );
- # If we're adding a comment, we need to show the
- # summary as the headline
- if ( $this->section == "new" && $this->summary != "" ) {
- $toparse = wfMessage( 'newsectionheaderdefaultlevel', $this->summary )->inContentLanguage()->text() . "\n\n" . $toparse;
- }
+ # don't parse non-wikitext pages, show message about preview
+ if ( $this->mTitle->isCssJsSubpage() || $this->mTitle->isCssOrJsPage() ) {
+ if( $this->mTitle->isCssJsSubpage() ) {
+ $level = 'user';
+ } elseif( $this->mTitle->isCssOrJsPage() ) {
+ $level = 'site';
+ } else {
+ $level = false;
+ }
- wfRunHooks( 'EditPageGetPreviewText', array( $this, &$toparse ) );
+ if ( $content->getModel() == CONTENT_MODEL_CSS ) {
+ $format = 'css';
+ } elseif ( $content->getModel() == CONTENT_MODEL_JAVASCRIPT ) {
+ $format = 'js';
+ } else {
+ $format = false;
+ }
- $toparse = $wgParser->preSaveTransform( $toparse, $this->mTitle, $wgUser, $parserOptions );
- $parserOutput = $wgParser->parse( $toparse, $this->mTitle, $parserOptions );
+ # Used messages to make sure grep find them:
+ # Messages: usercsspreview, userjspreview, sitecsspreview, sitejspreview
+ if( $level && $format ) {
+ $note = "<div id='mw-{$level}{$format}preview'>" . wfMessage( "{$level}{$format}preview" )->text() . "</div>";
+ } else {
+ $note = wfMessage( 'previewnote' )->text() ;
+ }
+ } else {
+ $note = wfMessage( 'previewnote' )->text() ;
+ }
- $rt = Title::newFromRedirectArray( $this->textbox1 );
+ $rt = $content->getRedirectChain();
if ( $rt ) {
$previewHTML = $this->mArticle->viewRedirect( $rt, false );
} else {
- $previewHTML = $parserOutput->getText();
- }
- $this->mParserOutput = $parserOutput;
- $wgOut->addParserOutputNoText( $parserOutput );
+ # If we're adding a comment, we need to show the
+ # summary as the headline
+ if ( $this->section === "new" && $this->summary !== "" ) {
+ $content = $content->addSectionHeader( $this->summary );
+ }
+
+ $hook_args = array( $this, &$content );
+ ContentHandler::runLegacyHooks( 'EditPageGetPreviewText', $hook_args );
+ wfRunHooks( 'EditPageGetPreviewContent', $hook_args );
+
+ $parserOptions->enableLimitReport();
- if ( count( $parserOutput->getWarnings() ) ) {
- $note .= "\n\n" . implode( "\n\n", $parserOutput->getWarnings() );
+ # For CSS/JS pages, we should have called the ShowRawCssJs hook here.
+ # But it's now deprecated, so never mind
+
+ $content = $content->preSaveTransform( $this->mTitle, $wgUser, $parserOptions );
+ $parserOutput = $content->getParserOutput( $this->getArticle()->getTitle(), null, $parserOptions );
+
+ $previewHTML = $parserOutput->getText();
+ $this->mParserOutput = $parserOutput;
+ $wgOut->addParserOutputNoText( $parserOutput );
+
+ if ( count( $parserOutput->getWarnings() ) ) {
+ $note .= "\n\n" . implode( "\n\n", $parserOutput->getWarnings() );
+ }
}
+ } catch ( MWContentSerializationException $ex ) {
+ $m = wfMessage('content-failed-to-parse', $this->contentModel, $this->contentFormat, $ex->getMessage() );
+ $note .= "\n\n" . $m->parse();
+ $previewHTML = '';
}
if ( $this->isConflict ) {
* @return string
*/
public static function schemaVersion() {
- return "0.7";
+ return "0.8";
}
/**
'xmlns' => "http://www.mediawiki.org/xml/export-$ver/",
'xmlns:xsi' => "http://www.w3.org/2001/XMLSchema-instance",
'xsi:schemaLocation' => "http://www.mediawiki.org/xml/export-$ver/ " .
- "http://www.mediawiki.org/xml/export-$ver.xsd",
+ "http://www.mediawiki.org/xml/export-$ver.xsd", #TODO: how do we get a new version up there?
'version' => $ver,
'xml:lang' => $wgLanguageCode ),
null ) .
$out .= " " . Xml::elementClean( 'comment', array(), strval( $row->rev_comment ) ) . "\n";
}
- if ( $row->rev_sha1 && !( $row->rev_deleted & Revision::DELETED_TEXT ) ) {
- $out .= " " . Xml::element('sha1', null, strval( $row->rev_sha1 ) ) . "\n";
- } else {
- $out .= " <sha1/>\n";
- }
-
$text = '';
if ( $row->rev_deleted & Revision::DELETED_TEXT ) {
$out .= " " . Xml::element( 'text', array( 'deleted' => 'deleted' ) ) . "\n";
"" ) . "\n";
}
+ if ( $row->rev_sha1 && !( $row->rev_deleted & Revision::DELETED_TEXT ) ) {
+ $out .= " " . Xml::element('sha1', null, strval( $row->rev_sha1 ) ) . "\n";
+ } else {
+ $out .= " <sha1/>\n";
+ }
+
+ if ( isset( $row->rev_content_model ) && !is_null( $row->rev_content_model ) ) {
+ $content_model = strval( $row->rev_content_model );
+ } else {
+ // probably using $wgContentHandlerUseDB = false;
+ // @todo: test!
+ $title = Title::makeTitle( $row->page_namespace, $row->page_title );
+ $content_model = ContentHandler::getDefaultModelFor( $title );
+ }
+
+ $out .= " " . Xml::element('model', null, strval( $content_model ) ) . "\n";
+
+ if ( isset( $row->rev_content_format ) && !is_null( $row->rev_content_format ) ) {
+ $content_format = strval( $row->rev_content_format );
+ } else {
+ // probably using $wgContentHandlerUseDB = false;
+ // @todo: test!
+ $content_handler = ContentHandler::getForModelID( $content_model );
+ $content_format = $content_handler->getDefaultFormat();
+ }
+
+ $out .= " " . Xml::element('format', null, strval( $content_format ) ) . "\n";
+
wfRunHooks( 'XmlDumpWriterWriteRevision', array( &$this, &$out, $row, $text ) );
$out .= " </revision>\n";
/**
* @param $sink DumpOutput
* @param $param
+ * @throws MWException
*/
function __construct( &$sink, $param ) {
parent::__construct( $sink );
*
* @param $data String
* @param $storageParams Array: associative array of parameters for the ExternalStore object.
- * @throws DBConnectionError|DBQueryError|MWException
- * @return string The URL of the stored data item, or false on error
+ * @throws MWException|DBConnectionError|DBQueryError
+ * @return string|bool The URL of the stored data item, or false on error
*/
public static function insertToDefault( $data, $storageParams = array() ) {
global $wgDefaultExternalStore;
*/
function fetchFromURL( $url ) {
$path = explode( '/', $url );
- $cluster = $path[2];
- $id = $path[3];
+ $cluster = $path[2];
+ $id = $path[3];
if ( isset( $path[4] ) ) {
$itemID = $path[4];
} else {
'eu_external_id' => $this->getId() ),
__METHOD__ );
}
-
+
/**
* Check whether this external user id is already linked with
* a local user.
? User::newFromId( $row->eu_local_id )
: null;
}
-
+
}
($row->rc_deleted & Revision::DELETED_COMMENT)
? wfMessage('rev-deleted-comment')->escaped()
: $row->rc_comment,
- $actiontext
+ $actiontext
);
}
$diffText = '';
// Don't bother generating the diff if we won't be able to show it
if ( $wgFeedDiffCutoff > 0 ) {
- $de = new DifferenceEngine( $title, $oldid, $newid );
- $diffText = $de->getDiff(
- wfMessage( 'previousrevision' )->text(), // hack
- wfMessage( 'revisionasof',
- $wgLang->timeanddate( $timestamp ),
- $wgLang->date( $timestamp ),
- $wgLang->time( $timestamp ) )->text() );
+ $rev = Revision::newFromId( $oldid );
+
+ if ( !$rev ) {
+ $diffText = false;
+ } else {
+ $context = clone RequestContext::getMain();
+ $context->setTitle( $title );
+
+ $contentHandler = $rev->getContentHandler();
+ $de = $contentHandler->createDifferenceEngine( $context, $oldid, $newid );
+ $diffText = $de->getDiff(
+ wfMessage( 'previousrevision' )->text(), // hack
+ wfMessage( 'revisionasof',
+ $wgLang->timeanddate( $timestamp ),
+ $wgLang->date( $timestamp ),
+ $wgLang->time( $timestamp ) )->text() );
+ }
}
if ( $wgFeedDiffCutoff <= 0 || ( strlen( $diffText ) > $wgFeedDiffCutoff ) ) {
} else {
$rev = Revision::newFromId( $newid );
if( $wgFeedDiffCutoff <= 0 || is_null( $rev ) ) {
- $newtext = '';
+ $newContent = ContentHandler::getForTitle( $title )->makeEmptyContent();
+ } else {
+ $newContent = $rev->getContent();
+ }
+
+ if ( $newContent instanceof TextContent ) {
+ // only textual content has a "source view".
+ $text = $newContent->getNativeData();
+
+ if ( $wgFeedDiffCutoff <= 0 || strlen( $text ) > $wgFeedDiffCutoff ) {
+ $html = null;
+ } else {
+ $html = nl2br( htmlspecialchars( $text ) );
+ }
} else {
- $newtext = $rev->getText();
+ //XXX: we could get an HTML representation of the content via getParserOutput, but that may
+ // contain JS magic and generally may not be suitable for inclusion in a feed.
+ // Perhaps Content should have a getDescriptiveHtml method and/or a getSourceText method.
+ //Compare also ApiFeedContributions::feedItemDesc
+ $html = null;
}
- if ( $wgFeedDiffCutoff <= 0 || strlen( $newtext ) > $wgFeedDiffCutoff ) {
+
+ if ( $html === null ) {
+
// Omit large new page diffs, bug 29110
+ // Also use diff link for non-textual content
$diffText = self::getDiffLink( $title, $newid );
} else {
$diffText = '<p><b>' . wfMessage( 'newpage' )->text() . '</b></p>' .
- '<div>' . nl2br( htmlspecialchars( $newtext ) ) . '</div>';
+ '<div>' . $html . '</div>';
}
}
$completeText .= $diffText;
* @param $reason String: reason of the deletion
* @param $suppress Boolean: whether to mark all deleted versions as restricted
* @param $user User object performing the request
+ * @throws MWException
* @return bool|Status
*/
public static function doDelete( &$title, &$file, &$oldimage, $reason, $suppress, User $user = null ) {
*
* @file
* @author Niklas Laxström
- * @author Antoine Musso
+ * @author Antoine Musso
*/
/**
* which will be assumed as INT if the data is an integer.
*
* @param $data Mixed: value to guess type for
+ * @throws MWException
* @exception MWException Unsupported datatype
* @return int Type constant
*/
*
* @param $name String: option name
* @param $strict Boolean: throw an exception when the option does not exist (default false)
+ * @throws MWException
* @return Boolean: true if option exist, false otherwise
*/
public function validateName( $name, $strict = false ) {
/**
* Validate and set an option integer value
- * The value will be altered to fit in the range.
+ * The value will be altered to fit in the range.
*
* @param $name String: option name
* @param $min Int: minimum value
* @param $max Int: maximum value
+ * @throws MWException
* @exception MWException Option is not of type int
* @return null
*/
* @param $value Mixed
* @param $default Mixed
* @param $changed Array to alter
+ * @throws MWException
*/
function wfAppendToArrayIfNotDefault( $key, $value, $default, &$changed ) {
if ( is_null( $changed ) ) {
*
* @param $text String
* @param $file String filename
+ * @throws MWException
*/
function wfErrorLog( $text, $file ) {
if ( substr( $file, 0, 4 ) == 'udp:' ) {
* but now throws an exception instead, with similar results.
*
* @param $msg String: message shown when dying.
+ * @throws MWException
*/
function wfDebugDieBacktrace( $msg = '' ) {
throw new MWException( $msg );
* @param $dir String: full path to directory to create
* @param $mode Integer: chmod value to use, default is $wgDirectoryMode
* @param $caller String: optional caller param for debugging.
+ * @throws MWException
* @return bool
*/
function wfMkdirParents( $dir, $mode = null, $caller = null ) {
*
* @param $req_ver Mixed: the version to check, can be a string, an integer, or
* a float
+ * @throws MWException
*/
function wfUsePHP( $req_ver ) {
$php_ver = PHP_VERSION;
*
* @param $req_ver Mixed: the version to check, can be a string, an integer, or
* a float
+ * @throws MWException
*/
function wfUseMW( $req_ver ) {
global $wgVersion;
* Set format in which to display the form
* @param $format String the name of the format to use, must be one of
* $this->availableDisplayFormats
+ * @throws MWException
* @since 1.20
* @return HTMLForm $this for chaining calls (since 1.20)
*/
* Initialise a new Object for the field
* @param $fieldname string
* @param $descriptor string input Descriptor, as described above
+ * @throws MWException
* @return HTMLFormField subclass
*/
static function loadInputFromParameters( $fieldname, $descriptor ) {
* @attention When doing method chaining, that should be the very last
* method call before displayForm().
*
+ * @throws MWException
* @return HTMLForm $this for chaining calls (since 1.20)
*/
function prepareForm() {
/**
* Validate all the fields, and call the submision callback
* function if everything is kosher.
+ * @throws MWException
* @return Mixed Bool true == Successful submission, Bool false
- * == No submission attempted, anything else == Error to
- * display.
+ * == No submission attempted, anything else == Error to
+ * display.
*/
function trySubmit() {
# Check for validation
/**
* Initialise the object
* @param $params array Associative Array. See HTMLForm doc for syntax.
+ * @throws MWException
*/
function __construct( $params ) {
$this->mParams = $params;
public function getRaw( $value ) {
list( $errors, $errorClass ) = $this->getErrorsAndErrorClass( $value );
$inputHtml = $this->getInputHTML( $value );
- $fieldType = get_class( $this );
$helptext = $this->getHelpTextHtmlRaw( $this->getHelpText() );
$cellAttributes = array();
$label = $this->getLabelHtml( $cellAttributes );
protected static $handlers = array();
+ /**
+ * Clears hooks registered via Hooks::register(). Does not touch $wgHooks.
+ * This is intended for use while testing and will fail if MW_PHPUNIT_TEST is not defined.
+ *
+ * @since 1.21
+ *
+ * @param $name String: the name of the hook to clear.
+ *
+ * @throws MWException if not in testing mode.
+ */
+ public static function clear( $name ) {
+ if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
+ throw new MWException( 'can not reset hooks in operation.' );
+ }
+
+ unset( self::$handlers[$name] );
+ }
+
+
/**
* Attach an event handler to a given hook
*
* @since 1.18
*
- * @param $name Mixed: name of hook
+ * @param $name String: name of hook
* @param $callback Mixed: callback function to attach
*/
public static function register( $name, $callback ) {
/**
* Returns true if a hook has a function registered to it.
+ * The function may have been registered either via Hooks::register or in $wgHooks.
*
* @since 1.18
*
- * @param $name Mixed: name of hook
- * @return Boolean: true if a hook has a function registered to it
+ * @param $name String: name of hook
+ * @return Boolean: true if the hook has a function registered to it
*/
public static function isRegistered( $name ) {
- if( !isset( self::$handlers[$name] ) ) {
- self::$handlers[$name] = array();
- }
+ global $wgHooks;
- return ( count( self::$handlers[$name] ) != 0 );
+ return !empty( $wgHooks[$name] ) || !empty( self::$handlers[$name] );
}
/**
* Returns an array of all the event functions attached to a hook
- *
+ * This combines functions registered via Hooks::register and with $wgHooks.
* @since 1.18
*
- * @param $name Mixed: name of the hook
+ * @throws MWException
+ * @throws FatalError
+ * @param $name String: name of the hook
+ *
* @return array
*/
public static function getHandlers( $name ) {
- if( !isset( self::$handlers[$name] ) ) {
+ global $wgHooks;
+
+ // Return quickly in the most common case
+ if ( empty( self::$handlers[$name] ) && empty( $wgHooks[$name] ) ) {
return array();
}
- return self::$handlers[$name];
+ if ( !is_array( self::$handlers ) ) {
+ throw new MWException( "Local hooks array is not an array!\n" );
+ }
+
+ if ( !is_array( $wgHooks ) ) {
+ throw new MWException( "Global hooks array is not an array!\n" );
+ }
+
+ if ( empty( Hooks::$handlers[$name] ) ) {
+ $hooks = $wgHooks[$name];
+ } elseif ( empty( $wgHooks[$name] ) ) {
+ $hooks = Hooks::$handlers[$name];
+ } else {
+ // so they are both not empty...
+ $hooks = array_merge( Hooks::$handlers[$name], $wgHooks[$name] );
+ }
+
+ if ( !is_array( $hooks ) ) {
+ throw new MWException( "Hooks array for event '$name' is not an array!\n" );
+ }
+
+ return $hooks;
}
/**
* Call hook functions defined in Hooks::register
*
- * Because programmers assign to $wgHooks, we need to be very
- * careful about its contents. So, there's a lot more error-checking
- * in here than would normally be necessary.
- *
- * @since 1.18
- *
* @param $event String: event name
- * @param $args Array: parameters passed to hook functions
- * @throws MWException
- * @throws FatalError
+ * @param $args Array: parameters passed to hook functions
+ *
* @return Boolean True if no handler aborted the hook
*/
public static function run( $event, $args = array() ) {
global $wgHooks;
// Return quickly in the most common case
- if ( !isset( self::$handlers[$event] ) && !isset( $wgHooks[$event] ) ) {
+ if ( empty( self::$handlers[$event] ) && empty( $wgHooks[$event] ) ) {
return true;
}
- if ( !is_array( self::$handlers ) ) {
- throw new MWException( "Local hooks array is not an array!\n" );
- }
-
- if ( !is_array( $wgHooks ) ) {
- throw new MWException( "Global hooks array is not an array!\n" );
- }
-
- $new_handlers = (array) self::$handlers;
- $old_handlers = (array) $wgHooks;
-
- $hook_array = array_merge( $new_handlers, $old_handlers );
-
- if ( !is_array( $hook_array[$event] ) ) {
- throw new MWException( "Hooks array for event '$event' is not an array!\n" );
- }
+ $hooks = self::getHandlers( $event );
- foreach ( $hook_array[$event] as $index => $hook ) {
+ foreach ( $hooks as $hook ) {
$object = null;
$method = null;
$func = null;
if ( count( $hook ) < 1 ) {
throw new MWException( 'Empty array in hooks for ' . $event . "\n" );
} elseif ( is_object( $hook[0] ) ) {
- $object = $hook_array[$event][$index][0];
+ $object = $hook[0];
if ( $object instanceof Closure ) {
$closure = true;
if ( count( $hook ) > 1 ) {
} elseif ( is_string( $hook ) ) { # functions look like strings, too
$func = $hook;
} elseif ( is_object( $hook ) ) {
- $object = $hook_array[$event][$index];
+ $object = $hook;
if ( $object instanceof Closure ) {
$closure = true;
} else {
if ( $nsId < NS_MAIN || in_array( $nsId, $params['exclude'] ) ) {
continue;
}
- if ( $nsId === 0 ) {
+ if ( $nsId === NS_MAIN ) {
// For other namespaces use use the namespace prefix as label, but for
// main we don't use "" but the user message descripting it (e.g. "(Main)" or "(Article)")
$nsName = wfMessage( 'blanknamespace' )->text();
+ } elseif ( is_int( $nsId ) ) {
+ $nsName = $wgContLang->convertNamespace( $nsId );
}
$optionsHtml[] = Html::element(
'option', array(
return $s;
}
+
+ /**
+ * Generate a srcset attribute value from an array mapping pixel densities
+ * to URLs. Note that srcset supports width and height values as well, which
+ * are not used here.
+ *
+ * @param array $urls
+ * @return string
+ */
+ static function srcSet( $urls ) {
+ $candidates = array();
+ foreach( $urls as $density => $url ) {
+ // Image candidate syntax per current whatwg live spec, 2012-09-23:
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/embedded-content-1.html#attr-img-srcset
+ $candidates[] = "{$url} {$density}x";
+ }
+ return implode( ", ", $candidates );
+ }
}
* Generate a new request object
* @param $url String: url to use
* @param $options Array: (optional) extra params to pass (see Http::request())
+ * @throws MWException
* @return CurlHttpRequest|PhpHttpRequest
* @see MWHttpRequest::__construct
*/
* will be aborted.
*
* @param $callback Callback
+ * @throws MWException
*/
public function setCallback( $callback ) {
if ( !is_callable( $callback ) ) {
$out->addHTML( Xml::openElement( 'div', array( 'id' => 'mw-imagepage-content',
'lang' => $pageLang->getHtmlCode(), 'dir' => $pageLang->getDir(),
'class' => 'mw-content-'.$pageLang->getDir() ) ) );
+
parent::view();
+
$out->addHTML( Xml::closeElement( 'div' ) );
} else {
# Just need to set the right headers
}
/**
- * Overloading Article's getContent method.
+ * Overloading Article's getContentObject method.
*
* Omit noarticletext if sharedupload; text will be fetched from the
* shared upload server if possible.
* @return string
*/
- public function getContent() {
+ public function getContentObject() {
$this->loadFile();
if ( $this->mPage->getFile() && !$this->mPage->getFile()->isLocal() && 0 == $this->getID() ) {
- return '';
+ return null;
}
- return parent::getContent();
+ return parent::getContentObject();
}
protected function openShowImage() {
/**
* Primary entry point
+ * @throws MWException
* @return bool
*/
public function doImport() {
$this->debug( "Enter revision handler" );
$revisionInfo = array();
- $normalFields = array( 'id', 'timestamp', 'comment', 'minor', 'text' );
+ $normalFields = array( 'id', 'timestamp', 'comment', 'minor', 'model', 'format', 'text' );
$skip = false;
if ( isset( $revisionInfo['text'] ) ) {
$revision->setText( $revisionInfo['text'] );
}
+ if ( isset( $revisionInfo['model'] ) ) {
+ $revision->setModel( $revisionInfo['model'] );
+ }
+ if ( isset( $revisionInfo['format'] ) ) {
+ $revision->setFormat( $revisionInfo['format'] );
+ }
$revision->setTitle( $pageInfo['_title'] );
if ( isset( $revisionInfo['timestamp'] ) ) {
var $timestamp = "20010115000000";
var $user = 0;
var $user_text = "";
+ var $model = null;
+ var $format = null;
var $text = "";
+ var $content = null;
var $comment = "";
var $minor = false;
var $type = "";
$this->user_text = $ip;
}
+ /**
+ * @param $model
+ */
+ function setModel( $model ) {
+ $this->model = $model;
+ }
+
+ /**
+ * @param $format
+ */
+ function setFormat( $format ) {
+ $this->format = $format;
+ }
+
/**
* @param $text
*/
/**
* @return string
+ *
+ * @deprecated Since 1.21, use getContent() instead.
*/
function getText() {
+ ContentHandler::deprecated( __METHOD__, '1.21' );
+
return $this->text;
}
+ /**
+ * @return Content
+ */
+ function getContent() {
+ if ( is_null( $this->content ) ) {
+ $this->content =
+ ContentHandler::makeContent(
+ $this->text,
+ $this->getTitle(),
+ $this->getModel(),
+ $this->getFormat()
+ );
+ }
+
+ return $this->content;
+ }
+
+ /**
+ * @return String
+ */
+ function getModel() {
+ if ( is_null( $this->model ) ) {
+ $this->model = $this->getTitle()->getContentModel();
+ }
+
+ return $this->model;
+ }
+
+ /**
+ * @return String
+ */
+ function getFormat() {
+ if ( is_null( $this->model ) ) {
+ $this->format = ContentHandler::getForTitle( $this->getTitle() )->getDefaultFormat();
+ }
+
+ return $this->format;
+ }
+
/**
* @return string
*/
# Insert the row
$revision = new Revision( array(
'page' => $pageId,
- 'text' => $this->getText(),
+ 'content_model' => $this->getModel(),
+ 'content_format' => $this->getFormat(),
+ 'text' => $this->getContent()->serialize( $this->getFormat() ), //XXX: just set 'content' => $this->getContent()?
'comment' => $this->getComment(),
'user' => $userId,
'user_text' => $userText,
class LinkFilter {
/**
- * Check whether $text contains a link to $filterEntry
+ * Check whether $content contains a link to $filterEntry
*
- * @param $text String: text to check
+ * @param $content Content: content to check
* @param $filterEntry String: domainparts, see makeRegex() for more details
* @return Integer: 0 if no match or 1 if there's at least one match
*/
- static function matchEntry( $text, $filterEntry ) {
+ static function matchEntry( Content $content, $filterEntry ) {
+ if ( !( $content instanceof TextContent ) ) {
+ //TODO: handle other types of content too.
+ // Maybe create ContentHandler::matchFilter( LinkFilter ).
+ // Think about a common base class for LinkFilter and MagicWord.
+ return 0;
+ }
+
+ $text = $content->getNativeData();
+
$regex = LinkFilter::makeRegex( $filterEntry );
return preg_match( $regex, $text );
}
* @param $context IContextSource context to use to get the messages
* @param $namespace int Namespace number
* @param $title string Text of the title, without the namespace part
+ * @return string
*/
public static function getInvalidTitleDescription( IContextSource $context, $namespace, $title ) {
global $wgContLang;
if ( !$thumb ) {
$s = self::makeBrokenImageLinkObj( $title, $fp['title'], '', '', '', $time == true );
} else {
+ self::processResponsiveImages( $file, $thumb, $hp );
$params = array(
'alt' => $fp['alt'],
'title' => $fp['title'],
$hp['width'] = isset( $fp['upright'] ) ? 130 : 180;
}
$thumb = false;
+ $noscale = false;
if ( !$exists ) {
$outerWidth = $hp['width'] + 2;
} elseif ( isset( $fp['framed'] ) ) {
// Use image dimensions, don't scale
$thumb = $file->getUnscaledThumb( $hp );
+ $noscale = true;
} else {
# Do not present an image bigger than the source, for bitmap-style images
# This is a hack to maintain compatibility with arbitrary pre-1.10 behaviour
$s .= wfMessage( 'thumbnail_error', '' )->escaped();
$zoomIcon = '';
} else {
+ if ( !$noscale ) {
+ self::processResponsiveImages( $file, $thumb, $hp );
+ }
$params = array(
'alt' => $fp['alt'],
'title' => $fp['title'],
return str_replace( "\n", ' ', $s );
}
+ /**
+ * Process responsive images: add 1.5x and 2x subimages to the thumbnail, where
+ * applicable.
+ *
+ * @param File $file
+ * @param MediaOutput $thumb
+ * @param array $hp image parameters
+ */
+ protected static function processResponsiveImages( $file, $thumb, $hp ) {
+ global $wgResponsiveImages;
+ if ( $wgResponsiveImages ) {
+ $hp15 = $hp;
+ $hp15['width'] = round( $hp['width'] * 1.5 );
+ $hp20 = $hp;
+ $hp20['width'] = $hp['width'] * 2;
+ if ( isset( $hp['height'] ) ) {
+ $hp15['height'] = round( $hp['height'] * 1.5 );
+ $hp20['height'] = $hp['height'] * 2;
+ }
+
+ $thumb15 = $file->transform( $hp15 );
+ $thumb20 = $file->transform( $hp20 );
+ if ( $thumb15->url !== $thumb->url ) {
+ $thumb->responsiveUrls['1.5'] = $thumb15->url;
+ }
+ if ( $thumb20->url !== $thumb->url ) {
+ $thumb->responsiveUrls['2'] = $thumb20->url;
+ }
+ }
+ }
+
/**
* Make a "broken" link to an image
*
* @param $title Title of the page we're updating
* @param $parserOutput ParserOutput: output from a full parse of this page
* @param $recursive Boolean: queue jobs for recursive updates?
+ * @throws MWException
*/
function __construct( $title, $parserOutput, $recursive = true ) {
parent::__construct( false ); // no implicit transaction
}
$this->mParserOutput = $parserOutput;
+
$this->mLinks = $parserOutput->getLinks();
$this->mImages = $parserOutput->getImages();
$this->mTemplates = $parserOutput->getTemplates();
parent::__construct( false ); // no implicit transaction
$this->mPage = $page;
+
+ if ( !$page->exists() ) {
+ throw new MWException( "Page ID not known, perhaps the page doesn't exist?" );
+ }
}
/**
__METHOD__ );
}
}
+
+ /**
+ * Update all the appropriate counts in the category table.
+ * @param $added array associative array of category name => sort key
+ * @param $deleted array associative array of category name => sort key
+ */
+ function updateCategoryCounts( $added, $deleted ) {
+ $a = WikiPage::factory( $this->mTitle );
+ $a->updateCategoryCounts(
+ array_keys( $added ), array_keys( $deleted )
+ );
+ }
}
* for $wgLocalisationCacheConf.
*
* @param $conf Array
+ * @throws MWException
*/
function __construct( $conf ) {
global $wgCacheDirectory;
/**
* Initialise a language in this object. Rebuild the cache if necessary.
* @param $code
+ * @throws MWException
*/
protected function initLanguage( $code ) {
if ( isset( $this->initialisedLangs[$code] ) ) {
* Read a PHP file containing localisation data.
* @param $_fileName
* @param $_fileType
+ * @throws MWException
* @return array
*/
protected function readPHPFile( $_fileName, $_fileType ) {
* Load localisation data for a given language for both core and extensions
* and save it to the persistent cache store and the process cache
* @param $code
+ * @throws MWException
*/
public function recache( $code ) {
global $wgExtensionMessagesFiles;
*/
protected $title = null;
+ /**
+ * Content object representing the message
+ */
+ protected $content = null;
+
/**
* @var string
*/
* turned off.
* @since 1.17
* @param $lang Mixed: language code or Language object.
+ * @throws MWException
* @return Message: $this
*/
public function inLanguage( $lang ) {
return $this;
}
+ /**
+ * Returns the message as a Content object.
+ * @return Content
+ */
+ public function content() {
+ if ( !$this->content ) {
+ $this->content = new MessageContent( $this->key );
+ }
+
+ return $this->content;
+ }
+
/**
* Returns the message parsed from wikitext to HTML.
* @since 1.17
return '<' . $key . '>';
}
+ # Replace $* with a list of parameters for &uselang=qqx.
+ if ( strpos( $string, '$*' ) !== false ) {
+ $paramlist = '';
+ if ( $this->parameters !== array() ) {
+ $paramlist = ': $' . implode( ', $', range( 1, count( $this->parameters ) ) );
+ }
+ $string = str_replace( '$*', $paramlist, $string );
+ }
+
# Replace parameters before text parsing
$string = $this->replaceParameters( $string, 'before' );
/**
* Wrapper for what ever method we use to get message contents
* @since 1.17
+ * @throws MWException
* @return string
*/
protected function fetchMessage() {
// Save the old and new blobs for later
$oldBlob = $row->mr_blob;
$newBlob = self::generateMessageBlob( $module, $lang );
-
+
$newRow = array(
'mr_resource' => $name,
'mr_lang' => $lang,
* @param $resourceLoader ResourceLoader object
* @param $modules Array of module names
* @param $lang String: language code
+ * @throws MWException
* @return array Array mapping module names to blobs
*/
private static function getFromDB( ResourceLoader $resourceLoader, $modules, $lang ) {
* @param $index
* @param $method
*
+ * @throws MWException
* @return bool
*/
private static function isMethodValidFor( $index, $method ) {
* Returns array of all defined namespaces with their canonical
* (English) names.
*
+ * @param bool $rebuild rebuild namespace list (default = false). Used for testing.
+ *
* @return array
* @since 1.17
*/
- public static function getCanonicalNamespaces() {
+ public static function getCanonicalNamespaces( $rebuild = false ) {
static $namespaces = null;
- if ( $namespaces === null ) {
+ if ( $namespaces === null || $rebuild ) {
global $wgExtraNamespaces, $wgCanonicalNamespaceNames;
$namespaces = array( NS_MAIN => '' ) + $wgCanonicalNamespaceNames;
if ( is_array( $wgExtraNamespaces ) ) {
/**
* Standard output handler for use with ob_start
- *
+ *
* @param $s string
- *
+ *
* @return string
*/
function wfOutputHandler( $s ) {
/**
* Handler that compresses data with gzip if allowed by the Accept header.
* Unlike ob_gzhandler, it works for HEAD requests too.
- *
+ *
* @param $s string
*
* @return string
* @param $interface Boolean: use interface language ($wgLang instead of
* $wgContLang) while parsing language sensitive magic
* words like GRAMMAR and PLURAL. This also disables
- * LanguageConverter.
+ * LanguageConverter.
* @param $language Language object: target language object, will override
* $interface
+ * @throws MWException
* @return String: HTML
*/
public function parse( $text, $linestart = true, $interface = false, $language = null ) {
wfRunHooks( 'AfterFinalPageOutput', array( $this ) );
$this->sendCacheControl();
+
ob_end_flush();
+
wfProfileOut( __METHOD__ );
}
$this->prepareErrorPage( $title );
if ( $msg instanceof Message ){
- $this->addHTML( $msg->parse() );
+ $this->addHTML( $msg->parseAsBlock() );
} else {
$this->addWikiMsgArray( $msg, $params );
}
* @param $action String: action that was denied or null if unknown
*/
public function showPermissionsErrorPage( $errors, $action = null ) {
- global $wgGroupPermissions;
-
// For some action (read, edit, create and upload), display a "login to do this action"
// error if all of the following conditions are met:
// 1. the user is not logged in
if ( in_array( $action, array( 'read', 'edit', 'createpage', 'createtalk', 'upload' ) )
&& $this->getUser()->isAnon() && count( $errors ) == 1 && isset( $errors[0][0] )
&& ( $errors[0][0] == 'badaccess-groups' || $errors[0][0] == 'badaccess-group0' )
- && ( ( isset( $wgGroupPermissions['user'][$action] ) && $wgGroupPermissions['user'][$action] )
- || ( isset( $wgGroupPermissions['autoconfirmed'][$action] ) && $wgGroupPermissions['autoconfirmed'][$action] ) )
+ && ( User::groupHasPermission( 'user', $action )
+ || User::groupHasPermission( 'autoconfirmed', $action ) )
) {
$displayReturnto = null;
* Display an error page noting that a given permission bit is required.
* @deprecated since 1.18, just throw the exception directly
* @param $permission String: key required
+ * @throws PermissionsError
*/
public function permissionRequired( $permission ) {
throw new PermissionsError( $permission );
* @param $protected Boolean: is this a permissions error?
* @param $reasons Array: list of reasons for this error, as returned by Title::getUserPermissionsErrors().
* @param $action String: action that was denied or null if unknown
+ * @throws ReadOnlyError
*/
public function readOnlyPage( $source = null, $protected = false, $reasons = array(), $action = null ) {
$this->setRobotPolicy( 'noindex,nofollow' );
*/
private function addDefaultModules() {
global $wgIncludeLegacyJavaScript, $wgPreloadJavaScriptMwUtil, $wgUseAjax,
- $wgAjaxWatch;
+ $wgAjaxWatch, $wgResponsiveImages;
// Add base resources
$this->addModules( array(
if ( $this->isArticle() && $this->getUser()->getOption( 'editondblclick' ) ) {
$this->addModules( 'mediawiki.action.view.dblClickEdit' );
}
+
+ // Support for high-density display images
+ if ( $wgResponsiveImages ) {
+ $this->addModules( 'mediawiki.hidpi' );
+ }
}
/**
* @return array
*/
public function getJSVars() {
- global $wgUseAjax, $wgContLang;
+ global $wgContLang;
$latestRevID = 0;
$pageID = 0;
$msgSpecs = array_values( $msgSpecs );
$s = $wrap;
foreach ( $msgSpecs as $n => $spec ) {
- $options = array();
if ( is_array( $spec ) ) {
$args = $spec;
$name = array_shift( $args );
* version are hardcoded here
*/
function wfPHPVersionError( $type ){
- $mwVersion = '1.20';
+ $mwVersion = '1.21';
$phpVersion = PHP_VERSION;
$message = "MediaWiki $mwVersion requires at least PHP version 5.3.2, you are using PHP $phpVersion.";
if( $type == 'index.php' ) {
global $wgUseRCPatrol, $wgEnableAPI, $wgRCMaxAge;
$watchlistdaysMax = ceil( $wgRCMaxAge / ( 3600 * 24 ) );
-
+
## Watchlist #####################################
$defaultPreferences['watchlistdays'] = array(
'type' => 'float',
$this->mArticle = $article;
$this->mTitle = $article->getTitle();
$this->mApplicableTypes = $this->mTitle->getRestrictionTypes();
-
+
// Check if the form should be disabled.
// If it is, the form will be available in read-only to show levels.
$this->mPermErrors = $this->mTitle->getUserPermissionsErrors( 'protect', $wgUser );
# They shouldn't be able to do this anyway, but just to make sure, ensure that cascading restrictions aren't being applied
# to a semi-protected page.
- global $wgGroupPermissions;
-
$edit_restriction = isset( $this->mRestrictions['edit'] ) ? $this->mRestrictions['edit'] : '';
$this->mCascade = $wgRequest->getBool( 'mwProtect-cascade' );
if ($this->mCascade && ($edit_restriction != 'protect') &&
- !(isset($wgGroupPermissions[$edit_restriction]['protect']) && $wgGroupPermissions[$edit_restriction]['protect'] ) )
+ !User::groupHasPermission( $edit_restriction, 'protect' ) )
$this->mCascade = false;
$status = $this->mArticle->doUpdateRestrictions( $this->mRestrictions, $expiry, $this->mCascade, $reasonstr, $wgUser );
}
function buildCleanupScript() {
- global $wgRestrictionLevels, $wgGroupPermissions, $wgOut;
+ global $wgRestrictionLevels, $wgOut;
$cascadeableLevels = array();
foreach( $wgRestrictionLevels as $key ) {
- if ( ( isset( $wgGroupPermissions[$key]['protect'] ) && $wgGroupPermissions[$key]['protect'] )
+ if ( User::groupHasPermission( $key, 'protect' )
|| $key == 'protect'
) {
$cascadeableLevels[] = $key;
/**
* For back-compat, subclasses may return a raw SQL query here, as a string.
* This is stronly deprecated; getQueryInfo() should be overridden instead.
+ * @throws MWException
* @return string
*/
function getSQL() {
protected $mTextRow;
protected $mTitle;
protected $mCurrent;
+ protected $mContentModel;
+ protected $mContentFormat;
+ protected $mContent;
+ protected $mContentHandler;
// Revision deletion constants
const DELETED_TEXT = 1;
* @param $flags Integer Bitfield (optional)
* @return Revision or null
*/
- public static function newFromTitle( $title, $id = 0, $flags = null ) {
+ public static function newFromTitle( $title, $id = 0, $flags = 0 ) {
$conds = array(
'page_namespace' => $title->getNamespace(),
'page_title' => $title->getDBkey()
} else {
// Use a join to get the latest revision
$conds[] = 'rev_id=page_latest';
- // Callers assume this will be up-to-date
- $flags = is_int( $flags ) ? $flags : self::READ_LATEST; // b/c
}
return self::newFromConds( $conds, (int)$flags );
}
* @param $flags Integer Bitfield (optional)
* @return Revision or null
*/
- public static function newFromPageId( $pageId, $revId = 0, $flags = null ) {
+ public static function newFromPageId( $pageId, $revId = 0, $flags = 0 ) {
$conds = array( 'page_id' => $pageId );
if ( $revId ) {
$conds['rev_id'] = $revId;
} else {
// Use a join to get the latest revision
$conds[] = 'rev_id = page_latest';
- // Callers assume this will be up-to-date
- $flags = is_int( $flags ) ? $flags : self::READ_LATEST; // b/c
}
return self::newFromConds( $conds, (int)$flags );
}
* @param $row
* @param $overrides array
*
+ * @throws MWException
* @return Revision
*/
public static function newFromArchiveRow( $row, $overrides = array() ) {
+ global $wgContentHandlerUseDB;
+
$attribs = $overrides + array(
'page' => isset( $row->ar_page_id ) ? $row->ar_page_id : null,
'id' => isset( $row->ar_rev_id ) ? $row->ar_rev_id : null,
'deleted' => $row->ar_deleted,
'len' => $row->ar_len,
'sha1' => isset( $row->ar_sha1 ) ? $row->ar_sha1 : null,
+ 'content_model' => isset( $row->ar_content_model ) ? $row->ar_content_model : null,
+ 'content_format' => isset( $row->ar_content_format ) ? $row->ar_content_format : null,
);
+
+ if ( !$wgContentHandlerUseDB ) {
+ unset( $attribs['content_model'] );
+ unset( $attribs['content_format'] );
+ }
+
if ( isset( $row->ar_text ) && !$row->ar_text_id ) {
// Pre-1.5 ar_text row
$attribs['text'] = self::getRevisionText( $row, 'ar_' );
* @return array
*/
public static function selectFields() {
- return array(
+ global $wgContentHandlerUseDB;
+
+ $fields = array(
'rev_id',
'rev_page',
'rev_text_id',
'rev_deleted',
'rev_len',
'rev_parent_id',
- 'rev_sha1'
+ 'rev_sha1',
);
+
+ if ( $wgContentHandlerUseDB ) {
+ $fields[] = 'rev_content_format';
+ $fields[] = 'rev_content_model';
+ }
+
+ return $fields;
}
/**
* Constructor
*
* @param $row Mixed: either a database row or an array
+ * @throws MWException
* @access private
*/
function __construct( $row ) {
$this->mTitle = null;
}
+ if( !isset( $row->rev_content_model ) || is_null( $row->rev_content_model ) ) {
+ $this->mContentModel = null; # determine on demand if needed
+ } else {
+ $this->mContentModel = strval( $row->rev_content_model );
+ }
+
+ if( !isset( $row->rev_content_format ) || is_null( $row->rev_content_format ) ) {
+ $this->mContentFormat = null; # determine on demand if needed
+ } else {
+ $this->mContentFormat = strval( $row->rev_content_format );
+ }
+
// Lazy extraction...
$this->mText = null;
if( isset( $row->old_text ) ) {
// Build a new revision to be saved...
global $wgUser; // ugh
+
+ # if we have a content object, use it to set the model and type
+ if ( !empty( $row['content'] ) ) {
+ //@todo: when is that set? test with external store setup! check out insertOn() [dk]
+ if ( !empty( $row['text_id'] ) ) {
+ throw new MWException( "Text already stored in external store (id {$row['text_id']}), "
+ . "can't serialize content object" );
+ }
+
+ $row['content_model'] = $row['content']->getModel();
+ # note: mContentFormat is initializes later accordingly
+ # note: content is serialized later in this method!
+ # also set text to null?
+ }
+
$this->mId = isset( $row['id'] ) ? intval( $row['id'] ) : null;
$this->mPage = isset( $row['page'] ) ? intval( $row['page'] ) : null;
$this->mTextId = isset( $row['text_id'] ) ? intval( $row['text_id'] ) : null;
$this->mParentId = isset( $row['parent_id'] ) ? intval( $row['parent_id'] ) : null;
$this->mSha1 = isset( $row['sha1'] ) ? strval( $row['sha1'] ) : null;
+ $this->mContentModel = isset( $row['content_model'] ) ? strval( $row['content_model'] ) : null;
+ $this->mContentFormat = isset( $row['content_format'] ) ? strval( $row['content_format'] ) : null;
+
// Enforce spacing trimming on supplied text
$this->mComment = isset( $row['comment'] ) ? trim( strval( $row['comment'] ) ) : null;
$this->mText = isset( $row['text'] ) ? rtrim( strval( $row['text'] ) ) : null;
$this->mTextRow = null;
- $this->mTitle = null; # Load on demand if needed
+ $this->mTitle = isset( $row['title'] ) ? $row['title'] : null;
+
+ // if we have a Content object, override mText and mContentModel
+ if ( !empty( $row['content'] ) ) {
+ if ( !( $row['content'] instanceof Content ) ) {
+ throw new MWException( '`content` field must contain a Content object.' );
+ }
+
+ $handler = $this->getContentHandler();
+ $this->mContent = $row['content'];
+
+ $this->mContentModel = $this->mContent->getModel();
+ $this->mContentHandler = null;
+
+ $this->mText = $handler->serializeContent( $row['content'], $this->getContentFormat() );
+ } elseif ( !is_null( $this->mText ) ) {
+ $handler = $this->getContentHandler();
+ $this->mContent = $handler->unserializeContent( $this->mText );
+ }
+
+ // if we have a Title object, override mPage. Useful for testing and convenience.
+ if ( isset( $row['title'] ) ) {
+ $this->mTitle = $row['title'];
+ $this->mPage = $this->mTitle->getArticleID();
+ } else {
+ $this->mTitle = null; // Load on demand if needed
+ }
+
+ // @todo: XXX: really? we are about to create a revision. it will usually then be the current one.
$this->mCurrent = false;
- # If we still have no length, see it we have the text to figure it out
+
+ // If we still have no length, see it we have the text to figure it out
if ( !$this->mSize ) {
- $this->mSize = is_null( $this->mText ) ? null : strlen( $this->mText );
+ if ( !is_null( $this->mContent ) ) {
+ $this->mSize = $this->mContent->getSize();
+ } else {
+ #NOTE: this should never happen if we have either text or content object!
+ $this->mSize = null;
+ }
}
- # Same for sha1
+
+ // Same for sha1
if ( $this->mSha1 === null ) {
$this->mSha1 = is_null( $this->mText ) ? null : self::base36Sha1( $this->mText );
}
+
+ // force lazy init
+ $this->getContentModel();
+ $this->getContentFormat();
} else {
throw new MWException( 'Revision constructor passed invalid row format.' );
}
if( isset( $this->mTitle ) ) {
return $this->mTitle;
}
- if( !is_null( $this->mId ) ) { //rev_id is defined as NOT NULL
+ if( !is_null( $this->mId ) ) { //rev_id is defined as NOT NULL, but this revision may not yet have been inserted.
$dbr = wfGetDB( DB_SLAVE );
$row = $dbr->selectRow(
array( 'page', 'revision' ),
$this->mTitle = Title::newFromRow( $row );
}
}
+
+ if ( !$this->mTitle && !is_null( $this->mPage ) && $this->mPage > 0 ) {
+ $this->mTitle = Title::newFromID( $this->mPage );
+ }
+
return $this->mTitle;
}
* Revision::RAW get the text regardless of permissions
* @param $user User object to check for, only if FOR_THIS_USER is passed
* to the $audience parameter
+ *
+ * @deprecated in 1.21, use getContent() instead
+ * @todo: replace usage in core
* @return String
*/
public function getText( $audience = self::FOR_PUBLIC, User $user = null ) {
+ ContentHandler::deprecated( __METHOD__, '1.21' );
+
+ $content = $this->getContent( $audience, $user );
+ return ContentHandler::getContentText( $content ); # returns the raw content text, if applicable
+ }
+
+ /**
+ * Fetch revision content if it's available to the specified audience.
+ * If the specified audience does not have the ability to view this
+ * revision, null will be returned.
+ *
+ * @param $audience Integer: one of:
+ * Revision::FOR_PUBLIC to be displayed to all users
+ * Revision::FOR_THIS_USER to be displayed to $wgUser
+ * Revision::RAW get the text regardless of permissions
+ * @param $user User object to check for, only if FOR_THIS_USER is passed
+ * to the $audience parameter
+ * @since 1.21
+ * @return Content
+ */
+ public function getContent( $audience = self::FOR_PUBLIC, User $user = null ) {
if( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_TEXT ) ) {
- return '';
+ return null;
} elseif( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_TEXT, $user ) ) {
- return '';
+ return null;
} else {
- return $this->getRawText();
+ return $this->getContentInternal();
}
}
* Fetch revision text without regard for view restrictions
*
* @return String
+ *
+ * @deprecated since 1.21. Instead, use Revision::getContent( Revision::RAW )
+ * or Revision::getSerializedData() as appropriate.
*/
public function getRawText() {
- if( is_null( $this->mText ) ) {
- // Revision text is immutable. Load on demand:
- $this->mText = $this->loadText();
- }
+ ContentHandler::deprecated( __METHOD__, "1.21" );
+
+ return $this->getText( self::RAW );
+ }
+
+ /**
+ * Fetch original serialized data without regard for view restrictions
+ *
+ * @since 1.21
+ * @return String
+ */
+ public function getSerializedData() {
return $this->mText;
}
+ /**
+ * Gets the content object for the revision
+ *
+ * @since 1.21
+ * @return Content
+ */
+ protected function getContentInternal() {
+ if( is_null( $this->mContent ) ) {
+ // Revision is immutable. Load on demand:
+
+ $handler = $this->getContentHandler();
+ $format = $this->getContentFormat();
+ $title = $this->getTitle();
+
+ if( is_null( $this->mText ) ) {
+ // Load text on demand:
+ $this->mText = $this->loadText();
+ }
+
+ $this->mContent = is_null( $this->mText ) ? null : $handler->unserializeContent( $this->mText, $format );
+ }
+
+ return $this->mContent->copy(); // NOTE: copy() will return $this for immutable content objects
+ }
+
+ /**
+ * Returns the content model for this revision.
+ *
+ * If no content model was stored in the database, $this->getTitle()->getContentModel() is
+ * used to determine the content model to use. If no title is know, CONTENT_MODEL_WIKITEXT
+ * is used as a last resort.
+ *
+ * @return String the content model id associated with this revision, see the CONTENT_MODEL_XXX constants.
+ **/
+ public function getContentModel() {
+ if ( !$this->mContentModel ) {
+ $title = $this->getTitle();
+ $this->mContentModel = ( $title ? $title->getContentModel() : CONTENT_MODEL_WIKITEXT );
+
+ assert( !empty( $this->mContentModel ) );
+ }
+
+ return $this->mContentModel;
+ }
+
+ /**
+ * Returns the content format for this revision.
+ *
+ * If no content format was stored in the database, the default format for this
+ * revision's content model is returned.
+ *
+ * @return String the content format id associated with this revision, see the CONTENT_FORMAT_XXX constants.
+ **/
+ public function getContentFormat() {
+ if ( !$this->mContentFormat ) {
+ $handler = $this->getContentHandler();
+ $this->mContentFormat = $handler->getDefaultFormat();
+
+ assert( !empty( $this->mContentFormat ) );
+ }
+
+ return $this->mContentFormat;
+ }
+
+ /**
+ * Returns the content handler appropriate for this revision's content model.
+ *
+ * @return ContentHandler
+ */
+ public function getContentHandler() {
+ if ( !$this->mContentHandler ) {
+ $model = $this->getContentModel();
+ $this->mContentHandler = ContentHandler::getForModelID( $model );
+
+ $format = $this->getContentFormat();
+
+ if ( !$this->mContentHandler->isSupportedFormat( $format ) ) {
+ throw new MWException( "Oops, the content format $format is not supported for this content model, $model" );
+ }
+ }
+
+ return $this->mContentHandler;
+ }
+
/**
* @return String
*/
* number on success and dies horribly on failure.
*
* @param $dbw DatabaseBase: (master connection)
+ * @throws MWException
* @return Integer
*/
public function insertOn( $dbw ) {
- global $wgDefaultExternalStore;
+ global $wgDefaultExternalStore, $wgContentHandlerUseDB;
wfProfileIn( __METHOD__ );
+ $this->checkContentModel();
+
$data = $this->mText;
$flags = self::compressRevisionText( $data );
$rev_id = isset( $this->mId )
? $this->mId
: $dbw->nextSequenceValue( 'revision_rev_id_seq' );
- $dbw->insert( 'revision',
- array(
- 'rev_id' => $rev_id,
- 'rev_page' => $this->mPage,
- 'rev_text_id' => $this->mTextId,
- 'rev_comment' => $this->mComment,
- 'rev_minor_edit' => $this->mMinorEdit ? 1 : 0,
- 'rev_user' => $this->mUser,
- 'rev_user_text' => $this->mUserText,
- 'rev_timestamp' => $dbw->timestamp( $this->mTimestamp ),
- 'rev_deleted' => $this->mDeleted,
- 'rev_len' => $this->mSize,
- 'rev_parent_id' => is_null( $this->mParentId )
- ? $this->getPreviousRevisionId( $dbw )
- : $this->mParentId,
- 'rev_sha1' => is_null( $this->mSha1 )
- ? self::base36Sha1( $this->mText )
- : $this->mSha1
- ), __METHOD__
+ $row = array(
+ 'rev_id' => $rev_id,
+ 'rev_page' => $this->mPage,
+ 'rev_text_id' => $this->mTextId,
+ 'rev_comment' => $this->mComment,
+ 'rev_minor_edit' => $this->mMinorEdit ? 1 : 0,
+ 'rev_user' => $this->mUser,
+ 'rev_user_text' => $this->mUserText,
+ 'rev_timestamp' => $dbw->timestamp( $this->mTimestamp ),
+ 'rev_deleted' => $this->mDeleted,
+ 'rev_len' => $this->mSize,
+ 'rev_parent_id' => is_null( $this->mParentId )
+ ? $this->getPreviousRevisionId( $dbw )
+ : $this->mParentId,
+ 'rev_sha1' => is_null( $this->mSha1 )
+ ? Revision::base36Sha1( $this->mText )
+ : $this->mSha1,
);
+ if ( $wgContentHandlerUseDB ) {
+ //NOTE: Store null for the default model and format, to save space.
+ //XXX: Makes the DB sensitive to changed defaults. Make this behaviour optional? Only in miser mode?
+
+ $model = $this->getContentModel();
+ $format = $this->getContentFormat();
+
+ $title = $this->getTitle();
+
+ if ( $title === null ) {
+ throw new MWException( "Insufficient information to determine the title of the revision's page!" );
+ }
+
+ $defaultModel = ContentHandler::getDefaultModelFor( $title );
+ $defaultFormat = ContentHandler::getForModelID( $defaultModel )->getDefaultFormat();
+
+ $row[ 'rev_content_model' ] = ( $model === $defaultModel ) ? null : $model;
+ $row[ 'rev_content_format' ] = ( $format === $defaultFormat ) ? null : $format;
+ }
+
+ $dbw->insert( 'revision', $row, __METHOD__ );
+
$this->mId = !is_null( $rev_id ) ? $rev_id : $dbw->insertId();
wfRunHooks( 'RevisionInsertComplete', array( &$this, $data, $flags ) );
return $this->mId;
}
+ protected function checkContentModel() {
+ global $wgContentHandlerUseDB;
+
+ $title = $this->getTitle(); //note: may return null for revisions that have not yet been inserted.
+
+ $model = $this->getContentModel();
+ $format = $this->getContentFormat();
+ $handler = $this->getContentHandler();
+
+ if ( !$handler->isSupportedFormat( $format ) ) {
+ $t = $title->getPrefixedDBkey();
+
+ throw new MWException( "Can't use format $format with content model $model on $t" );
+ }
+
+ if ( !$wgContentHandlerUseDB && $title ) {
+ // if $wgContentHandlerUseDB is not set, all revisions must use the default content model and format.
+
+ $defaultModel = ContentHandler::getDefaultModelFor( $title );
+ $defaultHandler = ContentHandler::getForModelID( $defaultModel );
+ $defaultFormat = $defaultHandler->getDefaultFormat();
+
+ if ( $this->getContentModel() != $defaultModel ) {
+ $t = $title->getPrefixedDBkey();
+
+ throw new MWException( "Can't save non-default content model with \$wgContentHandlerUseDB disabled: "
+ . "model is $model , default for $t is $defaultModel" );
+ }
+
+ if ( $this->getContentFormat() != $defaultFormat ) {
+ $t = $title->getPrefixedDBkey();
+
+ throw new MWException( "Can't use non-default content format with \$wgContentHandlerUseDB disabled: "
+ . "format is $format, default for $t is $defaultFormat" );
+ }
+ }
+
+ $content = $this->getContent( Revision::RAW );
+
+ if ( !$content->isValid() ) {
+ $t = $title->getPrefixedDBkey();
+
+ throw new MWException( "Content of $t is not valid! Content model is $model" );
+ }
+ }
+
/**
* Get the base 36 SHA-1 value for a string of text
* @param $text String
* @return Revision|null on error
*/
public static function newNullRevision( $dbw, $pageId, $summary, $minor ) {
+ global $wgContentHandlerUseDB;
+
wfProfileIn( __METHOD__ );
+ $fields = array( 'page_latest', 'page_namespace', 'page_title',
+ 'rev_text_id', 'rev_len', 'rev_sha1' );
+
+ if ( $wgContentHandlerUseDB ) {
+ $fields[] = 'rev_content_model';
+ $fields[] = 'rev_content_format';
+ }
+
$current = $dbw->selectRow(
array( 'page', 'revision' ),
- array( 'page_latest', 'page_namespace', 'page_title',
- 'rev_text_id', 'rev_len', 'rev_sha1' ),
+ $fields,
array(
'page_id' => $pageId,
'page_latest=rev_id',
__METHOD__ );
if( $current ) {
- $revision = new Revision( array(
+ $row = array(
'page' => $pageId,
'comment' => $summary,
'minor_edit' => $minor,
'parent_id' => $current->page_latest,
'len' => $current->rev_len,
'sha1' => $current->rev_sha1
- ) );
+ );
+
+ if ( $wgContentHandlerUseDB ) {
+ $row[ 'content_model' ] = $current->rev_content_model;
+ $row[ 'content_format' ] = $current->rev_content_format;
+ }
+
+ $revision = new Revision( $row );
$revision->setTitle( Title::makeTitle( $current->page_namespace, $current->page_title ) );
} else {
$revision = null;
}
return true;
}
-}
\ No newline at end of file
+}
* attribs regex matches.
*
* @param $set Array
+ * @throws MWException
* @return String
*/
private static function getTagAttributeCallback( $set ) {
*
* @param $fname String Name of called method
* @param $args Array Arguments to the method
+ * @throws MWException
* @return mixed
*/
function __call( $fname, $args ) {
* By default it is capitalized.
*
* @param $name string Language name, e.g. "English" or "español"
+ * @return string
* @private
*/
function formatLanguageName( $name ) {
*
* @param $name String
* @param $subpage String|Bool subpage string, or false to not use a subpage
+ * @throws MWException
* @return Title object
*/
public static function getTitleFor( $name, $subpage = false ) {
*
* @param $fName String Name of called method
* @param $a Array Arguments to the method
+ * @throws MWException
* @deprecated since 1.17, call parent::__construct()
*/
public function __call( $fName, $a ) {
* pages?
*/
public function isRestricted() {
- global $wgGroupPermissions;
// DWIM: If all anons can do something, then it is not restricted
- return $this->mRestriction != '' && empty( $wgGroupPermissions['*'][$this->mRestriction] );
+ return $this->mRestriction != '' && User::groupHasPermission( '*', $this->mRestriction );
}
/**
* Called from execute() to check if the given user can perform this action.
* Failures here must throw subclasses of ErrorPageError.
* @param $user User
+ * @throws UserBlockedError
* @return Bool true
- * @throws ErrorPageError
*/
protected function checkExecutePermissions( User $user ) {
$this->checkPermissions();
* Abort the database transaction started via beginTransaction (if any).
*/
public function abortTransaction() {
- if ( $this->mHasTransaction ) {
+ if ( $this->mHasTransaction ) { //XXX: actually... maybe always?
$this->mDb->rollback( get_class( $this ) . '::abortTransaction' );
$this->mHasTransaction = false;
}
* @param $dbkeys Array
*/
protected function invalidatePages( $namespace, array $dbkeys ) {
- if ( !count( $dbkeys ) ) {
+ if ( $dbkeys === array() ) {
return;
}
'page_touched < ' . $this->mDb->addQuotes( $now )
), __METHOD__
);
+
foreach ( $res as $row ) {
$ids[] = $row->page_id;
}
- if ( !count( $ids ) ) {
+
+ if ( $ids === array() ) {
return;
}
*/
/**
- * An HTTP 1.0 client built for the purposes of purging Squid and Varnish.
- * Uses asynchronous I/O, allowing purges to be done in a highly parallel
- * manner.
+ * An HTTP 1.0 client built for the purposes of purging Squid and Varnish.
+ * Uses asynchronous I/O, allowing purges to be done in a highly parallel
+ * manner.
*
* Could be replaced by curl_multi_exec() or some such.
*/
return array( $socket );
}
- /**
+ /**
* Get the host's IP address.
* Does not support IPv6 at present due to the lack of a convenient interface in PHP.
*/
* @param $url string
*/
public function queuePurge( $url ) {
+ global $wgSquidPurgeUseHostHeader;
$url = SquidUpdate::expand( str_replace( "\n", '', $url ) );
- $this->requests[] = "PURGE $url HTTP/1.0\r\n" .
- "Connection: Keep-Alive\r\n" .
- "Proxy-Connection: Keep-Alive\r\n" .
- "User-Agent: " . Http::userAgent() . ' ' . __CLASS__ . "\r\n\r\n";
+ $request = array();
+ if ( $wgSquidPurgeUseHostHeader ) {
+ $url = wfParseUrl( $url );
+ $host = $url['host'];
+ if ( isset( $url['port'] ) && strlen( $url['port'] ) > 0 ) {
+ $host .= ":" . $url['port'];
+ }
+ $path = $url['path'];
+ if ( isset( $url['query'] ) && is_string( $url['query'] ) ) {
+ $path = wfAppendQuery( $path, $url['query'] );
+ }
+ $request[] = "PURGE $path HTTP/1.1";
+ $request[] = "Host: $host";
+ } else {
+ $request[] = "PURGE $url HTTP/1.0";
+ }
+ $request[] = "Connection: Keep-Alive";
+ $request[] = "Proxy-Connection: Keep-Alive";
+ $request[] = "User-Agent: " . Http::userAgent() . ' ' . __CLASS__;
+ // Two ''s to create \r\n\r\n
+ $request[] = '';
+ $request[] = '';
+
+ $this->requests[] = implode( "\r\n", $request );
if ( $this->currentRequestIndex === null ) {
$this->nextRequest();
}
$numReady = socket_select( $readSockets, $writeSockets, $exceptSockets, $timeout );
wfRestoreWarnings();
if ( $numReady === false ) {
- wfDebugLog( 'squid', __METHOD__.': Error in stream_select: ' .
+ wfDebugLog( 'squid', __METHOD__.': Error in stream_select: ' .
socket_strerror( socket_last_error() ) . "\n" );
break;
}
}
}
+ /**
+ * Get the error message as HTML. This is done by parsing the wikitext error
+ * message.
+ */
+ public function getHTML( $shortContext = false, $longContext = false ) {
+ $text = $this->getWikiText( $shortContext, $longContext );
+ return MessageCache::singleton()->transform( $text, true );
+ }
+
/**
* Return an array with the wikitext for each item in the array.
* @param $errors Array
* @param $fname string Full name and path of the file to stream
* @param $headers array Any additional headers to send
* @param $sendErrors bool Send error messages if errors occur (like 404)
+ * @throws MWException
* @return bool Success
*/
public static function stream( $fname, $headers = array(), $sendErrors = true ) {
/**
* An iterator which works exactly like:
- *
+ *
* foreach ( explode( $delim, $s ) as $element ) {
* ...
* }
* @param $name String: name of the method called in this object.
* @param $level Integer: level to go in the stact trace to get the function
* who called this function.
+ * @throws MWException
*/
function _unstub( $name = '_unstub', $level = 2 ) {
static $recursionLevel = 0;
*
* @since 1.20
*
- * @return string Formatted timestamp
+ * @return Message Formatted timestamp
*/
public function getHumanTimestamp() {
$then = $this->getTimestamp( TS_UNIX );
var $mFragment; // /< Title fragment (i.e. the bit after the #)
var $mArticleID = -1; // /< Article ID, fetched from the link cache on demand
var $mLatestID = false; // /< ID of most recent revision
+ var $mContentModel = false; // /< ID of the page's content model, i.e. one of the CONTENT_MODEL_XXX constants
private $mEstimateRevisions; // /< Estimated number of revisions; null of not loaded
var $mRestrictions = array(); // /< Array of groups allowed to edit this article
var $mOldRestrictions = false;
}
}
+ /**
+ * Returns a list of fields that are to be selected for initializing Title objects or LinkCache entries.
+ * Uses $wgContentHandlerUseDB to determine whether to include page_content_model.
+ *
+ * @return array
+ */
+ protected static function getSelectFields() {
+ global $wgContentHandlerUseDB;
+
+ $fields = array(
+ 'page_namespace', 'page_title', 'page_id',
+ 'page_len', 'page_is_redirect', 'page_latest',
+ );
+
+ if ( $wgContentHandlerUseDB ) {
+ $fields[] = 'page_content_model';
+ }
+
+ return $fields;
+ }
+
/**
* Create a new Title from an article ID
*
$db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
$row = $db->selectRow(
'page',
- array(
- 'page_namespace', 'page_title', 'page_id',
- 'page_len', 'page_is_redirect', 'page_latest',
- ),
+ self::getSelectFields(),
array( 'page_id' => $id ),
__METHOD__
);
$res = $dbr->select(
'page',
- array(
- 'page_namespace', 'page_title', 'page_id',
- 'page_len', 'page_is_redirect', 'page_latest',
- ),
+ self::getSelectFields(),
array( 'page_id' => $ids ),
__METHOD__
);
$this->mRedirect = (bool)$row->page_is_redirect;
if ( isset( $row->page_latest ) )
$this->mLatestID = (int)$row->page_latest;
+ if ( isset( $row->page_content_model ) )
+ $this->mContentModel = strval( $row->page_content_model );
+ else
+ $this->mContentModel = false; # initialized lazily in getContentModel()
} else { // page not found
$this->mArticleID = 0;
$this->mLength = 0;
$this->mRedirect = false;
$this->mLatestID = 0;
+ $this->mContentModel = false; # initialized lazily in getContentModel()
}
}
$t->mArticleID = ( $ns >= 0 ) ? -1 : 0;
$t->mUrlform = wfUrlencode( $t->mDbkeyform );
$t->mTextform = str_replace( '_', ' ', $title );
+ $t->mContentModel = false; # initialized lazily in getContentModel()
return $t;
}
*
* @param $text String: Text with possible redirect
* @return Title: The corresponding Title
+ * @deprecated since 1.21, use Content::getRedirectTarget instead.
*/
public static function newFromRedirect( $text ) {
- return self::newFromRedirectInternal( $text );
+ ContentHandler::deprecated( __METHOD__, '1.21' );
+
+ $content = ContentHandler::makeContent( $text, null, CONTENT_MODEL_WIKITEXT );
+ return $content->getRedirectTarget();
}
/**
*
* @param $text String Text with possible redirect
* @return Title
+ * @deprecated since 1.21, use Content::getUltimateRedirectTarget instead.
*/
public static function newFromRedirectRecurse( $text ) {
- $titles = self::newFromRedirectArray( $text );
- return $titles ? array_pop( $titles ) : null;
+ ContentHandler::deprecated( __METHOD__, '1.21' );
+
+ $content = ContentHandler::makeContent( $text, null, CONTENT_MODEL_WIKITEXT );
+ return $content->getUltimateRedirectTarget();
}
/**
*
* @param $text String Text with possible redirect
* @return Array of Titles, with the destination last
+ * @deprecated since 1.21, use Content::getRedirectChain instead.
*/
public static function newFromRedirectArray( $text ) {
- global $wgMaxRedirects;
- $title = self::newFromRedirectInternal( $text );
- if ( is_null( $title ) ) {
- return null;
- }
- // recursive check to follow double redirects
- $recurse = $wgMaxRedirects;
- $titles = array( $title );
- while ( --$recurse > 0 ) {
- if ( $title->isRedirect() ) {
- $page = WikiPage::factory( $title );
- $newtitle = $page->getRedirectTarget();
- } else {
- break;
- }
- // Redirects to some special pages are not permitted
- if ( $newtitle instanceOf Title && $newtitle->isValidRedirectTarget() ) {
- // the new title passes the checks, so make that our current title so that further recursion can be checked
- $title = $newtitle;
- $titles[] = $newtitle;
- } else {
- break;
- }
- }
- return $titles;
- }
+ ContentHandler::deprecated( __METHOD__, '1.21' );
- /**
- * Really extract the redirect destination
- * Do not call this function directly, use one of the newFromRedirect* functions above
- *
- * @param $text String Text with possible redirect
- * @return Title
- */
- protected static function newFromRedirectInternal( $text ) {
- global $wgMaxRedirects;
- if ( $wgMaxRedirects < 1 ) {
- //redirects are disabled, so quit early
- return null;
- }
- $redir = MagicWord::get( 'redirect' );
- $text = trim( $text );
- if ( $redir->matchStartAndRemove( $text ) ) {
- // Extract the first link and see if it's usable
- // Ensure that it really does come directly after #REDIRECT
- // Some older redirects included a colon, so don't freak about that!
- $m = array();
- if ( preg_match( '!^\s*:?\s*\[{2}(.*?)(?:\|.*?)?\]{2}!', $text, $m ) ) {
- // Strip preceding colon used to "escape" categories, etc.
- // and URL-decode links
- if ( strpos( $m[1], '%' ) !== false ) {
- // Match behavior of inline link parsing here;
- $m[1] = rawurldecode( ltrim( $m[1], ':' ) );
- }
- $title = Title::newFromText( $m[1] );
- // If the title is a redirect to bad special pages or is invalid, return null
- if ( !$title instanceof Title || !$title->isValidRedirectTarget() ) {
- return null;
- }
- return $title;
- }
- }
- return null;
+ $content = ContentHandler::makeContent( $text, null, CONTENT_MODEL_WIKITEXT );
+ return $content->getRedirectChain();
}
/**
return $this->mNamespace;
}
+ /**
+ * Get the page's content model id, see the CONTENT_MODEL_XXX constants.
+ *
+ * @return String: Content model id
+ */
+ public function getContentModel() {
+ if ( !$this->mContentModel ) {
+ $linkCache = LinkCache::singleton();
+ $this->mContentModel = $linkCache->getGoodLinkFieldObj( $this, 'model' );
+ }
+
+ if ( !$this->mContentModel ) {
+ $this->mContentModel = ContentHandler::getDefaultModelFor( $this );
+ }
+
+ if( !$this->mContentModel ) {
+ throw new MWException( "failed to determin content model!" );
+ }
+
+ return $this->mContentModel;
+ }
+
+ /**
+ * Convenience method for checking a title's content model name
+ *
+ * @param String $id The content model ID (use the CONTENT_MODEL_XXX constants).
+ * @return Boolean true if $this->getContentModel() == $id
+ */
+ public function hasContentModel( $id ) {
+ return $this->getContentModel() == $id;
+ }
+
/**
* Get the namespace text
*
* @return Bool
*/
public function isConversionTable() {
+ //@todo: ConversionTable should become a separate content model.
+
return $this->getNamespace() == NS_MEDIAWIKI &&
strpos( $this->getText(), 'Conversiontable/' ) === 0;
}
* @return Bool
*/
public function isWikitextPage() {
- $retval = !$this->isCssOrJsPage() && !$this->isCssJsSubpage();
- wfRunHooks( 'TitleIsWikitextPage', array( $this, &$retval ) );
- return $retval;
+ return $this->hasContentModel( CONTENT_MODEL_WIKITEXT );
}
/**
- * Could this page contain custom CSS or JavaScript, based
- * on the title?
+ * Could this page contain custom CSS or JavaScript for the global UI.
+ * This is generally true for pages in the MediaWiki namespace having CONTENT_MODEL_CSS
+ * or CONTENT_MODEL_JAVASCRIPT.
+ *
+ * This method does *not* return true for per-user JS/CSS. Use isCssJsSubpage() for that!
+ *
+ * Note that this method should not return true for pages that contain and show "inactive" CSS or JS.
*
* @return Bool
*/
public function isCssOrJsPage() {
- $retval = $this->mNamespace == NS_MEDIAWIKI
- && preg_match( '!\.(?:css|js)$!u', $this->mTextform ) > 0;
- wfRunHooks( 'TitleIsCssOrJsPage', array( $this, &$retval ) );
- return $retval;
+ $isCssOrJsPage = NS_MEDIAWIKI == $this->mNamespace
+ && ( $this->hasContentModel( CONTENT_MODEL_CSS )
+ || $this->hasContentModel( CONTENT_MODEL_JAVASCRIPT ) );
+
+ #NOTE: this hook is also called in ContentHandler::getDefaultModel. It's called here again to make sure
+ # hook funktions can force this method to return true even outside the mediawiki namespace.
+
+ wfRunHooks( 'TitleIsCssOrJsPage', array( $this, &$isCssOrJsPage ) );
+
+ return $isCssOrJsPage;
}
/**
* @return Bool
*/
public function isCssJsSubpage() {
- return ( NS_USER == $this->mNamespace and preg_match( "/\\/.*\\.(?:css|js)$/", $this->mTextform ) );
+ return ( NS_USER == $this->mNamespace && $this->isSubpage()
+ && ( $this->hasContentModel( CONTENT_MODEL_CSS )
+ || $this->hasContentModel( CONTENT_MODEL_JAVASCRIPT ) ) );
}
/**
* @return Bool
*/
public function isCssSubpage() {
- return ( NS_USER == $this->mNamespace && preg_match( "/\\/.*\\.css$/", $this->mTextform ) );
+ return ( NS_USER == $this->mNamespace && $this->isSubpage()
+ && $this->hasContentModel( CONTENT_MODEL_CSS ) );
}
/**
* @return Bool
*/
public function isJsSubpage() {
- return ( NS_USER == $this->mNamespace && preg_match( "/\\/.*\\.js$/", $this->mTextform ) );
+ return ( NS_USER == $this->mNamespace && $this->isSubpage()
+ && $this->hasContentModel( CONTENT_MODEL_JAVASCRIPT ) );
}
/**
if ( !$user->isAllowed( 'move' ) ) {
// User can't move anything
- global $wgGroupPermissions;
- $userCanMove = false;
- if ( isset( $wgGroupPermissions['user']['move'] ) ) {
- $userCanMove = $wgGroupPermissions['user']['move'];
- }
- $autoconfirmedCanMove = false;
- if ( isset( $wgGroupPermissions['autoconfirmed']['move'] ) ) {
- $autoconfirmedCanMove = $wgGroupPermissions['autoconfirmed']['move'];
- }
+ $userCanMove = User::groupHasPermission( 'user', 'move' );
+ $autoconfirmedCanMove = User::groupHasPermission( 'autoconfirmed', 'move' );
if ( $user->isAnon() && ( $userCanMove || $autoconfirmedCanMove ) ) {
// custom message if logged-in users without any special rights can move
$errors[] = array( 'movenologintext' );
* @return Array list of errors
*/
private function checkReadPermissions( $action, $user, $errors, $doExpensiveQueries, $short ) {
- global $wgWhitelistRead, $wgGroupPermissions, $wgRevokePermissions;
+ global $wgWhitelistRead, $wgRevokePermissions;
static $useShortcut = null;
# Initialize the $useShortcut boolean, to determine if we can skip quite a bit of code below
if ( is_null( $useShortcut ) ) {
$useShortcut = true;
- if ( empty( $wgGroupPermissions['*']['read'] ) ) {
+ if ( !User::groupHasPermission( '*', 'read' ) ) {
# Not a public wiki, so no shortcut
$useShortcut = false;
} elseif ( !empty( $wgRevokePermissions ) ) {
if ( !$this->getArticleID( $flags ) ) {
return $this->mRedirect = false;
}
+
$linkCache = LinkCache::singleton();
- $this->mRedirect = (bool)$linkCache->getGoodLinkFieldObj( $this, 'redirect' );
+ $cached = $linkCache->getGoodLinkFieldObj( $this, 'redirect' );
+ if ( $cached === null ) {
+ // TODO: check the assumption that the cache actually knows about this title
+ // and handle this, such as get the title from the database.
+ // See https://bugzilla.wikimedia.org/show_bug.cgi?id=37209
+ }
+
+ $this->mRedirect = (bool)$cached;
return $this->mRedirect;
}
return $this->mLength = 0;
}
$linkCache = LinkCache::singleton();
- $this->mLength = intval( $linkCache->getGoodLinkFieldObj( $this, 'length' ) );
+ $cached = $linkCache->getGoodLinkFieldObj( $this, 'length' );
+ if ( $cached === null ) { # check the assumption that the cache actually knows about this title
+ # XXX: this does apparently happen, see https://bugzilla.wikimedia.org/show_bug.cgi?id=37209
+ # as a stop gap, perhaps log this, but don't throw an exception?
+ throw new MWException( "LinkCache doesn't currently know about this title: " . $this->getPrefixedDBkey() );
+ }
+
+ $this->mLength = intval( $cached );
return $this->mLength;
}
return $this->mLatestID = 0;
}
$linkCache = LinkCache::singleton();
- $this->mLatestID = intval( $linkCache->getGoodLinkFieldObj( $this, 'revision' ) );
+ $cached = $linkCache->getGoodLinkFieldObj( $this, 'revision' );
+ if ( $cached === null ) { # check the assumption that the cache actually knows about this title
+ # XXX: this does apparently happen, see https://bugzilla.wikimedia.org/show_bug.cgi?id=37209
+ # as a stop gap, perhaps log this, but don't throw an exception?
+ throw new MWException( "LinkCache doesn't currently know about this title: " . $this->getPrefixedDBkey() );
+ }
+
+ $this->mLatestID = intval( $cached );
return $this->mLatestID;
}
$this->mRedirect = null;
$this->mLength = -1;
$this->mLatestID = false;
+ $this->mContentModel = false;
$this->mEstimateRevisions = null;
}
$res = $db->select(
array( 'page', $table ),
- array( 'page_namespace', 'page_title', 'page_id', 'page_len', 'page_is_redirect', 'page_latest' ),
+ self::getSelectFields(),
array(
"{$prefix}_from=page_id",
"{$prefix}_namespace" => $this->getNamespace(),
* @return Array of Title objects linking here
*/
public function getLinksFrom( $options = array(), $table = 'pagelinks', $prefix = 'pl' ) {
+ global $wgContentHandlerUseDB;
+
$id = $this->getArticleID();
# If the page doesn't exist; there can't be any link from this page
$namespaceFiled = "{$prefix}_namespace";
$titleField = "{$prefix}_title";
+ $fields = array( $namespaceFiled, $titleField, 'page_id', 'page_len', 'page_is_redirect', 'page_latest' );
+ if ( $wgContentHandlerUseDB ) $fields[] = 'page_content_model';
+
$res = $db->select(
array( $table, 'page' ),
- array( $namespaceFiled, $titleField, 'page_id', 'page_len', 'page_is_redirect', 'page_latest' ),
+ $fields,
array( "{$prefix}_from" => $id ),
__METHOD__,
$options,
* @return Mixed True on success, getUserPermissionsErrors()-like array on failure
*/
public function isValidMoveOperation( &$nt, $auth = true, $reason = '' ) {
- global $wgUser;
+ global $wgUser, $wgContentHandlerUseDB;
$errors = array();
if ( !$nt ) {
$errors[] = array( 'badarticleerror' );
}
+ // Content model checks
+ if ( !$wgContentHandlerUseDB &&
+ $this->getContentModel() !== $nt->getContentModel() ) {
+ // can't move a page if that would change the page's content model
+ $errors[] = array( 'bad-target-model',
+ ContentHandler::getLocalizedName( $this->getContentModel() ),
+ ContentHandler::getLocalizedName( $nt->getContentModel() ) );
+ }
+
// Image-specific checks
if ( $this->getNamespace() == NS_FILE ) {
$errors = array_merge( $errors, $this->validateFileMoveOperation( $nt ) );
$logType = 'move';
}
- $redirectSuppressed = !$createRedirect;
+ if ( $createRedirect ) {
+ $contentHandler = ContentHandler::getForTitle( $this );
+ $redirectContent = $contentHandler->makeRedirectContent( $nt );
+
+ // NOTE: If this page's content model does not support redirects, $redirectContent will be null.
+ } else {
+ $redirectContent = null;
+ }
$logEntry = new ManualLogEntry( 'move', $logType );
$logEntry->setPerformer( $wgUser );
$logEntry->setComment( $reason );
$logEntry->setParameters( array(
'4::target' => $nt->getPrefixedText(),
- '5::noredir' => $redirectSuppressed ? '1': '0',
+ '5::noredir' => $redirectContent ? '0': '1',
) );
$formatter = LogFormatter::newFromEntry( $logEntry );
if ( !is_object( $nullRevision ) ) {
throw new MWException( 'No valid null revision produced in ' . __METHOD__ );
}
- $nullRevId = $nullRevision->insertOn( $dbw );
+
+ $nullRevision->insertOn( $dbw );
# Change the name of the target page:
$dbw->update( 'page',
}
# Recreate the redirect, this time in the other direction.
- if ( $redirectSuppressed ) {
+ if ( !$redirectContent ) {
WikiPage::onArticleDelete( $this );
} else {
- $mwRedir = MagicWord::get( 'redirect' );
- $redirectText = $mwRedir->getSynonym( 0 ) . ' [[' . $nt->getPrefixedText() . "]]\n";
$redirectArticle = WikiPage::factory( $this );
$newid = $redirectArticle->insertOn( $dbw );
if ( $newid ) { // sanity
$redirectRevision = new Revision( array(
'page' => $newid,
'comment' => $comment,
- 'text' => $redirectText ) );
+ 'content' => $redirectContent ) );
$redirectRevision->insertOn( $dbw );
$redirectArticle->updateRevisionOn( $dbw, $redirectRevision, 0 );
* @return Bool
*/
public function isSingleRevRedirect() {
+ global $wgContentHandlerUseDB;
+
$dbw = wfGetDB( DB_MASTER );
+
# Is it a redirect?
+ $fields = array( 'page_is_redirect', 'page_latest', 'page_id' );
+ if ( $wgContentHandlerUseDB ) $fields[] = 'page_content_model';
+
$row = $dbw->selectRow( 'page',
- array( 'page_is_redirect', 'page_latest', 'page_id' ),
+ $fields,
$this->pageCond(),
__METHOD__,
array( 'FOR UPDATE' )
$this->mArticleID = $row ? intval( $row->page_id ) : 0;
$this->mRedirect = $row ? (bool)$row->page_is_redirect : false;
$this->mLatestID = $row ? intval( $row->page_latest ) : false;
+ $this->mContentModel = $row && isset( $row->page_content_model ) ? strval( $row->page_content_model ) : false;
if ( !$this->mRedirect ) {
return false;
}
if( !is_object( $rev ) ){
return false;
}
- $text = $rev->getText();
+ $content = $rev->getContent();
# Does the redirect point to the source?
# Or is it a broken self-redirect, usually caused by namespace collisions?
- $m = array();
- if ( preg_match( "/\\[\\[\\s*([^\\]\\|]*)]]/", $text, $m ) ) {
- $redirTitle = Title::newFromText( $m[1] );
- if ( !is_object( $redirTitle ) ||
- ( $redirTitle->getPrefixedDBkey() != $this->getPrefixedDBkey() &&
- $redirTitle->getPrefixedDBkey() != $nt->getPrefixedDBkey() ) ) {
+ $redirTitle = $content->getRedirectTarget();
+
+ if ( $redirTitle ) {
+ if ( $redirTitle->getPrefixedDBkey() != $this->getPrefixedDBkey() &&
+ $redirTitle->getPrefixedDBkey() != $nt->getPrefixedDBkey() ) {
wfDebug( __METHOD__ . ": redirect points to other page\n" );
return false;
+ } else {
+ return true;
}
} else {
- # Fail safe
- wfDebug( __METHOD__ . ": failsafe\n" );
+ # Fail safe (not a redirect after all. strange.)
+ wfDebug( __METHOD__ . ": failsafe: database sais " . $nt->getPrefixedDBkey() .
+ " is a redirect, but it doesn't contain a valid redirect.\n" );
return false;
}
- return true;
}
/**
if ( $this->isSpecialPage() ) {
// special pages are in the user language
return $wgLang;
- } elseif ( $this->isCssOrJsPage() || $this->isCssJsSubpage() ) {
- // css/js should always be LTR and is, in fact, English
- return wfGetLangObj( 'en' );
- } elseif ( $this->getNamespace() == NS_MEDIAWIKI ) {
- // Parse mediawiki messages with correct target language
- list( /* $unused */, $lang ) = MessageCache::singleton()->figureMessage( $this->getText() );
- return wfGetLangObj( $lang );
}
- global $wgContLang;
- // If nothing special, it should be in the wiki content language
- $pageLang = $wgContLang;
+
+ //TODO: use the LinkCache to cache this! Note that this may depend on user settings, so the cache should be only per-request.
+ //NOTE: ContentHandler::getPageLanguage() may need to load the content to determine the page language!
+ $contentHandler = ContentHandler::getForTitle( $this );
+ $pageLang = $contentHandler->getPageLanguage( $this );
+
// Hook at the end because we don't want to override the above stuff
wfRunHooks( 'PageContentLanguage', array( $this, &$pageLang, $wgLang ) );
return wfGetLangObj( $pageLang );
* @return Language
*/
public function getPageViewLanguage() {
- $pageLang = $this->getPageLanguage();
- // If this is nothing special (so the content is converted when viewed)
- if ( !$this->isSpecialPage()
- && !$this->isCssOrJsPage() && !$this->isCssJsSubpage()
- && $this->getNamespace() !== NS_MEDIAWIKI
- ) {
+ global $wgLang;
+
+ if ( $this->isSpecialPage() ) {
// If the user chooses a variant, the content is actually
// in a language whose code is the variant code.
- $variant = $pageLang->getPreferredVariant();
- if ( $pageLang->getCode() !== $variant ) {
- $pageLang = Language::factory( $variant );
+ $variant = $wgLang->getPreferredVariant();
+ if ( $wgLang->getCode() !== $variant ) {
+ return Language::factory( $variant );
}
+
+ return $wgLang;
}
+
+ //NOTE: can't be cached persistently, depends on user settings
+ //NOTE: ContentHandler::getPageViewLanguage() may need to load the content to determine the page language!
+ $contentHandler = ContentHandler::getForTitle( $this );
+ $pageLang = $contentHandler->getPageViewLanguage( $this );
return $pageLang;
}
}
* - 'usable' Valid for batch processes and login
* - 'creatable' Valid for batch processes, login and account creation
*
+ * @throws MWException
* @return bool|string
*/
public static function getCanonicalName( $name, $validate = 'valid' ) {
if( $loggedOut !== null ) {
$this->mTouched = wfTimestamp( TS_MW, $loggedOut );
} else {
- $this->mTouched = '0'; # Allow any pages to be cached
+ $this->mTouched = '1'; # Allow any pages to be cached
}
$this->mToken = null; // Don't run cryptographic functions till we need a token
$defOpt = $wgDefaultUserOptions;
# default language setting
- $variant = $wgContLang->getDefaultVariant();
- $defOpt['variant'] = $variant;
- $defOpt['language'] = $variant;
+ $defOpt['variant'] = $wgContLang->getCode();
+ $defOpt['language'] = $wgContLang->getCode();
foreach( SearchEngine::searchableNamespaces() as $nsnum => $nsname ) {
$defOpt['searchNs'.$nsnum] = !empty( $wgNamespacesToBeSearchedDefault[$nsnum] );
}
}
/**
- * Add this existing user object to the database
+ * Add this existing user object to the database. If the user already
+ * exists, a fatal status object is returned, and the user object is
+ * initialised with the data from the database.
+ *
+ * Previously, this function generated a DB error due to a key conflict
+ * if the user already existed. Many extension callers use this function
+ * in code along the lines of:
+ *
+ * $user = User::newFromName( $name );
+ * if ( !$user->isLoggedIn() ) {
+ * $user->addToDatabase();
+ * }
+ * // do something with $user...
+ *
+ * However, this was vulnerable to a race condition (bug 16020). By
+ * initialising the user object if the user exists, we aim to support this
+ * calling sequence as far as possible.
+ *
+ * Note that if the user exists, this function will acquire a write lock,
+ * so it is still advisable to make the call conditional on isLoggedIn(),
+ * and to commit the transaction after calling.
+ *
+ * @return Status
*/
public function addToDatabase() {
$this->load();
'user_registration' => $dbw->timestamp( $this->mRegistration ),
'user_editcount' => 0,
'user_touched' => $dbw->timestamp( $this->mTouched ),
- ), __METHOD__
+ ), __METHOD__,
+ array( 'IGNORE' )
);
+ if ( !$dbw->affectedRows() ) {
+ $this->mId = $dbw->selectField( 'user', 'user_id',
+ array( 'user_name' => $this->mName ), __METHOD__ );
+ $loaded = false;
+ if ( $this->mId ) {
+ if ( $this->loadFromDatabase() ) {
+ $loaded = true;
+ }
+ }
+ if ( !$loaded ) {
+ throw new MWException( __METHOD__. ": hit a key conflict attempting " .
+ "to insert a user row, but then it doesn't exist when we select it!" );
+ }
+ return Status::newFatal( 'userexists' );
+ }
$this->mId = $dbw->insertId();
// Clear instance cache other than user table data, which is already accurate
$this->clearInstanceCache();
$this->saveOptions();
+ return Status::newGood();
}
/**
public static function getGroupsWithPermission( $role ) {
global $wgGroupPermissions;
$allowedGroups = array();
- foreach ( $wgGroupPermissions as $group => $rights ) {
- if ( isset( $rights[$role] ) && $rights[$role] ) {
+ foreach ( array_keys( $wgGroupPermissions ) as $group ) {
+ if ( self::groupHasPermission( $group, $role ) ) {
$allowedGroups[] = $group;
}
}
return $allowedGroups;
}
+ /**
+ * Check, if the given group has the given permission
+ *
+ * @param $group String Group to check
+ * @param $role String Role to check
+ * @return bool
+ */
+ public static function groupHasPermission( $group, $role ) {
+ global $wgGroupPermissions, $wgRevokePermissions;
+ return isset( $wgGroupPermissions[$group][$role] ) && $wgGroupPermissions[$group][$role]
+ && !( isset( $wgRevokePermissions[$group][$role] ) && $wgRevokePermissions[$group][$role] );
+ }
+
/**
* Get the localized descriptive name for a group, if it exists
*
* @todo document
*/
protected function loadOptions() {
+ global $wgContLang;
+
$this->load();
- if ( $this->mOptionsLoaded || !$this->getId() )
+
+ if ( $this->mOptionsLoaded ) {
return;
+ }
$this->mOptions = self::getDefaultOptions();
+ if ( !$this->getId() ) {
+ // For unlogged-in users, load language/variant options from request.
+ // There's no need to do it for logged-in users: they can set preferences,
+ // and handling of page content is done by $pageLang->getPreferredVariant() and such,
+ // so don't override user's choice (especially when the user chooses site default).
+ $variant = $wgContLang->getDefaultVariant();
+ $this->mOptions['variant'] = $variant;
+ $this->mOptions['language'] = $variant;
+ $this->mOptionsLoaded = true;
+ return;
+ }
+
// Maybe load from the object
if ( !is_null( $this->mOptionOverrides ) ) {
wfDebug( "User: loading options for user " . $this->getId() . " from override cache.\n" );
* @param $body String: email's text.
* @param $replyto MailAddress: optional reply-to email (default: null).
* @param $contentType String: optional custom Content-Type (default: text/plain; charset=UTF-8)
+ * @throws MWException
* @return Status object
*/
public static function send( $to, $from, $subject, $body, $replyto = null, $contentType = 'text/plain; charset=UTF-8' ) {
return $ret;
}
-
/**
* Unset an arbitrary value from our get/post data.
*
* Return the path and query string portion of the request URI.
* This will be suitable for use as a relative link in HTML output.
*
+ * @throws MWException
* @return String
*/
public function getRequestURL() {
* false if an error message has been shown and the request should be aborted.
*
* @param $extWhitelist array
+ * @throws HttpError
* @return bool
*/
public function checkUrlExtension( $extWhitelist = array() ) {
/**
* Work out the IP address based on various globals
* For trusted proxies, use the XFF client IP (first of the chain)
- *
+ *
* @since 1.19
*
+ * @throws MWException
* @return string
*/
public function getIP() {
* fake GET/POST values
* @param $wasPosted Bool: whether to treat the data as POST
* @param $session Mixed: session array or null
+ * @throws MWException
*/
public function __construct( $data = array(), $wasPosted = false, $session = null ) {
if( is_array( $data ) ) {
* - special pages
* - normal pages
*
+ * @throws MWException|PermissionsError|BadTitleError|HttpError
* @return void
*/
private function performRequest() {
wfProfileIn( __METHOD__ );
$request = $this->context->getRequest();
- $title = $this->context->getTitle();
+ $requestTitle = $title = $this->context->getTitle();
$output = $this->context->getOutput();
$user = $this->context->getUser();
global $wgArticle;
$wgArticle = new DeprecatedGlobal( 'wgArticle', $article, '1.18' );
- $this->performAction( $article );
+ $this->performAction( $article, $requestTitle );
} elseif ( is_string( $article ) ) {
$output->redirect( $article );
} else {
* Perform one of the "standard" actions
*
* @param $page Page
+ * @param $requestTitle The original title, before any redirects were applied
*/
- private function performAction( Page $page ) {
+ private function performAction( Page $page, Title $requestTitle ) {
global $wgUseSquid, $wgSquidMaxage;
wfProfileIn( __METHOD__ );
if ( $action instanceof Action ) {
# Let Squid cache things if we can purge them.
if ( $wgUseSquid &&
- in_array( $request->getFullRequestURL(), $title->getSquidURLs() )
+ in_array( $request->getFullRequestURL(), $requestTitle->getSquidURLs() )
) {
$output->setSquidMaxage( $wgSquidMaxage );
}
if ( $wgJobRunRate <= 0 || wfReadOnly() ) {
return;
}
+
if ( $wgJobRunRate < 1 ) {
$max = mt_getrandmax();
if ( mt_rand( 0, $max ) > $max * $wgJobRunRate ) {
- return;
+ return; // the higher $wgJobRunRate, the less likely we return here
}
$n = 1;
} else {
$n = intval( $wgJobRunRate );
}
- while ( $n-- && false != ( $job = Job::pop() ) ) {
- $output = $job->toString() . "\n";
- $t = - microtime( true );
- $success = $job->run();
- $t += microtime( true );
- $t = round( $t * 1000 );
- if ( !$success ) {
- $output .= "Error: " . $job->getLastError() . ", Time: $t ms\n";
- } else {
- $output .= "Success, Time: $t ms\n";
+ $group = JobQueueGroup::singleton();
+ $types = $group->getDefaultQueueTypes();
+ shuffle( $types ); // avoid starvation
+
+ // Scan the queues for a job N times...
+ do {
+ $jobFound = false; // found a job in any queue?
+ // Find a queue with a job on it and run it...
+ foreach ( $types as $i => $type ) {
+ $queue = $group->get( $type );
+ if ( $queue->isEmpty() ) {
+ unset( $types[$i] ); // don't keep checking this queue
+ continue;
+ }
+ $job = $queue->pop();
+ if ( $job ) {
+ $jobFound = true;
+ $output = $job->toString() . "\n";
+ $t = - microtime( true );
+ $success = $job->run();
+ $queue->ack( $job ); // done
+ $t += microtime( true );
+ $t = round( $t * 1000 );
+ if ( !$success ) {
+ $output .= "Error: " . $job->getLastError() . ", Time: $t ms\n";
+ } else {
+ $output .= "Success, Time: $t ms\n";
+ }
+ wfDebugLog( 'jobqueue', $output );
+ break;
+ } else {
+ unset( $types[$i] ); // don't keep checking this queue
+ }
}
- wfDebugLog( 'jobqueue', $output );
- }
+ } while ( --$n && $jobFound );
}
}
}
public function getActionOverrides() {
- return array( 'revert' => 'RevertFileAction' );
+ $overrides = parent::getActionOverrides();
+ $overrides[ 'revert' ] = 'RevertFileAction';
+ return $overrides;
}
/**
}
/**
- * @param bool $text
* @return bool
*/
- public function isRedirect( $text = false ) {
+ public function isRedirect( ) {
$this->loadFile();
if ( $this->mFile->isLocal() ) {
- return parent::isRedirect( $text );
+ return parent::isRedirect();
}
return (bool)$this->mFile->getRedirected();
* @return Array
*/
public function getActionOverrides() {
- return array();
+ $content_handler = $this->getContentHandler();
+ return $content_handler->getActionOverrides();
+ }
+
+ /**
+ * Returns the ContentHandler instance to be used to deal with the content of this WikiPage.
+ *
+ * Shorthand for ContentHandler::getForModelID( $this->getContentModel() );
+ *
+ * @return ContentHandler
+ *
+ * @since 1.21
+ */
+ public function getContentHandler() {
+ return ContentHandler::getForModelID( $this->getContentModel() );
}
/**
* @return array
*/
public static function selectFields() {
- return array(
+ global $wgContentHandlerUseDB;
+
+ $fields = array(
'page_id',
'page_namespace',
'page_title',
'page_latest',
'page_len',
);
+
+ if ( $wgContentHandlerUseDB ) {
+ $fields[] = 'page_content_model';
+ }
+
+ return $fields;
}
/**
}
/**
- * Tests if the article text represents a redirect
+ * Tests if the article content represents a redirect
*
- * @param $text mixed string containing article contents, or boolean
* @return bool
*/
- public function isRedirect( $text = false ) {
- if ( $text === false ) {
- if ( !$this->mDataLoaded ) {
- $this->loadPageData();
- }
+ public function isRedirect( ) {
+ $content = $this->getContent();
+ if ( !$content ) return false;
- return (bool)$this->mIsRedirect;
- } else {
- return Title::newFromRedirect( $text ) !== null;
+ return $content->isRedirect();
+ }
+
+ /**
+ * Returns the page's content model id (see the CONTENT_MODEL_XXX constants).
+ *
+ * Will use the revisions actual content model if the page exists,
+ * and the page's default if the page doesn't exist yet.
+ *
+ * @return String
+ *
+ * @since 1.21
+ */
+ public function getContentModel() {
+ if ( $this->exists() ) {
+ # look at the revision's actual content model
+ $rev = $this->getRevision();
+
+ if ( $rev !== null ) {
+ return $rev->getContentModel();
+ } else {
+ $title = $this->mTitle->getPrefixedDBkey();
+ wfWarn( "Page $title exists but has no (visible) revisions!" );
+ }
}
+
+ # use the default model for this page
+ return $this->mTitle->getContentModel();
}
/**
return null;
}
+ /**
+ * Get the content of the current revision. No side-effects...
+ *
+ * @param $audience Integer: one of:
+ * Revision::FOR_PUBLIC to be displayed to all users
+ * Revision::FOR_THIS_USER to be displayed to $wgUser
+ * Revision::RAW get the text regardless of permissions
+ * @param $user User object to check for, only if FOR_THIS_USER is passed
+ * to the $audience parameter
+ * @return Content|null The content of the current revision
+ *
+ * @since 1.21
+ */
+ public function getContent( $audience = Revision::FOR_PUBLIC, User $user = null ) {
+ $this->loadLastEdit();
+ if ( $this->mLastRevision ) {
+ return $this->mLastRevision->getContent( $audience );
+ }
+ return null;
+ }
+
/**
* Get the text of the current revision. No side-effects...
*
* Revision::RAW get the text regardless of permissions
* @param $user User object to check for, only if FOR_THIS_USER is passed
* to the $audience parameter
- * @return String|bool The text of the current revision. False on failure
+ * @return String|false The text of the current revision
+ * @deprecated as of 1.21, getContent() should be used instead.
*/
- public function getText( $audience = Revision::FOR_PUBLIC, User $user = null ) {
+ public function getText( $audience = Revision::FOR_PUBLIC, User $user = null ) { #@todo: deprecated, replace usage!
+ ContentHandler::deprecated( __METHOD__, '1.21' );
+
$this->loadLastEdit();
if ( $this->mLastRevision ) {
return $this->mLastRevision->getText( $audience, $user );
* Get the text of the current revision. No side-effects...
*
* @return String|bool The text of the current revision. False on failure
+ * @deprecated as of 1.21, getContent() should be used instead.
*/
public function getRawText() {
- $this->loadLastEdit();
- if ( $this->mLastRevision ) {
- return $this->mLastRevision->getRawText();
- }
- return false;
+ ContentHandler::deprecated( __METHOD__, '1.21' );
+
+ return $this->getText( Revision::RAW );
}
/**
return false;
}
- $text = $editInfo ? $editInfo->pst : false;
+ if ( $editInfo ) {
+ $content = $editInfo->pstContent;
+ } else {
+ $content = $this->getContent();
+ }
- if ( $this->isRedirect( $text ) ) {
+ if ( !$content || $content->isRedirect( ) ) {
return false;
}
- switch ( $wgArticleCountMethod ) {
- case 'any':
- return true;
- case 'comma':
- if ( $text === false ) {
- $text = $this->getRawText();
- }
- return strpos( $text, ',' ) !== false;
- case 'link':
+ $hasLinks = null;
+
+ if ( $wgArticleCountMethod === 'link' ) {
+ # nasty special case to avoid re-parsing to detect links
+
if ( $editInfo ) {
// ParserOutput::getLinks() is a 2D array of page links, so
// to be really correct we would need to recurse in the array
// but the main array should only have items in it if there are
// links.
- return (bool)count( $editInfo->output->getLinks() );
+ $hasLinks = (bool)count( $editInfo->output->getLinks() );
} else {
- return (bool)wfGetDB( DB_SLAVE )->selectField( 'pagelinks', 1,
+ $hasLinks = (bool)wfGetDB( DB_SLAVE )->selectField( 'pagelinks', 1,
array( 'pl_from' => $this->getId() ), __METHOD__ );
}
}
+
+ return $content->isCountable( $hasLinks );
}
/**
*/
public function insertRedirect() {
// recurse through to only get the final target
- $retval = Title::newFromRedirectRecurse( $this->getRawText() );
+ $content = $this->getContent();
+ $retval = $content ? $content->getUltimateRedirectTarget() : null;
if ( !$retval ) {
return null;
}
&& $parserOptions->getStubThreshold() == 0
&& $this->mTitle->exists()
&& ( $oldid === null || $oldid === 0 || $oldid === $this->getLatest() )
- && $this->mTitle->isWikitextPage();
+ && $this->getContentHandler()->isParserCacheSupported();
}
/**
* @param $parserOptions ParserOptions to use for the parse operation
* @param $oldid Revision ID to get the text from, passing null or 0 will
* get the current revision (default value)
+ *
* @return ParserOutput or false if the revision was not found
*/
public function getParserOutput( ParserOptions $parserOptions, $oldid = null ) {
}
if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
+ //@todo: move this logic to MessageCache
+
if ( $this->mTitle->exists() ) {
- $text = $this->getRawText();
+ // NOTE: use transclusion text for messages.
+ // This is consistent with MessageCache::getMsgFromNamespace()
+
+ $content = $this->getContent();
+ $text = $content === null ? null : $content->getWikitextForTransclusion();
+
+ if ( $text === null ) $text = false;
} else {
$text = false;
}
* @private
*/
public function updateRevisionOn( $dbw, $revision, $lastRevision = null, $lastRevIsRedirect = null ) {
+ global $wgContentHandlerUseDB;
+
wfProfileIn( __METHOD__ );
- $text = $revision->getText();
- $len = strlen( $text );
- $rt = Title::newFromRedirectRecurse( $text );
+ $content = $revision->getContent();
+ $len = $content->getSize();
+ $rt = $content->getUltimateRedirectTarget();
$conditions = array( 'page_id' => $this->getId() );
}
$now = wfTimestampNow();
+ $row = array( /* SET */
+ 'page_latest' => $revision->getId(),
+ 'page_touched' => $dbw->timestamp( $now ),
+ 'page_is_new' => ( $lastRevision === 0 ) ? 1 : 0,
+ 'page_is_redirect' => $rt !== null ? 1 : 0,
+ 'page_len' => $len,
+ );
+
+ if ( $wgContentHandlerUseDB ) {
+ $row[ 'page_content_model' ] = $revision->getContentModel();
+ }
+
$dbw->update( 'page',
- array( /* SET */
- 'page_latest' => $revision->getId(),
- 'page_touched' => $dbw->timestamp( $now ),
- 'page_is_new' => ( $lastRevision === 0 ) ? 1 : 0,
- 'page_is_redirect' => $rt !== null ? 1 : 0,
- 'page_len' => $len,
- ),
+ $row,
$conditions,
__METHOD__ );
$this->mLatest = $revision->getId();
$this->mIsRedirect = (bool)$rt;
# Update the LinkCache.
- LinkCache::singleton()->addGoodLinkObj( $this->getId(), $this->mTitle, $len, $this->mIsRedirect, $this->mLatest );
+ LinkCache::singleton()->addGoodLinkObj( $this->getId(), $this->mTitle, $len, $this->mIsRedirect,
+ $this->mLatest, $revision->getContentModel() );
}
wfProfileOut( __METHOD__ );
return $ret;
}
+ /**
+ * Get the content that needs to be saved in order to undo all revisions
+ * between $undo and $undoafter. Revisions must belong to the same page,
+ * must exist and must not be deleted
+ * @param $undo Revision
+ * @param $undoafter Revision Must be an earlier revision than $undo
+ * @return mixed string on success, false on failure
+ * @since 1.21
+ * Before we had the Content object, this was done in getUndoText
+ */
+ public function getUndoContent( Revision $undo, Revision $undoafter = null ) {
+ $handler = $undo->getContentHandler();
+ return $handler->getUndoContent( $this->getRevision(), $undo, $undoafter );
+ }
+
/**
* Get the text that needs to be saved in order to undo all revisions
* between $undo and $undoafter. Revisions must belong to the same page,
* @param $undo Revision
* @param $undoafter Revision Must be an earlier revision than $undo
* @return mixed string on success, false on failure
+ * @deprecated since 1.21: use ContentHandler::getUndoContent() instead.
*/
public function getUndoText( Revision $undo, Revision $undoafter = null ) {
- $cur_text = $this->getRawText();
- if ( $cur_text === false ) {
- return false; // no page
- }
- $undo_text = $undo->getText();
- $undoafter_text = $undoafter->getText();
+ ContentHandler::deprecated( __METHOD__, '1.21' );
- if ( $cur_text == $undo_text ) {
- # No use doing a merge if it's just a straight revert.
- return $undoafter_text;
- }
+ $this->loadLastEdit();
- $undone_text = '';
+ if ( $this->mLastRevision ) {
+ if ( is_null( $undoafter ) ) {
+ $undoafter = $undo->getPrevious();
+ }
- if ( !wfMerge( $undo_text, $undoafter_text, $cur_text, $undone_text ) ) {
- return false;
+ $handler = $this->getContentHandler();
+ $undone = $handler->getUndoContent( $this->mLastRevision, $undo, $undoafter );
+
+ if ( !$undone ) {
+ return false;
+ } else {
+ return ContentHandler::getContentText( $undone );
+ }
}
- return $undone_text;
+ return false;
}
/**
* @param $text String: new text of the section
* @param $sectionTitle String: new section's subject, only if $section is 'new'
* @param $edittime String: revision timestamp or null to use the current revision
- * @return string Complete article text, or null if error
+ * @return String new complete article text, or null if error
+ *
+ * @deprecated since 1.21, use replaceSectionContent() instead
*/
public function replaceSection( $section, $text, $sectionTitle = '', $edittime = null ) {
+ ContentHandler::deprecated( __METHOD__, '1.21' );
+
+ if ( strval( $section ) == '' ) { //NOTE: keep condition in sync with condition in replaceSectionContent!
+ // Whole-page edit; let the whole text through
+ return $text;
+ }
+
+ if ( !$this->supportsSections() ) {
+ throw new MWException( "sections not supported for content model " . $this->getContentHandler()->getModelID() );
+ }
+
+ # could even make section title, but that's not required.
+ $sectionContent = ContentHandler::makeContent( $text, $this->getTitle() );
+
+ $newContent = $this->replaceSectionContent( $section, $sectionContent, $sectionTitle, $edittime );
+
+ return ContentHandler::getContentText( $newContent );
+ }
+
+ /**
+ * Returns true iff this page's content model supports sections.
+ *
+ * @return boolean whether sections are supported.
+ *
+ * @todo: the skin should check this and not offer section functionality if sections are not supported.
+ * @todo: the EditPage should check this and not offer section functionality if sections are not supported.
+ */
+ public function supportsSections() {
+ return $this->getContentHandler()->supportsSections();
+ }
+
+ /**
+ * @param $section null|bool|int or a section number (0, 1, 2, T1, T2...)
+ * @param $content Content: new content of the section
+ * @param $sectionTitle String: new section's subject, only if $section is 'new'
+ * @param $edittime String: revision timestamp or null to use the current revision
+ *
+ * @return Content new complete article content, or null if error
+ *
+ * @since 1.21
+ */
+ public function replaceSectionContent( $section, Content $sectionContent, $sectionTitle = '', $edittime = null ) {
wfProfileIn( __METHOD__ );
if ( strval( $section ) == '' ) {
// Whole-page edit; let the whole text through
+ $newContent = $sectionContent;
} else {
+ if ( !$this->supportsSections() ) {
+ throw new MWException( "sections not supported for content model " . $this->getContentHandler()->getModelID() );
+ }
+
// Bug 30711: always use current version when adding a new section
if ( is_null( $edittime ) || $section == 'new' ) {
- $oldtext = $this->getRawText();
- if ( $oldtext === false ) {
+ $oldContent = $this->getContent();
+ if ( ! $oldContent ) {
wfDebug( __METHOD__ . ": no page text\n" );
wfProfileOut( __METHOD__ );
return null;
return null;
}
- $oldtext = $rev->getText();
+ $oldContent = $rev->getContent();
}
- if ( $section == 'new' ) {
- # Inserting a new section
- $subject = $sectionTitle ? wfMessage( 'newsectionheaderdefaultlevel' )
- ->rawParams( $sectionTitle )->inContentLanguage()->text() . "\n\n" : '';
- if ( wfRunHooks( 'PlaceNewSection', array( $this, $oldtext, $subject, &$text ) ) ) {
- $text = strlen( trim( $oldtext ) ) > 0
- ? "{$oldtext}\n\n{$subject}{$text}"
- : "{$subject}{$text}";
- }
- } else {
- # Replacing an existing section; roll out the big guns
- global $wgParser;
-
- $text = $wgParser->replaceSection( $oldtext, $section, $text );
- }
+ $newContent = $oldContent->replaceSection( $section, $sectionContent, $sectionTitle );
}
wfProfileOut( __METHOD__ );
- return $text;
+ return $newContent;
}
/**
* revision: The revision object for the inserted revision, or null
*
* Compatibility note: this function previously returned a boolean value indicating success/failure
+ *
+ * @deprecated since 1.21: use doEditContent() instead.
*/
public function doEdit( $text, $summary, $flags = 0, $baseRevId = false, $user = null ) {
+ ContentHandler::deprecated( __METHOD__, '1.21' );
+
+ $content = ContentHandler::makeContent( $text, $this->getTitle() );
+
+ return $this->doEditContent( $content, $summary, $flags, $baseRevId, $user );
+ }
+
+ /**
+ * Change an existing article or create a new article. Updates RC and all necessary caches,
+ * optionally via the deferred update array.
+ *
+ * @param $content Content: new content
+ * @param $summary String: edit summary
+ * @param $flags Integer bitfield:
+ * EDIT_NEW
+ * Article is known or assumed to be non-existent, create a new one
+ * EDIT_UPDATE
+ * Article is known or assumed to be pre-existing, update it
+ * EDIT_MINOR
+ * Mark this edit minor, if the user is allowed to do so
+ * EDIT_SUPPRESS_RC
+ * Do not log the change in recentchanges
+ * EDIT_FORCE_BOT
+ * Mark the edit a "bot" edit regardless of user rights
+ * EDIT_DEFER_UPDATES
+ * Defer some of the updates until the end of index.php
+ * EDIT_AUTOSUMMARY
+ * Fill in blank summaries with generated text where possible
+ *
+ * If neither EDIT_NEW nor EDIT_UPDATE is specified, the status of the article will be detected.
+ * If EDIT_UPDATE is specified and the article doesn't exist, the function will return an
+ * edit-gone-missing error. If EDIT_NEW is specified and the article does exist, an
+ * edit-already-exists error will be returned. These two conditions are also possible with
+ * auto-detection due to MediaWiki's performance-optimised locking strategy.
+ *
+ * @param $baseRevId the revision ID this edit was based off, if any
+ * @param $user User the user doing the edit
+ * @param $serialisation_format String: format for storing the content in the database
+ *
+ * @return Status object. Possible errors:
+ * edit-hook-aborted: The ArticleSave hook aborted the edit but didn't set the fatal flag of $status
+ * edit-gone-missing: In update mode, but the article didn't exist
+ * edit-conflict: In update mode, the article changed unexpectedly
+ * edit-no-change: Warning that the text was the same as before
+ * edit-already-exists: In creation mode, but the article already exists
+ *
+ * Extensions may define additional errors.
+ *
+ * $return->value will contain an associative array with members as follows:
+ * new: Boolean indicating if the function attempted to create a new article
+ * revision: The revision object for the inserted revision, or null
+ *
+ * @since 1.21
+ */
+ public function doEditContent( Content $content, $summary, $flags = 0, $baseRevId = false,
+ User $user = null, $serialisation_format = null ) {
global $wgUser, $wgUseAutomaticEditSummaries, $wgUseRCPatrol, $wgUseNPPatrol;
# Low-level sanity check
wfProfileIn( __METHOD__ );
+ if ( !$content->getContentHandler()->canBeUsedOn( $this->getTitle() ) ) {
+ wfProfileOut( __METHOD__ );
+ return Status::newFatal( 'content-not-allowed-here',
+ ContentHandler::getLocalizedName( $content->getModel() ),
+ $this->getTitle()->getPrefixedText() );
+ }
+
$user = is_null( $user ) ? $wgUser : $user;
$status = Status::newGood( array() );
$flags = $this->checkFlags( $flags );
- if ( !wfRunHooks( 'ArticleSave', array( &$this, &$user, &$text, &$summary,
- $flags & EDIT_MINOR, null, null, &$flags, &$status ) ) )
- {
- wfDebug( __METHOD__ . ": ArticleSave hook aborted save!\n" );
+ # handle hook
+ $hook_args = array( &$this, &$user, &$content, &$summary,
+ $flags & EDIT_MINOR, null, null, &$flags, &$status );
+
+ if ( !wfRunHooks( 'PageContentSave', $hook_args )
+ || !ContentHandler::runLegacyHooks( 'ArticleSave', $hook_args ) ) {
+
+ wfDebug( __METHOD__ . ": ArticleSave or ArticleSaveContent hook aborted save!\n" );
if ( $status->isOK() ) {
$status->fatal( 'edit-hook-aborted' );
$isminor = ( $flags & EDIT_MINOR ) && $user->isAllowed( 'minoredit' );
$bot = $flags & EDIT_FORCE_BOT;
- $oldtext = $this->getRawText(); // current revision
- $oldsize = strlen( $oldtext );
+ $old_content = $this->getContent( Revision::RAW ); // current revision's content
+
+ $oldsize = $old_content ? $old_content->getSize() : 0;
$oldid = $this->getLatest();
$oldIsRedirect = $this->isRedirect();
$oldcountable = $this->isCountable();
+ $handler = $content->getContentHandler();
+
# Provide autosummaries if one is not provided and autosummaries are enabled.
if ( $wgUseAutomaticEditSummaries && $flags & EDIT_AUTOSUMMARY && $summary == '' ) {
- $summary = self::getAutosummary( $oldtext, $text, $flags );
+ if ( !$old_content ) $old_content = null;
+ $summary = $handler->getAutosummary( $old_content, $content, $flags );
}
- $editInfo = $this->prepareTextForEdit( $text, null, $user );
- $text = $editInfo->pst;
- $newsize = strlen( $text );
+ $editInfo = $this->prepareContentForEdit( $content, null, $user, $serialisation_format );
+ $serialized = $editInfo->pst;
+ $content = $editInfo->pstContent;
+ $newsize = $content->getSize();
$dbw = wfGetDB( DB_MASTER );
$now = wfTimestampNow();
wfProfileOut( __METHOD__ );
return $status;
- } elseif ( $oldtext === false ) {
+ } elseif ( !$old_content ) {
# Sanity check for bug 37225
wfProfileOut( __METHOD__ );
throw new MWException( "Could not find text for current revision {$oldid}." );
'page' => $this->getId(),
'comment' => $summary,
'minor_edit' => $isminor,
- 'text' => $text,
+ 'text' => $serialized,
+ 'len' => $newsize,
'parent_id' => $oldid,
'user' => $user->getId(),
'user_text' => $user->getName(),
- 'timestamp' => $now
- ) );
- # Bug 37225: use accessor to get the text as Revision may trim it.
- # After trimming, the text may be a duplicate of the current text.
- $text = $revision->getText(); // sanity; EditPage should trim already
+ 'timestamp' => $now,
+ 'content_model' => $content->getModel(),
+ 'content_format' => $serialisation_format,
+ ) ); #XXX: pass content object?!
- $changed = ( strcmp( $text, $oldtext ) != 0 );
+ $changed = !$content->equals( $old_content );
if ( $changed ) {
+ if ( !$content->isValid() ) {
+ throw new MWException( "New content failed validity check!" );
+ }
+
$dbw->begin( __METHOD__ );
+
+ $prepStatus = $content->prepareSave( $this, $flags, $baseRevId, $user );
+ $status->merge( $prepStatus );
+
+ if ( !$status->isOK() ) {
+ $dbw->rollback();
+
+ wfProfileOut( __METHOD__ );
+ return $status;
+ }
+
$revisionId = $revision->insertOn( $dbw );
# Update page
}
# Update links tables, site stats, etc.
- $this->doEditUpdates( $revision, $user, array( 'changed' => $changed,
- 'oldcountable' => $oldcountable ) );
+ $this->doEditUpdates(
+ $revision,
+ $user,
+ array(
+ 'changed' => $changed,
+ 'oldcountable' => $oldcountable
+ )
+ );
if ( !$changed ) {
$status->warning( 'edit-no-change' );
$dbw->begin( __METHOD__ );
+ $prepStatus = $content->prepareSave( $this, $flags, $baseRevId, $user );
+ $status->merge( $prepStatus );
+
+ if ( !$status->isOK() ) {
+ $dbw->rollback();
+
+ wfProfileOut( __METHOD__ );
+ return $status;
+ }
+
+ $status->merge( $prepStatus );
+
# Add the page record; stake our claim on this title!
# This will return false if the article already exists
$newid = $this->insertOn( $dbw );
'page' => $newid,
'comment' => $summary,
'minor_edit' => $isminor,
- 'text' => $text,
+ 'text' => $serialized,
+ 'len' => $newsize,
'user' => $user->getId(),
'user_text' => $user->getName(),
- 'timestamp' => $now
+ 'timestamp' => $now,
+ 'content_model' => $content->getModel(),
+ 'content_format' => $serialisation_format,
) );
$revisionId = $revision->insertOn( $dbw );
# Bug 37225: use accessor to get the text as Revision may trim it
- $text = $revision->getText(); // sanity; EditPage should trim already
+ $content = $revision->getContent(); // sanity; get normalized version
# Update the page record with revision data
$this->updateRevisionOn( $dbw, $revision, 0 );
$this->mTitle->getUserPermissionsErrors( 'autopatrol', $user ) );
# Add RC row to the DB
$rc = RecentChange::notifyNew( $now, $this->mTitle, $isminor, $user, $summary, $bot,
- '', strlen( $text ), $revisionId, $patrolled );
+ '', $content->getSize(), $revisionId, $patrolled );
# Log auto-patrolled edits
if ( $patrolled ) {
# Update links, etc.
$this->doEditUpdates( $revision, $user, array( 'created' => true ) );
- wfRunHooks( 'ArticleInsertComplete', array( &$this, &$user, $text, $summary,
- $flags & EDIT_MINOR, null, null, &$flags, $revision ) );
+ $hook_args = array( &$this, &$user, $content, $summary,
+ $flags & EDIT_MINOR, null, null, &$flags, $revision );
+
+ ContentHandler::runLegacyHooks( 'ArticleInsertComplete', $hook_args );
+ wfRunHooks( 'PageContentInsertComplete', $hook_args );
}
# Do updates right now unless deferral was requested
// Return the new revision (or null) to the caller
$status->value['revision'] = $revision;
- wfRunHooks( 'ArticleSaveComplete', array( &$this, &$user, $text, $summary,
- $flags & EDIT_MINOR, null, null, &$flags, $revision, &$status, $baseRevId ) );
+ $hook_args = array( &$this, &$user, $content, $summary,
+ $flags & EDIT_MINOR, null, null, &$flags, $revision, &$status, $baseRevId );
+
+ ContentHandler::runLegacyHooks( 'ArticleSaveComplete', $hook_args );
+ wfRunHooks( 'PageContentSaveComplete', $hook_args );
# Promote user to any groups they meet the criteria for
$user->addAutopromoteOnceGroups( 'onEdit' );
/**
* Get parser options suitable for rendering the primary article wikitext
*
+ * @see ContentHandler::makeParserOptions
+ *
* @param IContextSource|User|string $context One of the following:
* - IContextSource: Use the User and the Language of the provided
* context
* @return ParserOptions
*/
public function makeParserOptions( $context ) {
- global $wgContLang;
-
- if ( $context instanceof IContextSource ) {
- $options = ParserOptions::newFromContext( $context );
- } elseif ( $context instanceof User ) { // settings per user (even anons)
- $options = ParserOptions::newFromUser( $context );
- } else { // canonical settings
- $options = ParserOptions::newFromUserAndLang( new User, $wgContLang );
- }
+ $options = $this->getContentHandler()->makeParserOptions( $context );
if ( $this->getTitle()->isConversionTable() ) {
+ //@todo: ConversionTable should become a separate content model, so we don't need special cases like this one.
$options->disableContentConversion();
}
- $options->enableLimitReport(); // show inclusion/loop reports
- $options->setTidy( true ); // fix bad HTML
-
return $options;
}
/**
* Prepare text which is about to be saved.
* Returns a stdclass with source, pst and output members
- * @return bool|object
+ *
+ * @deprecated in 1.21: use prepareContentForEdit instead.
*/
public function prepareTextForEdit( $text, $revid = null, User $user = null ) {
+ ContentHandler::deprecated( __METHOD__, '1.21' );
+ $content = ContentHandler::makeContent( $text, $this->getTitle() );
+ return $this->prepareContentForEdit( $content, $revid , $user );
+ }
+
+ /**
+ * Prepare content which is about to be saved.
+ * Returns a stdclass with source, pst and output members
+ *
+ * @param \Content $content
+ * @param null $revid
+ * @param null|\User $user
+ * @param null $serialization_format
+ *
+ * @return bool|object
+ *
+ * @since 1.21
+ */
+ public function prepareContentForEdit( Content $content, $revid = null, User $user = null, $serialization_format = null ) {
global $wgParser, $wgContLang, $wgUser;
$user = is_null( $user ) ? $wgUser : $user;
- // @TODO fixme: check $user->getId() here???
+ //XXX: check $user->getId() here???
+
if ( $this->mPreparedEdit
- && $this->mPreparedEdit->newText == $text
+ && $this->mPreparedEdit->newContent
+ && $this->mPreparedEdit->newContent->equals( $content )
&& $this->mPreparedEdit->revid == $revid
+ && $this->mPreparedEdit->format == $serialization_format
+ #XXX: also check $user here?
) {
// Already prepared
return $this->mPreparedEdit;
$edit = (object)array();
$edit->revid = $revid;
- $edit->newText = $text;
- $edit->pst = $wgParser->preSaveTransform( $text, $this->mTitle, $user, $popts );
+
+ $edit->pstContent = $content->preSaveTransform( $this->mTitle, $user, $popts );
+ $edit->pst = $edit->pstContent->serialize( $serialization_format ); #XXX: do we need this??
+ $edit->format = $serialization_format;
+
$edit->popts = $this->makeParserOptions( 'canonical' );
- $edit->output = $wgParser->parse( $edit->pst, $this->mTitle, $edit->popts, true, true, $revid );
- $edit->oldText = $this->getRawText();
+
+ $edit->output = $edit->pstContent->getParserOutput( $this->mTitle, $revid, $edit->popts );
+
+ $edit->newContent = $content;
+ $edit->oldContent = $this->getContent( Revision::RAW );
+
+ #NOTE: B/C for hooks! don't use these fields!
+ $edit->newText = ContentHandler::getContentText( $edit->newContent );
+ $edit->oldText = $edit->oldContent ? ContentHandler::getContentText( $edit->oldContent ) : '';
$this->mPreparedEdit = $edit;
* Purges pages that include this page if the text was changed here.
* Every 100th edit, prune the recent changes table.
*
- * @private
* @param $revision Revision object
* @param $user User object that did the revision
* @param $options Array of options, following indexes are used:
wfProfileIn( __METHOD__ );
$options += array( 'changed' => true, 'created' => false, 'oldcountable' => null );
- $text = $revision->getText();
+ $content = $revision->getContent();
# Parse the text
# Be careful not to double-PST: $text is usually already PST-ed once
if ( !$this->mPreparedEdit || $this->mPreparedEdit->output->getFlag( 'vary-revision' ) ) {
wfDebug( __METHOD__ . ": No prepared edit or vary-revision is set...\n" );
- $editInfo = $this->prepareTextForEdit( $text, $revision->getId(), $user );
+ $editInfo = $this->prepareContentForEdit( $content, $revision->getId(), $user );
} else {
wfDebug( __METHOD__ . ": No vary-revision, using prepared edit...\n" );
$editInfo = $this->mPreparedEdit;
}
# Update the links tables and other secondary data
- $updates = $editInfo->output->getSecondaryDataUpdates( $this->mTitle );
+ $updates = $content->getSecondaryDataUpdates( $this->getTitle(), null, true, $editInfo->output );
DataUpdate::runUpdates( $updates );
wfRunHooks( 'ArticleEditUpdates', array( &$this, &$editInfo, $options['changed'] ) );
}
DeferredUpdates::addUpdate( new SiteStatsUpdate( 0, 1, $good, $total ) );
- DeferredUpdates::addUpdate( new SearchUpdate( $id, $title, $text ) );
+ DeferredUpdates::addUpdate( new SearchUpdate( $id, $title, $content->getTextForSearchIndex() ) );
+ #@TODO: let the search engine decide what to do with the content object
# If this is another user's talk page, update newtalk.
# Don't do this if $options['changed'] = false (null-edits) nor if
}
if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
- MessageCache::singleton()->replace( $shortTitle, $text );
+ #XXX: could skip pseudo-messages like js/css here, based on content model.
+ $msgtext = $content->getWikitextForTransclusion();
+ if ( $msgtext === false || $msgtext === null ) $msgtext = '';
+
+ MessageCache::singleton()->replace( $shortTitle, $msgtext );
}
if( $options['created'] ) {
* @param $user User The relevant user
* @param $comment String: comment submitted
* @param $minor Boolean: whereas it's a minor modification
+ *
+ * @deprecated since 1.21, use doEditContent() instead.
*/
public function doQuickEdit( $text, User $user, $comment = '', $minor = 0 ) {
+ ContentHandler::deprecated( __METHOD__, "1.21" );
+
+ $content = ContentHandler::makeContent( $text, $this->getTitle() );
+ return $this->doQuickEditContent( $content, $user, $comment , $minor );
+ }
+
+ /**
+ * Edit an article without doing all that other stuff
+ * The article must already exist; link tables etc
+ * are not updated, caches are not flushed.
+ *
+ * @param $content Content: content submitted
+ * @param $user User The relevant user
+ * @param $comment String: comment submitted
+ * @param $serialisation_format String: format for storing the content in the database
+ * @param $minor Boolean: whereas it's a minor modification
+ */
+ public function doQuickEditContent( Content $content, User $user, $comment = '', $minor = 0, $serialisation_format = null ) {
wfProfileIn( __METHOD__ );
+ $serialized = $content->serialize( $serialisation_format );
+
$dbw = wfGetDB( DB_MASTER );
$revision = new Revision( array(
'page' => $this->getId(),
- 'text' => $text,
+ 'text' => $serialized,
+ 'length' => $content->getSize(),
'comment' => $comment,
'minor_edit' => $minor ? 1 : 0,
- ) );
+ ) ); #XXX: set the content object?
$revision->insertOn( $dbw );
$this->updateRevisionOn( $dbw, $revision );
public function doDeleteArticleReal(
$reason, $suppress = false, $id = 0, $commit = true, &$error = '', User $user = null
) {
- global $wgUser;
+ global $wgUser, $wgContentHandlerUseDB;
wfDebug( __METHOD__ . "\n" );
$bitfield = 'rev_deleted';
}
+ // we need to remember the old content so we can use it to generate all deletion updates.
+ $content = $this->getContent( Revision::RAW );
+
$dbw = wfGetDB( DB_MASTER );
$dbw->begin( __METHOD__ );
// For now, shunt the revision data into the archive table.
//
// In the future, we may keep revisions and mark them with
// the rev_deleted field, which is reserved for this purpose.
+
+ $row = array(
+ 'ar_namespace' => 'page_namespace',
+ 'ar_title' => 'page_title',
+ 'ar_comment' => 'rev_comment',
+ 'ar_user' => 'rev_user',
+ 'ar_user_text' => 'rev_user_text',
+ 'ar_timestamp' => 'rev_timestamp',
+ 'ar_minor_edit' => 'rev_minor_edit',
+ 'ar_rev_id' => 'rev_id',
+ 'ar_parent_id' => 'rev_parent_id',
+ 'ar_text_id' => 'rev_text_id',
+ 'ar_text' => '\'\'', // Be explicit to appease
+ 'ar_flags' => '\'\'', // MySQL's "strict mode"...
+ 'ar_len' => 'rev_len',
+ 'ar_page_id' => 'page_id',
+ 'ar_deleted' => $bitfield,
+ 'ar_sha1' => 'rev_sha1',
+ );
+
+ if ( $wgContentHandlerUseDB ) {
+ $row[ 'ar_content_model' ] = 'rev_content_model';
+ $row[ 'ar_content_format' ] = 'rev_content_format';
+ }
+
$dbw->insertSelect( 'archive', array( 'page', 'revision' ),
+ $row,
array(
- 'ar_namespace' => 'page_namespace',
- 'ar_title' => 'page_title',
- 'ar_comment' => 'rev_comment',
- 'ar_user' => 'rev_user',
- 'ar_user_text' => 'rev_user_text',
- 'ar_timestamp' => 'rev_timestamp',
- 'ar_minor_edit' => 'rev_minor_edit',
- 'ar_rev_id' => 'rev_id',
- 'ar_parent_id' => 'rev_parent_id',
- 'ar_text_id' => 'rev_text_id',
- 'ar_text' => '\'\'', // Be explicit to appease
- 'ar_flags' => '\'\'', // MySQL's "strict mode"...
- 'ar_len' => 'rev_len',
- 'ar_page_id' => 'page_id',
- 'ar_deleted' => $bitfield,
- 'ar_sha1' => 'rev_sha1'
- ), array(
'page_id' => $id,
'page_id = rev_page'
), __METHOD__
return $status;
}
- $this->doDeleteUpdates( $id );
+ $this->doDeleteUpdates( $id, $content );
# Log the deletion, if the page was suppressed, log it at Oversight instead
$logtype = $suppress ? 'suppress' : 'delete';
$dbw->commit( __METHOD__ );
}
- wfRunHooks( 'ArticleDeleteComplete', array( &$this, &$user, $reason, $id ) );
+ wfRunHooks( 'ArticleDeleteComplete', array( &$this, &$user, $reason, $id, $content, $logEntry ) );
$status->value = $logid;
return $status;
}
* Do some database updates after deletion
*
* @param $id Int: page_id value of the page being deleted (B/C, currently unused)
+ * @param $content Content: optional page content to be used when determining the required updates.
+ * This may be needed because $this->getContent() may already return null when the page proper was deleted.
*/
- public function doDeleteUpdates( $id ) {
+ public function doDeleteUpdates( $id, Content $content = null ) {
# update site status
DeferredUpdates::addUpdate( new SiteStatsUpdate( 0, 1, - (int)$this->isCountable(), -1 ) );
# remove secondary indexes, etc
- $updates = $this->getDeletionUpdates( );
+ $updates = $this->getDeletionUpdates( $content );
DataUpdate::runUpdates( $updates );
# Clear caches
$this->mTitle->resetArticleID( 0 );
}
- public function getDeletionUpdates() {
- $updates = array(
- new LinksDeletionUpdate( $this ),
- );
-
- //@todo: make a hook to add update objects
- //NOTE: deletion updates will be determined by the ContentHandler in the future
- return $updates;
- }
-
/**
* Roll back the most recent consecutive set of edits to a page
* from the same user; fails if there are no eligible edits to
}
# Actually store the edit
- $status = $this->doEdit( $target->getText(), $summary, $flags, $target->getId(), $guser );
+ $status = $this->doEditContent( $target->getContent(), $summary, $flags, $target->getId(), $guser );
+
+ if ( !$status->isOK() ) {
+ return $status->getErrorsArray();
+ }
+
if ( !empty( $status->value['revision'] ) ) {
$revId = $status->value['revision']->getId();
} else {
/**
* Return an applicable autosummary if one exists for the given edit.
- * @param $oldtext String: the previous text of the page.
- * @param $newtext String: The submitted text of the page.
+ * @param $oldtext String|null: the previous text of the page.
+ * @param $newtext String|null: The submitted text of the page.
* @param $flags Int bitmask: a bitmask of flags submitted for the edit.
* @return string An appropriate autosummary, or an empty string.
+ *
+ * @deprecated since 1.21, use ContentHandler::getAutosummary() instead
*/
public static function getAutosummary( $oldtext, $newtext, $flags ) {
- global $wgContLang;
-
- # Decide what kind of autosummary is needed.
-
- # Redirect autosummaries
- $ot = Title::newFromRedirect( $oldtext );
- $rt = Title::newFromRedirect( $newtext );
+ # NOTE: stub for backwards-compatibility. assumes the given text is wikitext. will break horribly if it isn't.
- if ( is_object( $rt ) && ( !is_object( $ot ) || !$rt->equals( $ot ) || $ot->getFragment() != $rt->getFragment() ) ) {
- $truncatedtext = $wgContLang->truncate(
- str_replace( "\n", ' ', $newtext ),
- max( 0, 255
- - strlen( wfMessage( 'autoredircomment' )->inContentLanguage()->text() )
- - strlen( $rt->getFullText() )
- ) );
- return wfMessage( 'autoredircomment', $rt->getFullText() )
- ->rawParams( $truncatedtext )->inContentLanguage()->text();
- }
-
- # New page autosummaries
- if ( $flags & EDIT_NEW && strlen( $newtext ) ) {
- # If they're making a new article, give its text, truncated, in the summary.
-
- $truncatedtext = $wgContLang->truncate(
- str_replace( "\n", ' ', $newtext ),
- max( 0, 200 - strlen( wfMessage( 'autosumm-new' )->inContentLanguage()->text() ) ) );
-
- return wfMessage( 'autosumm-new' )->rawParams( $truncatedtext )
- ->inContentLanguage()->text();
- }
+ ContentHandler::deprecated( __METHOD__, '1.21' );
- # Blanking autosummaries
- if ( $oldtext != '' && $newtext == '' ) {
- return wfMessage( 'autosumm-blank' )->inContentLanguage()->text();
- } elseif ( strlen( $oldtext ) > 10 * strlen( $newtext ) && strlen( $newtext ) < 500 ) {
- # Removing more than 90% of the article
+ $handler = ContentHandler::getForModelID( CONTENT_MODEL_WIKITEXT );
+ $oldContent = is_null( $oldtext ) ? null : $handler->unserializeContent( $oldtext );
+ $newContent = is_null( $newtext ) ? null : $handler->unserializeContent( $newtext );
- $truncatedtext = $wgContLang->truncate(
- $newtext,
- max( 0, 200 - strlen( wfMessage( 'autosumm-replace' )->inContentLanguage()->text() ) ) );
-
- return wfMessage( 'autosumm-replace' )->rawParams( $truncatedtext )
- ->inContentLanguage()->text();
- }
-
- # If we reach this point, there's no applicable autosummary for our case, so our
- # autosummary is empty.
- return '';
+ return $handler->getAutosummary( $oldContent, $newContent, $flags );
}
/**
* if no revision occurred
*/
public function getAutoDeleteReason( &$hasHistory ) {
- global $wgContLang;
-
- // Get the last revision
- $rev = $this->getRevision();
-
- if ( is_null( $rev ) ) {
- return false;
- }
-
- // Get the article's contents
- $contents = $rev->getText();
- $blank = false;
-
- // If the page is blank, use the text from the previous revision,
- // which can only be blank if there's a move/import/protect dummy revision involved
- if ( $contents == '' ) {
- $prev = $rev->getPrevious();
-
- if ( $prev ) {
- $contents = $prev->getText();
- $blank = true;
- }
- }
-
- $dbw = wfGetDB( DB_MASTER );
-
- // Find out if there was only one contributor
- // Only scan the last 20 revisions
- $res = $dbw->select( 'revision', 'rev_user_text',
- array( 'rev_page' => $this->getID(), $dbw->bitAnd( 'rev_deleted', Revision::DELETED_USER ) . ' = 0' ),
- __METHOD__,
- array( 'LIMIT' => 20 )
- );
-
- if ( $res === false ) {
- // This page has no revisions, which is very weird
- return false;
- }
-
- $hasHistory = ( $res->numRows() > 1 );
- $row = $dbw->fetchObject( $res );
-
- if ( $row ) { // $row is false if the only contributor is hidden
- $onlyAuthor = $row->rev_user_text;
- // Try to find a second contributor
- foreach ( $res as $row ) {
- if ( $row->rev_user_text != $onlyAuthor ) { // Bug 22999
- $onlyAuthor = false;
- break;
- }
- }
- } else {
- $onlyAuthor = false;
- }
-
- // Generate the summary with a '$1' placeholder
- if ( $blank ) {
- // The current revision is blank and the one before is also
- // blank. It's just not our lucky day
- $reason = wfMessage( 'exbeforeblank', '$1' )->inContentLanguage()->text();
- } else {
- if ( $onlyAuthor ) {
- $reason = wfMessage(
- 'excontentauthor',
- '$1',
- $onlyAuthor
- )->inContentLanguage()->text();
- } else {
- $reason = wfMessage( 'excontent', '$1' )->inContentLanguage()->text();
- }
- }
-
- if ( $reason == '-' ) {
- // Allow these UI messages to be blanked out cleanly
- return '';
- }
-
- // Replace newlines with spaces to prevent uglyness
- $contents = preg_replace( "/[\n\r]/", ' ', $contents );
- // Calculate the maximum amount of chars to get
- // Max content length = max comment length - length of the comment (excl. $1)
- $maxLength = 255 - ( strlen( $reason ) - 2 );
- $contents = $wgContLang->truncate( $contents, $maxLength );
- // Remove possible unfinished links
- $contents = preg_replace( '/\[\[([^\]]*)\]?$/', '$1', $contents );
- // Now replace the '$1' placeholder
- $reason = str_replace( '$1', $contents, $reason );
-
- return $reason;
+ return $this->getContentHandler()->getAutoDeleteReason( $this->getTitle(), $hasHistory );
}
/**
global $wgUser;
return $this->isParserCacheUsed( ParserOptions::newFromUser( $wgUser ), $oldid );
}
+
+ /**
+ * Returns a list of updates to be performed when this page is deleted. The updates should remove any information
+ * about this page from secondary data stores such as links tables.
+ *
+ * @param Content|null $content optional Content object for determining the necessary updates
+ * @return Array an array of DataUpdates objects
+ */
+ public function getDeletionUpdates( Content $content = null ) {
+ if ( !$content ) {
+ // load content object, which may be used to determine the necessary updates
+ // XXX: the content may not be needed to determine the updates, then this would be overhead.
+ $content = $this->getContent( Revision::RAW );
+ }
+
+ if ( !$content ) {
+ $updates = array();
+ } else {
+ $updates = $content->getDeletionUpdates( $this );
+ }
+
+ wfRunHooks( 'WikiPageDeletionUpdates', array( $this, $content, &$updates ) );
+ return $updates;
+ }
+
}
class PoolWorkArticleView extends PoolCounterWork {
private $parserOptions;
/**
- * @var string|null
+ * @var Content|null
*/
- private $text;
+ private $content = null;
/**
* @var ParserOutput|bool
* @param $revid Integer: ID of the revision being parsed
* @param $useParserCache Boolean: whether to use the parser cache
* @param $parserOptions parserOptions to use for the parse operation
- * @param $text String: text to parse or null to load it
+ * @param $content Content|String: content to parse or null to load it; may also be given as a wikitext string, for BC
*/
- function __construct( Page $page, ParserOptions $parserOptions, $revid, $useParserCache, $text = null ) {
+ function __construct( Page $page, ParserOptions $parserOptions, $revid, $useParserCache, $content = null ) {
+ if ( is_string($content) ) { #BC: old style call
+ $modelId = $page->getRevision()->getContentModel();
+ $format = $page->getRevision()->getContentFormat();
+ $content = ContentHandler::makeContent( $content, $page->getTitle(), $modelId, $format );
+ }
+
$this->page = $page;
$this->revid = $revid;
$this->cacheable = $useParserCache;
$this->parserOptions = $parserOptions;
- $this->text = $text;
+ $this->content = $content;
$this->cacheKey = ParserCache::singleton()->getKey( $page, $parserOptions );
parent::__construct( 'ArticleView', $this->cacheKey . ':revid:' . $revid );
}
* @return bool
*/
function doWork() {
- global $wgParser, $wgUseFileCache;
+ global $wgUseFileCache;
+
+ // @todo: several of the methods called on $this->page are not declared in Page, but present
+ // in WikiPage and delegated by Article.
$isCurrent = $this->revid === $this->page->getLatest();
- if ( $this->text !== null ) {
- $text = $this->text;
+ if ( $this->content !== null ) {
+ $content = $this->content;
} elseif ( $isCurrent ) {
- $text = $this->page->getRawText();
+ #XXX: why use RAW audience here, and PUBLIC (default) below?
+ $content = $this->page->getContent( Revision::RAW );
+ if ( $content === null ) {
+ return false;
+ }
+
} else {
$rev = Revision::newFromTitle( $this->page->getTitle(), $this->revid );
if ( $rev === null ) {
return false;
}
- $text = $rev->getText();
+
+ #XXX: why use PUBLIC audience here (default), and RAW above?
+ $content = $rev->getContent();
}
$time = - microtime( true );
- $this->parserOutput = $wgParser->parse( $text, $this->page->getTitle(),
- $this->parserOptions, true, true, $this->revid );
+ $this->parserOutput = $content->getParserOutput( $this->page->getTitle(), $this->revid, $this->parserOptions );
$time += microtime( true );
# Timing hack
return false;
}
}
+
* The values are passed to Sanitizer::encodeAttribute.
* Return null if no attributes given.
* @param $attribs Array of attributes for an XML element
+ * @throws MWException
* @return null|string
*/
public static function expandAttributes( $attribs ) {
/**
* Construct a language selector appropriate for use in a form or preferences
- *
+ *
* @param string $selected The language code of the selected language
* @param boolean $customisedOnly If true only languages which have some content are listed
* @param string $inLanguage The ISO code of the language to display the select list in (optional)
*
* @param $offset int The offset into the string at which to start unpacking.
*
+ * @throws MWException
* @return array Unpacked associative array. Note that large integers in the input
* may be represented as floating point numbers in the return value, so
* the use of weak comparison is advised.
} else {
// Unsigned little-endian integer
$length = intval( $type );
- $bytes = substr( $string, $pos, $length );
// Calculate the value. Use an algorithm which automatically
// upgrades the value to floating point if necessary.
# Sift for real versus user names
foreach ( $contributors as $user ) {
- $cnt--;
+ $cnt--;
if ( $user->isLoggedIn() ) {
$link = $this->link( $user );
if ( !in_array( 'realname', $wgHiddenPrefs ) && $user->getRealName() ) {
} else {
$conds = array();
}
- $checkDeleted = Xml::checkLabel( $this->msg( 'history-show-deleted' )->text(),
+ if ( $this->getUser()->isAllowed( 'deletedhistory' ) ) {
+ $checkDeleted = Xml::checkLabel( $this->msg( 'history-show-deleted' )->text(),
'deleted', 'mw-show-deleted-only', $request->getBool( 'deleted' ) ) . "\n";
+ } else {
+ $checkDeleted = '';
+ }
// Add the general form
$action = htmlspecialchars( $wgScript );
} elseif ( $rev->getVisibility() && $user->isAllowed( 'deletedhistory' ) ) {
// If revision was hidden from sysops, disable the link
if ( !$rev->userCan( Revision::DELETED_RESTRICTED, $user ) ) {
- $cdel = Linker::revDeleteLinkDisabled( false );
+ $del = Linker::revDeleteLinkDisabled( false );
// Otherwise, show the link...
} else {
$query = array( 'type' => 'revision',
public function onView() {
$content = '';
+ // Validate revision
+ $oldid = $this->page->getOldID();
+ if ( $oldid ) {
+ $revision = $this->page->getRevisionFetched();
+
+ // Revision is missing
+ if ( $revision === null ) {
+ return $this->msg( 'missing-revision', $oldid )->parse();
+ }
+
+ // Revision is not current
+ if ( !$revision->isCurrent() ) {
+ return $this->msg( 'pageinfo-not-current' )->plain();
+ }
+ }
+
// Page header
if ( !$this->msg( 'pageinfo-header' )->isDisabled() ) {
$content .= $this->msg( 'pageinfo-header' )->parse();
'.mw-templatesUsedExplanation { display: none; }' );
// Get page information
- $title = $this->getTitle();
- $pageInfo = $this->pageInfo( $title );
+ $pageInfo = $this->pageInfo();
// Allow extensions to add additional information
- wfRunHooks( 'InfoAction', array( &$pageInfo ) );
+ wfRunHooks( 'InfoAction', array( $this->getContext(), &$pageInfo ) );
// Render page information
foreach ( $pageInfo as $header => $infoTable ) {
}
// Page credits
- /*if ( $title->exists() ) {
+ /*if ( $this->page->exists() ) {
$content .= Html::rawElement( 'div', array( 'id' => 'mw-credits' ), $this->getContributors() );
}*/
* may add additional information in arbitrary positions. Array values are arrays with one
* element to be rendered as a header, arrays with two elements to be rendered as a table row.
*
- * @param $title Title object
+ * @return array
*/
- protected function pageInfo( $title ) {
- global $wgContLang, $wgDisableCounters, $wgRCMaxAge;
+ protected function pageInfo() {
+ global $wgContLang, $wgRCMaxAge;
$user = $this->getUser();
$lang = $this->getLanguage();
$id = $title->getArticleID();
// Get page information that would be too "expensive" to retrieve by normal means
- $userCanViewUnwatchedPages = $user->isAllowed( 'unwatchedpages' );
- $pageCounts = self::pageCounts( $title, $userCanViewUnwatchedPages, $wgDisableCounters );
+ $pageCounts = self::pageCounts( $title, $user );
// Get page properties
$dbr = wfGetDB( DB_SLAVE );
$this->msg( 'pageinfo-robot-policy' ), $this->msg( "pageinfo-robot-${policy['index']}" )
);
- if ( !$wgDisableCounters ) {
+ if ( isset( $pageCounts['views'] ) ) {
// Number of views
$pageInfo['header-basic'][] = array(
$this->msg( 'pageinfo-views' ), $lang->formatNum( $pageCounts['views'] )
);
}
- if ( $userCanViewUnwatchedPages ) {
+ if ( isset( $pageCounts['watchers'] ) ) {
// Number of page watchers
$pageInfo['header-basic'][] = array(
$this->msg( 'pageinfo-watchers' ), $lang->formatNum( $pageCounts['watchers'] )
);
}
+ if ( !$this->page->exists() ) {
+ return $pageInfo;
+ }
+
// Edit history
$pageInfo['header-edits'] = array();
* Returns page counts that would be too "expensive" to retrieve by normal means.
*
* @param $title Title object
- * @param $canViewUnwatched bool
- * @param $disableCounter bool
+ * @param $user User object
* @return array
*/
- protected static function pageCounts( $title, $canViewUnwatched, $disableCounter ) {
- global $wgRCMaxAge;
+ protected static function pageCounts( $title, $user ) {
+ global $wgRCMaxAge, $wgDisableCounters;
wfProfileIn( __METHOD__ );
$id = $title->getArticleID();
$dbr = wfGetDB( DB_SLAVE );
$result = array();
- if ( !$disableCounter ) {
+ if ( !$wgDisableCounters ) {
// Number of views
$views = (int) $dbr->selectField(
'page',
$result['views'] = $views;
}
- if ( $canViewUnwatched ) {
+ if ( $user->isAllowed( 'unwatchedpages' ) ) {
// Number of page watchers
$watchers = (int) $dbr->selectField(
'watchlist',
}
function onView() {
- global $wgGroupPermissions, $wgSquidMaxage, $wgForcedRawSMaxage, $wgJsMimeType;
+ global $wgSquidMaxage, $wgForcedRawSMaxage, $wgJsMimeType;
$this->getOutput()->disable();
$request = $this->getRequest();
$response->header( 'Content-type: ' . $contentType . '; charset=UTF-8' );
# Output may contain user-specific data;
# vary generated content for open sessions on private wikis
- $privateCache = !$wgGroupPermissions['*']['read'] && ( $smaxage == 0 || session_id() != '' );
+ $privateCache = !User::groupHasPermission( '*', 'read' ) && ( $smaxage == 0 || session_id() != '' );
# allow the client to cache this for 24 hours
$mode = $privateCache ? 'private' : 'public';
$response->header( 'Cache-Control: ' . $mode . ', s-maxage=' . $smaxage . ', max-age=' . $maxage );
$request->response()->header( "Last-modified: $lastmod" );
// Public-only due to cache headers
- $text = $rev->getText();
+ $content = $rev->getContent();
+
+ if ( !$content instanceof TextContent ) {
+ wfHttpError( 415, "Unsupported Media Type", "The requested page uses the content model `"
+ . $content->getModel() . "` which is not supported via this interface." );
+ die();
+ }
+
$section = $request->getIntOrNull( 'section' );
if ( $section !== null ) {
- $text = $wgParser->getSection( $text, $section );
+ $content = $content->getSection( $section );
}
+
+ $text = $content->getNativeData();
}
}
$lang = $this->getLanguage();
$userDate = $lang->userDate( $timestamp, $user );
$userTime = $lang->userTime( $timestamp, $user );
-
+
$this->getOutput()->addWikiMsg( 'filerevert-success', $this->getTitle()->getText(),
$userDate, $userTime,
wfExpandUrl( $this->page->getFile()->getArchiveUrl( $this->getRequest()->getText( 'oldimage' ) ),
protected function getPageTitle() {
return $this->msg( 'filerevert', $this->getTitle()->getText() );
}
-
+
protected function getDescription() {
$this->getOutput()->addBacklinkSubtitle( $this->getTitle() );
return '';
return;
}
- # Display permissions errors before read-only message -- there's no
- # point in misleading the user into thinking the inability to rollback
- # is only temporary.
- if ( !empty( $result ) && $result !== array( array( 'readonlytext' ) ) ) {
- # array_diff is completely broken for arrays of arrays, sigh.
- # Remove any 'readonlytext' error manually.
- $out = array();
- foreach ( $result as $error ) {
- if ( $error != array( 'readonlytext' ) ) {
- $out [] = $error;
- }
- }
- throw new PermissionsError( 'rollback', $out );
- }
+ #NOTE: Permission errors already handled by Action::checkExecute.
if ( $result == array( array( 'readonlytext' ) ) ) {
throw new ReadOnlyError;
}
+ #XXX: Would be nice if ErrorPageError could take multiple errors, and/or a status object.
+ # Right now, we only show the first error
+ foreach ( $result as $error ) {
+ throw new ErrorPageError( 'rollbackfailed', $error[0], array_slice( $error, 1 ) );
+ }
+
$current = $details['current'];
$target = $details['target'];
$newId = $details['newid'];
$this->getOutput()->returnToMain( false, $this->getTitle() );
if ( !$request->getBool( 'hidediff', false ) && !$this->getUser()->getBoolOption( 'norollbackdiff', false ) ) {
- $de = new DifferenceEngine( $this->getContext(), $current->getId(), $newId, false, true );
+ $contentHandler = $current->getContentHandler();
+ $de = $contentHandler->createDifferenceEngine( $this->getContext(), $current->getId(), $newId, false, true );
$de->showDiff( '', '' );
}
}
$rev1 = $this->revisionOrTitleOrId( $params['fromrev'], $params['fromtitle'], $params['fromid'] );
$rev2 = $this->revisionOrTitleOrId( $params['torev'], $params['totitle'], $params['toid'] );
- $de = new DifferenceEngine( $this->getContext(),
+ $revision = Revision::newFromId( $rev1 );
+
+ if ( !$revision ) {
+ $this->dieUsage( 'The diff cannot be retrieved, ' .
+ 'one revision does not exist or you do not have permission to view it.', 'baddiff' );
+ }
+
+ $contentHandler = $revision->getContentHandler();
+ $de = $contentHandler->createDifferenceEngine( $this->getContext(),
$rev1,
$rev2,
null, // rcid
$this->dieUsageMsg( array( 'invalidtitle', $params['title'] ) );
}
+ if ( !isset( $params['contentmodel'] ) || $params['contentmodel'] == '' ) {
+ $contentHandler = $pageObj->getContentHandler();
+ } else {
+ $contentHandler = ContentHandler::getForModelID( $params['contentmodel'] );
+ }
+
+ // @todo ask handler whether direct editing is supported at all! make allowFlatEdit() method or some such
+
+ if ( !isset( $params['contentformat'] ) || $params['contentformat'] == '' ) {
+ $params['contentformat'] = $contentHandler->getDefaultFormat();
+ }
+
+ $contentFormat = $params['contentformat'];
+
+ if ( !$contentHandler->isSupportedFormat( $contentFormat ) ) {
+ $name = $titleObj->getPrefixedDBkey();
+ $model = $contentHandler->getModelID();
+
+ $this->dieUsage( "The requested format $contentFormat is not supported for content model ".
+ " $model used by $name", 'badformat' );
+ }
+
$apiResult = $this->getResult();
if ( $params['redirect'] ) {
if ( $titleObj->isRedirect() ) {
$oldTitle = $titleObj;
- $titles = Title::newFromRedirectArray(
- Revision::newFromTitle(
- $oldTitle, false, Revision::READ_LATEST
- )->getText( Revision::FOR_THIS_USER, $user )
- );
+ $titles = Revision::newFromTitle( $oldTitle, false, Revision::READ_LATEST )
+ ->getContent( Revision::FOR_THIS_USER, $user )
+ ->getRedirectChain();
// array_shift( $titles );
$redirValues = array();
$this->dieUsageMsg( $errors[0] );
}
- $articleObj = Article::newFromTitle( $titleObj, $this->getContext() );
-
$toMD5 = $params['text'];
if ( !is_null( $params['appendtext'] ) || !is_null( $params['prependtext'] ) )
{
- // For non-existent pages, Article::getContent()
- // returns an interface message rather than ''
- // We do want getContent()'s behavior for non-existent
- // MediaWiki: pages, though
- if ( $articleObj->getID() == 0 && $titleObj->getNamespace() != NS_MEDIAWIKI ) {
- $content = '';
- } else {
- $content = $articleObj->getContent();
+ $content = $pageObj->getContent();
+
+ if ( !$content ) {
+ if ( $titleObj->getNamespace() == NS_MEDIAWIKI ) {
+ # If this is a MediaWiki:x message, then load the messages
+ # and return the message value for x.
+ $text = $titleObj->getDefaultMessageText();
+ if ( $text === false ) {
+ $text = '';
+ }
+
+ try {
+ $content = ContentHandler::makeContent( $text, $this->getTitle() );
+ } catch ( MWContentSerializationException $ex ) {
+ $this->dieUsage( $ex->getMessage(), 'parseerror' );
+ return;
+ }
+ } else {
+ # Otherwise, make a new empty content.
+ $content = $contentHandler->makeEmptyContent();
+ }
+ }
+
+ // @todo: Add support for appending/prepending to the Content interface
+
+ if ( !( $content instanceof TextContent ) ) {
+ $mode = $contentHandler->getModelID();
+ $this->dieUsage( "Can't append to pages using content model $mode", 'appendnotsupported' );
}
if ( !is_null( $params['section'] ) ) {
+ if ( !$contentHandler->supportsSections() ) {
+ $modelName = $contentHandler->getModelID();
+ $this->dieUsage( "Sections are not supported for this content model: $modelName.", 'sectionsnotsupported' );
+ }
+
// Process the content for section edits
- global $wgParser;
$section = intval( $params['section'] );
- $content = $wgParser->getSection( $content, $section, false );
- if ( $content === false ) {
+ $content = $content->getSection( $section );
+
+ if ( !$content ) {
$this->dieUsage( "There is no section {$section}.", 'nosuchsection' );
}
}
- $params['text'] = $params['prependtext'] . $content . $params['appendtext'];
+
+ if ( !$content ) {
+ $text = '';
+ } else {
+ $text = $content->serialize( $contentFormat );
+ }
+
+ $params['text'] = $params['prependtext'] . $text . $params['appendtext'];
$toMD5 = $params['prependtext'] . $params['appendtext'];
}
$this->dieUsageMsg( array( 'nosuchrevid', $params['undoafter'] ) );
}
- if ( $undoRev->getPage() != $articleObj->getID() ) {
+ if ( $undoRev->getPage() != $pageObj->getID() ) {
$this->dieUsageMsg( array( 'revwrongpage', $undoRev->getID(), $titleObj->getPrefixedText() ) );
}
- if ( $undoafterRev->getPage() != $articleObj->getID() ) {
+ if ( $undoafterRev->getPage() != $pageObj->getID() ) {
$this->dieUsageMsg( array( 'revwrongpage', $undoafterRev->getID(), $titleObj->getPrefixedText() ) );
}
- $newtext = $articleObj->getUndoText( $undoRev, $undoafterRev );
- if ( $newtext === false ) {
+ $newContent = $contentHandler->getUndoContent( $pageObj->getRevision(), $undoRev, $undoafterRev );
+
+ if ( !$newContent ) {
$this->dieUsageMsg( 'undo-failure' );
}
- $params['text'] = $newtext;
+
+ $params['text'] = $newContent->serialize( $params['contentformat'] );
+
// If no summary was given and we only undid one rev,
// use an autosummary
if ( is_null( $params['summary'] ) && $titleObj->getNextRevisionID( $undoafterRev->getID() ) == $params['undo'] ) {
// That interface kind of sucks, but it's workable
$requestArray = array(
'wpTextbox1' => $params['text'],
+ 'format' => $contentFormat,
+ 'model' => $contentHandler->getModelID(),
'wpEditToken' => $params['token'],
'wpIgnoreBlankSummary' => ''
);
if ( !is_null( $params['basetimestamp'] ) && $params['basetimestamp'] != '' ) {
$requestArray['wpEdittime'] = wfTimestamp( TS_MW, $params['basetimestamp'] );
} else {
- $requestArray['wpEdittime'] = $articleObj->getTimestamp();
+ $requestArray['wpEdittime'] = $pageObj->getTimestamp();
}
if ( !is_null( $params['starttimestamp'] ) && $params['starttimestamp'] != '' ) {
// TODO: Make them not or check if they still do
$wgTitle = $titleObj;
- $ep = new EditPage( $articleObj );
+ $articleObject = new Article( $titleObj );
+ $ep = new EditPage( $articleObject );
+
+ // allow editing of non-textual content.
+ $ep->allowNonTextContent = true;
+
$ep->setContextTitle( $titleObj );
$ep->importFormData( $req );
}
// Do the actual save
- $oldRevId = $articleObj->getRevIdFetched();
+ $oldRevId = $articleObject->getRevIdFetched();
$result = null;
// Fake $wgRequest for some hooks inside EditPage
// @todo FIXME: This interface SUCKS
case EditPage::AS_HOOK_ERROR_EXPECTED:
$this->dieUsageMsg( 'hookaborted' );
+ case EditPage::AS_PARSE_ERROR:
+ $this->dieUsage( $status->getMessage(), 'parseerror' );
+
case EditPage::AS_IMAGE_REDIRECT_ANON:
$this->dieUsageMsg( 'noimageredirect-anon' );
$r['result'] = 'Success';
$r['pageid'] = intval( $titleObj->getArticleID() );
$r['title'] = $titleObj->getPrefixedText();
- $newRevId = $articleObj->getLatest();
+ $r['contentmodel'] = $titleObj->getContentModel();
+ $newRevId = $articleObject->getLatest();
if ( $newRevId == $oldRevId ) {
$r['nochange'] = '';
} else {
$r['oldrevid'] = intval( $oldRevId );
$r['newrevid'] = intval( $newRevId );
$r['newtimestamp'] = wfTimestamp( TS_ISO_8601,
- $articleObj->getTimestamp() );
+ $pageObj->getTimestamp() );
}
break;
array( 'undo-failure' ),
array( 'hashcheckfailed' ),
array( 'hookaborted' ),
+ array( 'code' => 'parseerror', 'info' => 'Failed to parse the given text.' ),
array( 'noimageredirect-anon' ),
array( 'noimageredirect-logged' ),
array( 'spamdetected', 'spam' ),
array( 'unknownerror', 'retval' ),
array( 'code' => 'nosuchsection', 'info' => 'There is no section section.' ),
array( 'code' => 'invalidsection', 'info' => 'The section parameter must be set to an integer or \'new\'' ),
+ array( 'code' => 'sectionsnotsupported', 'info' => 'Sections are not supported for this type of page.' ),
+ array( 'code' => 'editnotsupported', 'info' => 'Editing of this type of page is not supported using '
+ . 'the text based edit API.' ),
+ array( 'code' => 'appendnotsupported', 'info' => 'This type of page can not be edited by appending '
+ . 'or prepending text.' ),
+ array( 'code' => 'badformat', 'info' => 'The requested serialization format can not be applied to '
+ . 'the page\'s content model' ),
array( 'customcssprotected' ),
array( 'customjsprotected' ),
)
ApiBase::PARAM_TYPE => 'boolean',
ApiBase::PARAM_DFLT => false,
),
+ 'contentformat' => array(
+ ApiBase::PARAM_TYPE => ContentHandler::getAllContentFormats(),
+ ),
+ 'contentmodel' => array(
+ ApiBase::PARAM_TYPE => ContentHandler::getContentModels(),
+ )
);
}
'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',
+ 'contentformat' => 'Content serialization format used for the input text',
+ 'contentmodel' => 'Content model of the new content',
);
}
protected function feedItemDesc( $revision ) {
if( $revision ) {
$msg = wfMessage( 'colon-separator' )->inContentLanguage()->text();
+ $content = $revision->getContent();
+
+ if ( $content instanceof TextContent ) {
+ // only textual content has a "source view".
+ $html = nl2br( htmlspecialchars( $content->getNativeData() ) );
+ } else {
+ //XXX: we could get an HTML representation of the content via getParserOutput, but that may
+ // contain JS magic and generally may not be suitable for inclusion in a feed.
+ // Perhaps Content should have a getDescriptiveHtml method and/or a getSourceText method.
+ //Compare also FeedUtils::formatDiffRow.
+ $html = '';
+ }
+
return '<p>' . htmlspecialchars( $revision->getUserText() ) . $msg .
htmlspecialchars( FeedItem::stripComment( $revision->getComment() ) ) .
- "</p>\n<hr />\n<div>" .
- nl2br( htmlspecialchars( $revision->getText() ) ) . "</div>";
+ "</p>\n<hr />\n<div>" . $html . "</div>";
}
return '';
}
--- /dev/null
+<?php
+/**
+ *
+ *
+ * Created on Oct 22, 2006
+ *
+ * Copyright © 2006 Yuri Astrakhan <Firstname><Lastname>@gmail.com
+ *
+ * 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
+ */
+
+/**
+ * API Serialized PHP output formatter
+ * @ingroup API
+ */
+class ApiFormatNone extends ApiFormatBase {
+
+ public function __construct( $main, $format ) {
+ parent::__construct( $main, $format );
+ }
+
+ public function getMimeType() {
+ return 'text/plain';
+ }
+
+ public function execute() {
+ }
+
+ public function getDescription() {
+ return 'Output nothing' . parent::getDescription();
+ }
+
+ public function getVersion() {
+ return __CLASS__ . ': $Id$';
+ }
+}
'dbgfm' => 'ApiFormatDbg',
'dump' => 'ApiFormatDump',
'dumpfm' => 'ApiFormatDump',
+ 'none' => 'ApiFormatNone',
);
/**
protected function logRequest( $time ) {
$request = $this->getRequest();
$milliseconds = $time === null ? '?' : round( $time * 1000 );
- $s = 'API' .
+ $s = 'API' .
' ' . $request->getMethod() .
' ' . wfUrlencode( str_replace( ' ', '_', $this->getUser()->getName() ) ) .
' ' . $request->getIP() .
*/
public function getCheck( $name ) {
$this->mParamsUsed[$name] = true;
- return $this->getRequest()->getCheck( $name );
+ return $this->getRequest()->getCheck( $name );
}
/**
* @ingroup API
*/
class ApiParse extends ApiBase {
- private $section, $text, $pstText = null;
+
+ /** @var String $section */
+ private $section = null;
+
+ /** @var Content $content */
+ private $content = null;
+
+ /** @var Content $pstContent */
+ private $pstContent = null;
public function __construct( $main, $action ) {
parent::__construct( $main, $action );
$pageid = $params['pageid'];
$oldid = $params['oldid'];
+ $model = $params['contentmodel'];
+ $format = $params['contentformat'];
+
if ( !is_null( $page ) && ( !is_null( $text ) || $title != 'API' ) ) {
$this->dieUsage( 'The page parameter cannot be used together with the text and title parameters', 'params' );
}
// If for some reason the "oldid" is actually the current revision, it may be cached
if ( $rev->isCurrent() ) {
// May get from/save to parser cache
- $p_result = $this->getParsedSectionOrText( $pageObj, $popts, $pageid,
- isset( $prop['wikitext'] ) ) ;
+ $p_result = $this->getParsedContent( $pageObj, $popts,
+ $pageid, isset( $prop['wikitext'] ) ) ;
} else { // This is an old revision, so get the text differently
- $this->text = $rev->getText( Revision::FOR_THIS_USER, $this->getUser() );
+ $this->content = $rev->getContent( Revision::FOR_THIS_USER, $this->getUser() );
if ( $this->section !== false ) {
- $this->text = $this->getSectionText( $this->text, 'r' . $rev->getId() );
+ $this->content = $this->getSectionContent( $this->content, 'r' . $rev->getId() );
}
// Should we save old revision parses to the parser cache?
- $p_result = $wgParser->parse( $this->text, $titleObj, $popts );
+ $p_result = $this->content->getParserOutput( $titleObj, $popts );
}
} else { // Not $oldid, but $pageid or $page
if ( $params['redirects'] ) {
$oldid = $pageObj->getLatest();
}
+
$popts = $pageObj->makeParserOptions( $this->getContext() );
$popts->enableLimitReport( !$params['disablepp'] );
// Potentially cached
- $p_result = $this->getParsedSectionOrText( $pageObj, $popts, $pageid,
- isset( $prop['wikitext'] ) ) ;
+ $p_result = $this->getParsedContent( $pageObj, $popts, $pageid,
+ isset( $prop['wikitext'] ) ) ;
}
} else { // Not $oldid, $pageid, $page. Hence based on $text
-
- if ( is_null( $text ) ) {
- $this->dieUsage( 'The text parameter should be passed with the title parameter. Should you be using the "page" parameter instead?', 'params' );
- }
- $this->text = $text;
$titleObj = Title::newFromText( $title );
if ( !$titleObj ) {
$this->dieUsageMsg( array( 'invalidtitle', $title ) );
$popts = $pageObj->makeParserOptions( $this->getContext() );
$popts->enableLimitReport( !$params['disablepp'] );
+ if ( is_null( $text ) ) {
+ $this->dieUsage( 'The text parameter should be passed with the title parameter. Should you be using the "page" parameter instead?', 'params' );
+ }
+
+ try {
+ $this->content = ContentHandler::makeContent( $text, $titleObj, $model, $format );
+ } catch ( MWContentSerializationException $ex ) {
+ $this->dieUsage( $ex->getMessage(), 'parseerror' );
+ }
+
if ( $this->section !== false ) {
- $this->text = $this->getSectionText( $this->text, $titleObj->getText() );
+ $this->content = $this->getSectionContent( $this->content, $titleObj->getText() );
}
if ( $params['pst'] || $params['onlypst'] ) {
- $this->pstText = $wgParser->preSaveTransform( $this->text, $titleObj, $this->getUser(), $popts );
+ $this->pstContent = $this->content->preSaveTransform( $titleObj, $this->getUser(), $popts );
}
if ( $params['onlypst'] ) {
// Build a result and bail out
$result_array = array();
$result_array['text'] = array();
- $result->setContent( $result_array['text'], $this->pstText );
+ $result->setContent( $result_array['text'], $this->pstContent->serialize( $format ) );
if ( isset( $prop['wikitext'] ) ) {
$result_array['wikitext'] = array();
- $result->setContent( $result_array['wikitext'], $this->text );
+ $result->setContent( $result_array['wikitext'], $this->content->serialize( $format ) );
}
$result->addValue( null, $this->getModuleName(), $result_array );
return;
}
+
// Not cached (save or load)
- $p_result = $wgParser->parse( $params['pst'] ? $this->pstText : $this->text, $titleObj, $popts );
+ if ( $params['pst'] ) {
+ $p_result = $this->pstContent->getParserOutput( $titleObj, $popts );
+ } else {
+ $p_result = $this->content->getParserOutput( $titleObj, $popts );
+ }
}
$result_array = array();
if ( isset( $prop['wikitext'] ) ) {
$result_array['wikitext'] = array();
- $result->setContent( $result_array['wikitext'], $this->text );
- if ( !is_null( $this->pstText ) ) {
+ $result->setContent( $result_array['wikitext'], $this->content->serialize( $format ) );
+ if ( !is_null( $this->pstContent ) ) {
$result_array['psttext'] = array();
- $result->setContent( $result_array['psttext'], $this->pstText );
+ $result->setContent( $result_array['psttext'], $this->pstContent->serialize( $format ) );
}
}
if ( isset( $prop['properties'] ) ) {
}
if ( $params['generatexml'] ) {
+ if ( $this->content->getModel() != CONTENT_MODEL_WIKITEXT ) {
+ $this->dieUsage( "generatexml is only supported for wikitext content", "notwikitext" );
+ }
+
$wgParser->startExternalParse( $titleObj, $popts, OT_PREPROCESS );
- $dom = $wgParser->preprocessToDom( $this->text );
+ $dom = $wgParser->preprocessToDom( $this->content->getNativeData() );
if ( is_callable( array( $dom, 'saveXML' ) ) ) {
$xml = $dom->saveXML();
} else {
* @param $getWikitext Bool
* @return ParserOutput
*/
- private function getParsedSectionOrText( $page, $popts, $pageId = null, $getWikitext = false ) {
- global $wgParser;
+ private function getParsedContent( WikiPage $page, $popts, $pageId = null, $getWikitext = false ) {
+ $this->content = $page->getContent( Revision::RAW ); //XXX: really raw?
if ( $this->section !== false ) {
- $this->text = $this->getSectionText( $page->getRawText(), !is_null( $pageId )
- ? 'page id ' . $pageId : $page->getTitle()->getPrefixedText() );
+ $this->content = $this->getSectionContent(
+ $this->content,
+ !is_null( $pageId ) ? 'page id ' . $pageId : $page->getTitle()->getText() );
// Not cached (save or load)
- return $wgParser->parse( $this->text, $page->getTitle(), $popts );
+ return $this->content->getParserOutput( $page->getTitle(), $popts );
} else {
// Try the parser cache first
// getParserOutput will save to Parser cache if able
$this->dieUsage( "There is no revision ID {$page->getLatest()}", 'missingrev' );
}
if ( $getWikitext ) {
- $this->text = $page->getRawText();
+ $this->content = $page->getContent( Revision::RAW );
}
return $pout;
}
}
- private function getSectionText( $text, $what ) {
- global $wgParser;
+ private function getSectionContent( Content $content, $what ) {
// Not cached (save or load)
- $text = $wgParser->getSection( $text, $this->section, false );
- if ( $text === false ) {
+ $section = $content->getSection( $this->section );
+ if ( $section === false ) {
$this->dieUsage( "There is no section {$this->section} in " . $what, 'nosuchsection' );
}
- return $text;
+ if ( $section === null ) {
+ $this->dieUsage( "Sections are not supported by " . $what, 'nosuchsection' );
+ $section = false;
+ }
+ return $section;
}
private function formatLangLinks( $links ) {
'section' => null,
'disablepp' => false,
'generatexml' => false,
+ 'contentformat' => array(
+ ApiBase::PARAM_TYPE => ContentHandler::getAllContentFormats(),
+ ),
+ 'contentmodel' => array(
+ ApiBase::PARAM_TYPE => ContentHandler::getContentModels(),
+ )
);
}
'section' => 'Only retrieve the content of this section number',
'disablepp' => 'Disable the PP Report from the parser output',
'generatexml' => 'Generate XML parse tree',
+ 'contentformat' => 'Content serialization format used for the input text',
+ 'contentmodel' => 'Content model of the new content',
);
}
array( 'code' => 'nosuchsection', 'info' => 'There is no section sectionnumber in page' ),
array( 'nosuchpageid' ),
array( 'invalidtitle', 'title' ),
+ array( 'code' => 'parseerror', 'info' => 'Failed to parse the given text.' ),
+ array( 'code' => 'notwikitext', 'info' => 'The requested operation is only supported on wikitext content.' ),
) );
}
if( $forceLinkUpdate ) {
if ( !$user->pingLimiter() ) {
- global $wgParser, $wgEnableParserCache;
+ global $wgEnableParserCache;
$popts = $page->makeParserOptions( 'canonical' );
- $p_result = $wgParser->parse( $page->getRawText(), $title, $popts,
- true, true, $page->getLatest() );
+
+ # Parse content; note that HTML generation is only needed if we want to cache the result.
+ $content = $page->getContent( Revision::RAW );
+ $p_result = $content->getParserOutput( $title, $page->getLatest(), $popts, $wgEnableParserCache );
# Update the links tables
- $updates = $p_result->getSecondaryDataUpdates( $title );
+ $updates = $content->getSecondaryDataUpdates( $title, null, true, $p_result );
DataUpdate::runUpdates( $updates );
$r['linkupdate'] = '';
private $params, $redirects, $convertTitles, $iwUrl;
+ /**
+ * List of Api Query prop modules
+ * @var array
+ */
private $mQueryPropModules = array(
'categories' => 'ApiQueryCategories',
'categoryinfo' => 'ApiQueryCategoryInfo',
'templates' => 'ApiQueryLinks',
);
+ /**
+ * List of Api Query list modules
+ * @var array
+ */
private $mQueryListModules = array(
'allcategories' => 'ApiQueryAllCategories',
'allimages' => 'ApiQueryAllImages',
'watchlistraw' => 'ApiQueryWatchlistRaw',
);
+ /**
+ * List of Api Query meta modules
+ * @var array
+ */
private $mQueryMetaModules = array(
'allmessages' => 'ApiQueryAllMessages',
'siteinfo' => 'ApiQuerySiteinfo',
'userinfo' => 'ApiQueryUserInfo',
);
+ /**
+ * List of Api Query generator modules
+ * Defined in code, rather than being derived at runtime,
+ * due to performance reasons
+ * @var array
+ */
+ private $mQueryGenerators = array(
+ 'allcategories' => 'ApiQueryAllCategories',
+ 'allimages' => 'ApiQueryAllImages',
+ 'alllinks' => 'ApiQueryAllLinks',
+ 'allpages' => 'ApiQueryAllPages',
+ 'backlinks' => 'ApiQueryBacklinks',
+ 'categories' => 'ApiQueryCategories',
+ 'categorymembers' => 'ApiQueryCategoryMembers',
+ 'duplicatefiles' => 'ApiQueryDuplicateFiles',
+ 'embeddedin' => 'ApiQueryBacklinks',
+ 'exturlusage' => 'ApiQueryExtLinksUsage',
+ 'images' => 'ApiQueryImages',
+ 'imageusage' => 'ApiQueryBacklinks',
+ 'iwbacklinks' => 'ApiQueryIWBacklinks',
+ 'langbacklinks' => 'ApiQueryLangBacklinks',
+ 'links' => 'ApiQueryLinks',
+ 'protectedtitles' => 'ApiQueryProtectedTitles',
+ 'querypage' => 'ApiQueryQueryPage',
+ 'random' => 'ApiQueryRandom',
+ 'recentchanges' => 'ApiQueryRecentChanges',
+ 'search' => 'ApiQuerySearch',
+ 'templates' => 'ApiQueryLinks',
+ 'watchlist' => 'ApiQueryWatchlist',
+ 'watchlistraw' => 'ApiQueryWatchlistRaw',
+ );
+
private $mSlaveDB = null;
private $mNamedDB = array();
- protected $mAllowedGenerators = array();
+ protected $mAllowedGenerators;
/**
* @param $main ApiMain
parent::__construct( $main, $action );
// Allow custom modules to be added in LocalSettings.php
- global $wgAPIPropModules, $wgAPIListModules, $wgAPIMetaModules,
- $wgMemc, $wgAPICacheHelpTimeout;
+ global $wgAPIPropModules, $wgAPIListModules, $wgAPIMetaModules, $wgAPIGeneratorModules;
self::appendUserModules( $this->mQueryPropModules, $wgAPIPropModules );
self::appendUserModules( $this->mQueryListModules, $wgAPIListModules );
self::appendUserModules( $this->mQueryMetaModules, $wgAPIMetaModules );
+ self::appendUserModules( $this->mQueryGenerators, $wgAPIGeneratorModules );
$this->mPropModuleNames = array_keys( $this->mQueryPropModules );
$this->mListModuleNames = array_keys( $this->mQueryListModules );
$this->mMetaModuleNames = array_keys( $this->mQueryMetaModules );
-
- // Get array of query generators from cache if present
- $key = wfMemcKey( 'apiquerygenerators', SpecialVersion::getVersion( 'nodb' ) );
-
- if ( $wgAPICacheHelpTimeout > 0 ) {
- $cached = $wgMemc->get( $key );
- if ( $cached ) {
- $this->mAllowedGenerators = $cached;
- return;
- }
- }
- $this->makeGeneratorList( $this->mQueryPropModules );
- $this->makeGeneratorList( $this->mQueryListModules );
-
- if ( $wgAPICacheHelpTimeout > 0 ) {
- $wgMemc->set( $key, $this->mAllowedGenerators, $wgAPICacheHelpTimeout );
- }
+ $this->mAllowedGenerators = array_keys( $this->mQueryGenerators );
}
/**
* Get the array mapping module names to class names
* @return array array(modulename => classname)
*/
- function getModules() {
+ public function getModules() {
return array_merge( $this->mQueryPropModules, $this->mQueryListModules, $this->mQueryMetaModules );
}
+ /**
+ * Get the generators array mapping module names to class names
+ * @return array array(modulename => classname)
+ */
+ public function getGenerators() {
+ return $this->mQueryGenerators;
+ }
+
/**
* Get whether the specified module is a prop, list or a meta query module
* @param $moduleName string Name of the module to find type for
return implode( "\n", $moduleDescriptions );
}
- /**
- * Adds any classes that are a subclass of ApiQueryGeneratorBase
- * to the allowed generator list
- * @param $moduleList array()
- */
- private function makeGeneratorList( $moduleList ) {
- foreach( $moduleList as $moduleName => $moduleClass ) {
- if ( is_subclass_of( $moduleClass, 'ApiQueryGeneratorBase' ) ) {
- $this->mAllowedGenerators[] = $moduleName;
- }
- }
- }
-
/**
* Override to add extra parameters from PageSet
* @return string
} else {
$this->addWhereRange( 'cat_pages', 'older', $max, $min);
}
-
if ( isset( $params['prefix'] ) ) {
$this->addWhere( 'cat_title' . $db->buildLike( $this->titlePartToKey( $params['prefix'] ), $db->anyString() ) );
$this->addTables( 'filearchive' );
$this->addFields( array( 'fa_name', 'fa_deleted' ) );
- $this->addFieldsIf( 'fa_storage_key', $fld_sha1 );
+ $this->addFieldsIf( 'fa_sha1', $fld_sha1 );
$this->addFieldsIf( 'fa_timestamp', $fld_timestamp );
$this->addFieldsIf( array( 'fa_user', 'fa_user_text' ), $fld_user );
$this->addFieldsIf( array( 'fa_height', 'fa_width', 'fa_size' ), $fld_dimensions || $fld_size );
$sha1Set = isset( $params['sha1'] );
$sha1base36Set = isset( $params['sha1base36'] );
if ( $sha1Set || $sha1base36Set ) {
- global $wgMiserMode;
- if ( $wgMiserMode ) {
- $this->dieUsage( 'Search by hash disabled in Miser Mode', 'hashsearchdisabled' );
- }
-
$sha1 = false;
if ( $sha1Set ) {
if ( !$this->validateSha1Hash( $params['sha1'] ) ) {
$sha1 = $params['sha1base36'];
}
if ( $sha1 ) {
- $this->addWhere( 'fa_storage_key ' . $db->buildLike( "{$sha1}.", $db->anyString() ) );
+ $this->addWhereFld( 'fa_sha1', $sha1 );
}
}
self::addTitleInfo( $file, $title );
if ( $fld_sha1 ) {
- $file['sha1'] = wfBaseConvert( LocalRepo::getHashFromKey( $row->fa_storage_key ), 36, 16, 40 );
+ $file['sha1'] = wfBaseConvert( $row->fa_sha1, 36, 16, 40 );
}
if ( $fld_timestamp ) {
$file['timestamp'] = wfTimestamp( TS_ISO_8601, $row->fa_timestamp );
'prefix' => 'Search for all image titles that begin with this value',
'dir' => 'The direction in which to list',
'limit' => 'How many images to return in total',
- 'sha1' => "SHA1 hash of image. Overrides {$this->getModulePrefix()}sha1base36. Disabled in Miser Mode",
- 'sha1base36' => 'SHA1 hash of image in base 36 (used in MediaWiki). Disabled in Miser Mode',
+ 'sha1' => "SHA1 hash of image. Overrides {$this->getModulePrefix()}sha1base36",
+ 'sha1base36' => 'SHA1 hash of image in base 36 (used in MediaWiki)',
'prop' => array(
'What image information to get:',
' sha1 - Adds SHA-1 hash for the image',
--- /dev/null
+<?php
+
+/**
+ * Base query module for querying results from ORMTables.
+ *
+ * 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.21
+ *
+ * @file
+ * @ingroup API
+ *
+ * @licence GNU GPL v2+
+ * @author Jeroen De Dauw < jeroendedauw@gmail.com >
+ */
+abstract class ApiQueryORM extends ApiQueryBase {
+
+ /**
+ * Returns an instance of the IORMTable table being queried.
+ *
+ * @since 1.21
+ *
+ * @return IORMTable
+ */
+ protected abstract function getTable();
+
+ /**
+ * Returns the name of the individual rows.
+ * For example: page, user, contest, campaign, etc.
+ * This is used to appropriately name elements in XML.
+ * Deriving classes typically override this method.
+ *
+ * @since 1.21
+ *
+ * @return string
+ */
+ protected function getRowName() {
+ return 'item';
+ }
+
+ /**
+ * Returns the name of the list of rows.
+ * For example: pages, users, contests, campaigns, etc.
+ * This is used to appropriately name nodes in the output.
+ * Deriving classes typically override this method.
+ *
+ * @since 1.21
+ *
+ * @return string
+ */
+ protected function getListName() {
+ return 'items';
+ }
+
+ /**
+ * Returns the path to where the items results should be added in the result.
+ *
+ * @since 1.21
+ *
+ * @return null|string|array
+ */
+ protected function getResultPath() {
+ return null;
+ }
+
+ /**
+ * Get the parameters, find out what the conditions for the query are,
+ * run it, and add the results.
+ *
+ * @since 1.21
+ */
+ public function execute() {
+ $params = $this->getParams();
+
+ if ( !in_array( 'id', $params['props'] ) ) {
+ $params['props'][] = 'id';
+ }
+
+ $results = $this->getResults( $params, $this->getConditions( $params ) );
+ $this->addResults( $params, $results );
+ }
+
+ /**
+ * Get the request parameters, handle the * value for the props param
+ * and remove all params set to null (ie those that are not actually provided).
+ *
+ * @since 1.21
+ *
+ * @return array
+ */
+ protected function getParams() {
+ return array_filter(
+ $this->extractRequestParams(),
+ function( $prop ) {
+ return isset( $prop );
+ }
+ );
+ }
+
+ /**
+ * Get the conditions for the query. These will be provided as
+ * regular parameters, together with limit, props, continue,
+ * and possibly others which we need to get rid off.
+ *
+ * @since 1.21
+ *
+ * @param array $params
+ *
+ * @return array
+ */
+ protected function getConditions( array $params ) {
+ $conditions = array();
+ $fields = $this->getTable()->getFields();
+
+ foreach ( $params as $name => $value ) {
+ if ( array_key_exists( $name, $fields ) ) {
+ $conditions[$name] = $value;
+ }
+ }
+
+ return $conditions;
+ }
+
+ /**
+ * Get the actual results.
+ *
+ * @since 1.21
+ *
+ * @param array $params
+ * @param array $conditions
+ *
+ * @return ORMResult
+ */
+ protected function getResults( array $params, array $conditions ) {
+ return $this->getTable()->select(
+ $params['props'],
+ $conditions,
+ array(
+ 'LIMIT' => $params['limit'] + 1,
+ 'ORDER BY' => $this->getTable()->getPrefixedField( 'id' ) . ' ASC',
+ ),
+ __METHOD__
+ );
+ }
+
+ /**
+ * Serialize the results and add them to the result object.
+ *
+ * @since 1.21
+ *
+ * @param array $params
+ * @param ORMResult $results
+ */
+ protected function addResults( array $params, ORMResult $results ) {
+ $serializedResults = array();
+ $count = 0;
+
+ foreach ( $results as /* IORMRow */ $result ) {
+ if ( ++$count > $params['limit'] ) {
+ // We've reached the one extra which shows that
+ // there are additional pages to be had. Stop here...
+ $this->setContinueEnumParameter( 'continue', $result->getId() );
+ break;
+ }
+
+ $serializedResults[] = $this->formatRow( $result, $params );
+ }
+
+ $this->setIndexedTagNames( $serializedResults );
+ $this->addSerializedResults( $serializedResults );
+ }
+
+ /**
+ * Formats a row to it's desired output format.
+ *
+ * @since 1.21
+ *
+ * @param IORMRow $result
+ * @param array $params
+ *
+ * @return mixed
+ */
+ protected function formatRow( IORMRow $result, array $params ) {
+ return $result->toArray( $params['props'] );
+ }
+
+ /**
+ * Set the tag names for formats such as XML.
+ *
+ * @since 1.21
+ *
+ * @param array $serializedResults
+ */
+ protected function setIndexedTagNames( array &$serializedResults ) {
+ $this->getResult()->setIndexedTagName( $serializedResults, $this->getRowName() );
+ }
+
+ /**
+ * Add the serialized results to the result object.
+ *
+ * @since 1.21
+ *
+ * @param array $serializedResults
+ */
+ protected function addSerializedResults( array $serializedResults ) {
+ $this->getResult()->addValue(
+ $this->getResultPath(),
+ $this->getListName(),
+ $serializedResults
+ );
+ }
+
+ /**
+ * @see ApiBase::getAllowedParams()
+ * @return array
+ */
+ public function getAllowedParams() {
+ $params = array (
+ 'props' => array(
+ ApiBase::PARAM_TYPE => $this->getTable()->getFieldNames(),
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_REQUIRED => true,
+ ),
+ 'limit' => array(
+ ApiBase::PARAM_DFLT => 20,
+ ApiBase::PARAM_TYPE => 'limit',
+ ApiBase::PARAM_MIN => 1,
+ ApiBase::PARAM_MAX => ApiBase::LIMIT_BIG1,
+ ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2
+ ),
+ 'continue' => null,
+ );
+
+ return array_merge( $this->getTable()->getAPIParams(), $params );
+ }
+
+ /**
+ * @see ApiBase::getParamDescription()
+ * @return array
+ */
+ public function getParamDescription() {
+ $descriptions = array (
+ 'props' => 'Fields to query',
+ 'continue' => 'Offset number from where to continue the query',
+ 'limit' => 'Max amount of rows to return',
+ );
+
+ return array_merge( $this->getTable()->getFieldDescriptions(), $descriptions );
+ }
+
+}
class ApiQueryRevisions extends ApiQueryBase {
private $diffto, $difftotext, $expandTemplates, $generateXML, $section,
- $token, $parseContent;
+ $token, $parseContent, $contentFormat;
public function __construct( $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'rv' );
}
- private $fld_ids = false, $fld_flags = false, $fld_timestamp = false, $fld_size = false,
+ private $fld_ids = false, $fld_flags = false, $fld_timestamp = false, $fld_size = false, $fld_sha1 = false,
$fld_comment = false, $fld_parsedcomment = false, $fld_user = false, $fld_userid = false,
- $fld_content = false, $fld_tags = false;
+ $fld_content = false, $fld_tags = false, $fld_contentmodel = false;
private $tokenFunctions;
$this->fld_parsedcomment = isset ( $prop['parsedcomment'] );
$this->fld_size = isset ( $prop['size'] );
$this->fld_sha1 = isset ( $prop['sha1'] );
+ $this->fld_contentmodel = isset ( $prop['contentmodel'] );
$this->fld_userid = isset( $prop['userid'] );
$this->fld_user = isset ( $prop['user'] );
$this->token = $params['token'];
+ if ( !empty( $params['contentformat'] ) ) {
+ $this->contentFormat = $params['contentformat'];
+ }
+
// Possible indexes used
$index = array();
}
}
+ if ( $this->fld_contentmodel ) {
+ $vals['contentmodel'] = $revision->getContentModel();
+ }
+
if ( $this->fld_comment || $this->fld_parsedcomment ) {
if ( $revision->isDeleted( Revision::DELETED_COMMENT ) ) {
$vals['commenthidden'] = '';
}
}
- $text = null;
+ $content = null;
global $wgParser;
if ( $this->fld_content || !is_null( $this->difftotext ) ) {
- $text = $revision->getText();
+ $content = $revision->getContent();
// Expand templates after getting section content because
// template-added sections don't count and Parser::preprocess()
// will have less input
if ( $this->section !== false ) {
- $text = $wgParser->getSection( $text, $this->section, false );
- if ( $text === false ) {
+ $content = $content->getSection( $this->section, false );
+ if ( !$content ) {
$this->dieUsage( "There is no section {$this->section} in r" . $revision->getId(), 'nosuchsection' );
}
}
}
if ( $this->fld_content && !$revision->isDeleted( Revision::DELETED_TEXT ) ) {
+ $text = null;
+
if ( $this->generateXML ) {
- $wgParser->startExternalParse( $title, ParserOptions::newFromContext( $this->getContext() ), OT_PREPROCESS );
- $dom = $wgParser->preprocessToDom( $text );
- if ( is_callable( array( $dom, 'saveXML' ) ) ) {
- $xml = $dom->saveXML();
+ if ( $content->getModel() === CONTENT_MODEL_WIKITEXT ) {
+ $t = $content->getNativeData(); # note: don't set $text
+
+ $wgParser->startExternalParse( $title, ParserOptions::newFromContext( $this->getContext() ), OT_PREPROCESS );
+ $dom = $wgParser->preprocessToDom( $t );
+ if ( is_callable( array( $dom, 'saveXML' ) ) ) {
+ $xml = $dom->saveXML();
+ } else {
+ $xml = $dom->__toString();
+ }
+ $vals['parsetree'] = $xml;
} else {
- $xml = $dom->__toString();
+ $this->setWarning( "Conversion to XML is supported for wikitext only, " .
+ $title->getPrefixedDBkey() .
+ " uses content model " . $content->getModel() . ")" );
}
- $vals['parsetree'] = $xml;
-
}
+
if ( $this->expandTemplates && !$this->parseContent ) {
- $text = $wgParser->preprocess( $text, $title, ParserOptions::newFromContext( $this->getContext() ) );
+ #XXX: implement template expansion for all content types in ContentHandler?
+ if ( $content->getModel() === CONTENT_MODEL_WIKITEXT ) {
+ $text = $content->getNativeData();
+
+ $text = $wgParser->preprocess( $text, $title, ParserOptions::newFromContext( $this->getContext() ) );
+ } else {
+ $this->setWarning( "Template expansion is supported for wikitext only, " .
+ $title->getPrefixedDBkey() .
+ " uses content model " . $content->getModel() . ")" );
+
+ $text = false;
+ }
}
if ( $this->parseContent ) {
- $text = $wgParser->parse( $text, $title, ParserOptions::newFromContext( $this->getContext() ) )->getText();
+ $po = $content->getParserOutput( $title, ParserOptions::newFromContext( $this->getContext() ) );
+ $text = $po->getText();
+ }
+
+ if ( $text === null ) {
+ $format = $this->contentFormat ? $this->contentFormat : $content->getDefaultFormat();
+
+ if ( !$content->isSupportedFormat( $format ) ) {
+ $model = $content->getModel();
+ $name = $title->getPrefixedDBkey();
+
+ $this->dieUsage( "The requested format {$this->contentFormat} is not supported ".
+ "for content model $model used by $name", 'badformat' );
+ }
+
+ $text = $content->serialize( $format );
+ $vals['contentformat'] = $format;
+ }
+
+ if ( $text !== false ) {
+ ApiResult::setContent( $vals, $text );
}
- ApiResult::setContent( $vals, $text );
} elseif ( $this->fld_content ) {
$vals['texthidden'] = '';
}
$vals['diff'] = array();
$context = new DerivativeContext( $this->getContext() );
$context->setTitle( $title );
+ $handler = $revision->getContentHandler();
+
if ( !is_null( $this->difftotext ) ) {
- $engine = new DifferenceEngine( $context );
- $engine->setText( $text, $this->difftotext );
+ $model = $title->getContentModel();
+
+ if ( $this->contentFormat
+ && !ContentHandler::getForModelID( $model )->isSupportedFormat( $this->contentFormat ) ) {
+
+ $name = $title->getPrefixedDBkey();
+
+ $this->dieUsage( "The requested format {$this->contentFormat} is not supported for ".
+ "content model $model used by $name", 'badformat' );
+ }
+
+ $difftocontent = ContentHandler::makeContent( $this->difftotext, $title, $model, $this->contentFormat );
+
+ $engine = $handler->createDifferenceEngine( $context );
+ $engine->setContent( $content, $difftocontent );
} else {
- $engine = new DifferenceEngine( $context, $revision->getID(), $this->diffto );
+ $engine = $handler->createDifferenceEngine( $context, $revision->getID(), $this->diffto );
$vals['diff']['from'] = $engine->getOldid();
$vals['diff']['to'] = $engine->getNewid();
}
'userid',
'size',
'sha1',
+ 'contentmodel',
'comment',
'parsedcomment',
'content',
'continue' => null,
'diffto' => null,
'difftotext' => null,
+ 'contentformat' => array(
+ ApiBase::PARAM_TYPE => ContentHandler::getAllContentFormats(),
+ ApiBase::PARAM_DFLT => null
+ ),
);
}
' userid - User id of revision creator',
' size - Length (bytes) of the revision',
' sha1 - SHA-1 (base 16) of the revision',
+ ' contentmodel - Content model id',
' comment - Comment by the user for revision',
' parsedcomment - Parsed comment by the user for the revision',
' content - Text of the revision',
'difftotext' => array( 'Text to diff each revision to. Only diffs a limited number of revisions.',
"Overrides {$p}diffto. If {$p}section is set, only that section will be diffed against this text" ),
'tag' => 'Only list revisions tagged with this tag',
+ 'contentformat' => 'Serialization format used for difftotext and expected for output of content',
);
}
public function getPossibleErrors() {
return array_merge( parent::getPossibleErrors(), array(
array( 'nosuchrevid', 'diffto' ),
- array( 'code' => 'revids', 'info' => 'The revids= parameter may not be used with the list options (limit, startid, endid, dirNewer, start, end).' ),
- array( 'code' => 'multpages', 'info' => 'titles, pageids or a generator was used to supply multiple pages, but the limit, startid, endid, dirNewer, user, excludeuser, start and end parameters may only be used on a single page.' ),
+ array( 'code' => 'revids', 'info' => 'The revids= parameter may not be used with the list options '
+ . '(limit, startid, endid, dirNewer, start, end).' ),
+ array( 'code' => 'multpages', 'info' => 'titles, pageids or a generator was used to supply multiple pages, '
+ . ' but the limit, startid, endid, dirNewer, user, excludeuser, '
+ . 'start and end parameters may only be used on a single page.' ),
array( 'code' => 'diffto', 'info' => 'rvdiffto must be set to a non-negative number, "prev", "next" or "cur"' ),
array( 'code' => 'badparams', 'info' => 'start and startid cannot be used together' ),
array( 'code' => 'badparams', 'info' => 'end and endid cannot be used together' ),
array( 'code' => 'badparams', 'info' => 'user and excludeuser cannot be used together' ),
array( 'code' => 'nosuchsection', 'info' => 'There is no section section in rID' ),
+ array( 'code' => 'badformat', 'info' => 'The requested serialization format can not be applied '
+ . ' to the page\'s content model' ),
) );
}
if( $this->mParams['filesize'] > $maxSize ) {
$this->dieUsage( 'The file you submitted was too large', 'file-too-large' );
}
+ if ( !$this->mUpload->getTitle() ) {
+ $this->dieUsage( 'Invalid file title supplied', 'internal-error' );
+ }
} else {
$this->verifyUpload();
}
-
+
// Check if the user has the rights to modify or overwrite the requested title
// (This check is irrelevant if stashing is already requested, since the errors
// can always be fixed by changing the title)
$this->dieRecoverableError( $permErrors[0], 'filename' );
}
}
- // Get the result based on the current upload context:
+ // Get the result based on the current upload context:
$result = $this->getContextResult();
if ( $result['result'] === 'Success' ) {
return array();
}
- // Check we added the last chunk:
+ // Check we added the last chunk:
if( $this->mParams['offset'] + $chunkSize == $this->mParams['filesize'] ) {
$status = $this->mUpload->concatenateChunks();
$result['offset'] = $this->mParams['offset'] + $chunkSize;
return $result;
}
-
+
/**
* Stash the file and return the file key
* Also re-raises exceptions with slightly more informative message strings (useful for API)
public function incrMissesRecent( WebRequest $request ) {
global $wgMemc;
if ( mt_rand( 0, self::MISS_FACTOR - 1 ) == 0 ) {
- # Get a large IP range that should include the user even if that
+ # Get a large IP range that should include the user even if that
# person's IP address changes
$ip = $request->getIP();
if ( !IP::isValid( $ip ) ) {
* Construct an ObjectFileCache from a Title and an action
* @param $title Title|string Title object or prefixed DB key string
* @param $action string
+ * @throws MWException
* @return HTMLFileCache
*/
public static function newFromTitle( $title, $action ) {
* Get a field of a title object from cache.
* If this link is not good, it will return NULL.
* @param $title Title
- * @param $field String: ('length','redirect','revision')
+ * @param $field String: ('length','redirect','revision','model')
* @return mixed
*/
public function getGoodLinkFieldObj( $title, $field ) {
* @param $len Integer: text's length
* @param $redir Integer: whether the page is a redirect
* @param $revision Integer: latest revision's ID
+ * @param $model Integer: latest revision's content model ID
*/
- public function addGoodLinkObj( $id, $title, $len = -1, $redir = null, $revision = false ) {
+ public function addGoodLinkObj( $id, $title, $len = -1, $redir = null, $revision = false, $model = false ) {
$dbkey = $title->getPrefixedDbKey();
$this->mGoodLinks[$dbkey] = intval( $id );
$this->mGoodLinkFields[$dbkey] = array(
'length' => intval( $len ),
'redirect' => intval( $redir ),
- 'revision' => intval( $revision ) );
+ 'revision' => intval( $revision ),
+ 'model' => intval( $model ) );
}
/**
* @since 1.19
* @param $title Title
* @param $row object which has the fields page_id, page_is_redirect,
- * page_latest
+ * page_latest and page_content_model
*/
public function addGoodLinkObjFromRow( $title, $row ) {
$dbkey = $title->getPrefixedDbKey();
'length' => intval( $row->page_len ),
'redirect' => intval( $row->page_is_redirect ),
'revision' => intval( $row->page_latest ),
+ 'model' => !empty( $row->page_content_model ) ? strval( $row->page_content_model ) : null,
);
}
* @return Integer
*/
public function addLinkObj( $nt ) {
- global $wgAntiLockFlags;
+ global $wgAntiLockFlags, $wgContentHandlerUseDB;
+
wfProfileIn( __METHOD__ );
$key = $nt->getPrefixedDBkey();
$options = array();
}
- $s = $db->selectRow( 'page',
- array( 'page_id', 'page_len', 'page_is_redirect', 'page_latest' ),
+ $f = array( 'page_id', 'page_len', 'page_is_redirect', 'page_latest' );
+ if ( $wgContentHandlerUseDB ) $f[] = 'page_content_model';
+
+ $s = $db->selectRow( 'page', $f,
array( 'page_namespace' => $nt->getNamespace(), 'page_title' => $nt->getDBkey() ),
__METHOD__, $options );
# Set fields...
* @param $key String: the message cache key
* @param $useDB Boolean: get the message from the DB, false to use only
* the localisation
- * @param $langcode String: code of the language to get the message for, if
+ * @param bool|string $langcode Code of the language to get the message for, if
* it is a valid code create a language for that language,
* if it is a string but not a valid code then make a basic
* language object, if it is a false boolean then use the
* @param $isFullKey Boolean: specifies whether $key is a two part key
* "msg/lang".
*
+ * @throws MWException
* @return string|bool
*/
function get( $key, $useDB = true, $langcode = true, $isFullKey = false ) {
Title::makeTitle( NS_MEDIAWIKI, $title ), false, Revision::READ_LATEST
);
if ( $revision ) {
- $message = $revision->getText();
- if ($message === false) {
+ $content = $revision->getContent();
+ if ( !$content ) {
// A possibly temporary loading failure.
wfDebugLog( 'MessageCache', __METHOD__ . ": failed to load message page text for {$title} ($code)" );
+ $message = null; // no negative caching
} else {
- $this->mCache[$code][$title] = ' ' . $message;
- $this->mMemc->set( $titleKey, ' ' . $message, $this->mExpiry );
+ // XXX: Is this the right way to turn a Content object into a message?
+ // NOTE: $content is typically either WikitextContent, JavaScriptContent or CssContent.
+ // MessageContent is *not* used for storing messages, it's only used for wrapping them when needed.
+ $message = $content->getWikitextForTransclusion();
+
+ if ( $message === false || $message === null ) {
+ wfDebugLog( 'MessageCache', __METHOD__ . ": message content doesn't provide wikitext "
+ . "(content model: " . $content->getContentHandler() . ")" );
+
+ $message = false; // negative caching
+ } else {
+ $this->mCache[$code][$title] = ' ' . $message;
+ $this->mMemc->set( $titleKey, ' ' . $message, $this->mExpiry );
+ }
}
} else {
- $message = false;
+ $message = false; // negative caching
+ }
+
+ if ( $message === false ) { // negative caching
$this->mCache[$code][$title] = '!NONEXISTENT';
$this->mMemc->set( $titleKey, '!NONEXISTENT', $this->mExpiry );
}
return;
}
+ wfDebug( "Squid purge: " . implode( ' ', $urlArr ) . "\n" );
+
if ( $wgHTCPMulticastRouting ) {
SquidUpdate::HTCPPurge( $urlArr );
}
static function expand( $url ) {
return wfExpandUrl( $url, PROTO_INTERNAL );
}
-
+
/**
* Find the HTCP routing rule to use for a given URL.
* @param $url string URL to match
}
return false;
}
-
}
* Initialize a new child class based on a configuration array
* @param $conf Array of configuration settings, see $wgConfiguration
* for details
+ * @throws MWException
* @return Conf
*/
private static function newFromSettings( $conf ) {
/**
* Get the singleton if we don't want a specific wiki
- * @param $wiki String An id for a remote wiki
+ * @param bool|string $wiki An id for a remote wiki
+ * @throws MWException
* @return Conf child
*/
public static function load( $wiki = false ) {
*
* @param $name
* @param $value
- *
+ *
* @return bool
*/
protected function writeSetting( $name, $value ) {
--- /dev/null
+<?php
+/**
+ * A content object represents page content, e.g. the text to show on a page.
+ * Content objects have no knowledge about how they relate to Wiki pages.
+ *
+ * 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.21
+ *
+ * @file
+ * @ingroup Content
+ *
+ * @author Daniel Kinzler
+ */
+abstract class AbstractContent implements Content {
+
+ /**
+ * Name of the content model this Content object represents.
+ * Use with CONTENT_MODEL_XXX constants
+ *
+ * @since 1.21
+ *
+ * @var string $model_id
+ */
+ protected $model_id;
+
+ /**
+ * @param string|null $modelId
+ *
+ * @since 1.21
+ */
+ public function __construct( $modelId = null ) {
+ $this->model_id = $modelId;
+ }
+
+ /**
+ * @see Content::getModel
+ *
+ * @since 1.21
+ */
+ public function getModel() {
+ return $this->model_id;
+ }
+
+ /**
+ * Throws an MWException if $model_id is not the id of the content model
+ * supported by this Content object.
+ *
+ * @since 1.21
+ *
+ * @param string $modelId The model to check
+ *
+ * @throws MWException
+ */
+ protected function checkModelID( $modelId ) {
+ if ( $modelId !== $this->model_id ) {
+ throw new MWException(
+ "Bad content model: " .
+ "expected {$this->model_id} " .
+ "but got $modelId."
+ );
+ }
+ }
+
+ /**
+ * @see Content::getContentHandler
+ *
+ * @since 1.21
+ */
+ public function getContentHandler() {
+ return ContentHandler::getForContent( $this );
+ }
+
+ /**
+ * @see Content::getDefaultFormat
+ *
+ * @since 1.21
+ */
+ public function getDefaultFormat() {
+ return $this->getContentHandler()->getDefaultFormat();
+ }
+
+ /**
+ * @see Content::getSupportedFormats
+ *
+ * @since 1.21
+ */
+ public function getSupportedFormats() {
+ return $this->getContentHandler()->getSupportedFormats();
+ }
+
+ /**
+ * @see Content::isSupportedFormat
+ *
+ * @param string $format
+ *
+ * @since 1.21
+ *
+ * @return boolean
+ */
+ public function isSupportedFormat( $format ) {
+ if ( !$format ) {
+ return true; // this means "use the default"
+ }
+
+ return $this->getContentHandler()->isSupportedFormat( $format );
+ }
+
+ /**
+ * Throws an MWException if $this->isSupportedFormat( $format ) does not
+ * return true.
+ *
+ * @since 1.21
+ *
+ * @param string $format
+ * @throws MWException
+ */
+ protected function checkFormat( $format ) {
+ if ( !$this->isSupportedFormat( $format ) ) {
+ throw new MWException(
+ "Format $format is not supported for content model " .
+ $this->getModel()
+ );
+ }
+ }
+
+ /**
+ * @see Content::serialize
+ *
+ * @param string|null $format
+ *
+ * @since 1.21
+ *
+ * @return string
+ */
+ public function serialize( $format = null ) {
+ return $this->getContentHandler()->serializeContent( $this, $format );
+ }
+
+ /**
+ * @see Content::isEmpty
+ *
+ * @since 1.21
+ *
+ * @return boolean
+ */
+ public function isEmpty() {
+ return $this->getSize() === 0;
+ }
+
+ /**
+ * @see Content::isValid
+ *
+ * @since 1.21
+ *
+ * @return boolean
+ */
+ public function isValid() {
+ return true;
+ }
+
+ /**
+ * @see Content::equals
+ *
+ * @since 1.21
+ *
+ * @param Content|null $that
+ *
+ * @return boolean
+ */
+ public function equals( Content $that = null ) {
+ if ( is_null( $that ) ) {
+ return false;
+ }
+
+ if ( $that === $this ) {
+ return true;
+ }
+
+ if ( $that->getModel() !== $this->getModel() ) {
+ return false;
+ }
+
+ return $this->getNativeData() === $that->getNativeData();
+ }
+
+
+ /**
+ * Returns a list of DataUpdate objects for recording information about this
+ * Content in some secondary data store.
+ *
+ * This default implementation calls
+ * $this->getParserOutput( $content, $title, null, null, false ),
+ * and then calls getSecondaryDataUpdates( $title, $recursive ) on the
+ * resulting ParserOutput object.
+ *
+ * Subclasses may override this to determine the secondary data updates more
+ * efficiently, preferrably without the need to generate a parser output object.
+ *
+ * @see Content::getSecondaryDataUpdates()
+ *
+ * @param $title Title The context for determining the necessary updates
+ * @param $old Content|null An optional Content object representing the
+ * previous content, i.e. the content being replaced by this Content
+ * object.
+ * @param $recursive boolean Whether to include recursive updates (default:
+ * false).
+ * @param $parserOutput ParserOutput|null Optional ParserOutput object.
+ * Provide if you have one handy, to avoid re-parsing of the content.
+ *
+ * @return Array. A list of DataUpdate objects for putting information
+ * about this content object somewhere.
+ *
+ * @since 1.21
+ */
+ public function getSecondaryDataUpdates( Title $title,
+ Content $old = null,
+ $recursive = true, ParserOutput $parserOutput = null
+ ) {
+ if ( !$parserOutput ) {
+ $parserOutput = $this->getParserOutput( $title, null, null, false );
+ }
+
+ return $parserOutput->getSecondaryDataUpdates( $title, $recursive );
+ }
+
+
+ /**
+ * @see Content::getRedirectChain
+ *
+ * @since 1.21
+ */
+ public function getRedirectChain() {
+ global $wgMaxRedirects;
+ $title = $this->getRedirectTarget();
+ if ( is_null( $title ) ) {
+ return null;
+ }
+ // recursive check to follow double redirects
+ $recurse = $wgMaxRedirects;
+ $titles = array( $title );
+ while ( --$recurse > 0 ) {
+ if ( $title->isRedirect() ) {
+ $page = WikiPage::factory( $title );
+ $newtitle = $page->getRedirectTarget();
+ } else {
+ break;
+ }
+ // Redirects to some special pages are not permitted
+ if ( $newtitle instanceOf Title && $newtitle->isValidRedirectTarget() ) {
+ // The new title passes the checks, so make that our current
+ // title so that further recursion can be checked
+ $title = $newtitle;
+ $titles[] = $newtitle;
+ } else {
+ break;
+ }
+ }
+ return $titles;
+ }
+
+ /**
+ * @see Content::getRedirectTarget
+ *
+ * @since 1.21
+ */
+ public function getRedirectTarget() {
+ return null;
+ }
+
+ /**
+ * @see Content::getUltimateRedirectTarget
+ * @note: migrated here from Title::newFromRedirectRecurse
+ *
+ * @since 1.21
+ */
+ public function getUltimateRedirectTarget() {
+ $titles = $this->getRedirectChain();
+ return $titles ? array_pop( $titles ) : null;
+ }
+
+ /**
+ * @see Content::isRedirect
+ *
+ * @since 1.21
+ *
+ * @return bool
+ */
+ public function isRedirect() {
+ return $this->getRedirectTarget() !== null;
+ }
+
+ /**
+ * @see Content::updateRedirect
+ *
+ * This default implementation always returns $this.
+ *
+ * @param Title $target
+ *
+ * @since 1.21
+ *
+ * @return Content $this
+ */
+ public function updateRedirect( Title $target ) {
+ return $this;
+ }
+
+ /**
+ * @see Content::getSection
+ *
+ * @since 1.21
+ */
+ public function getSection( $sectionId ) {
+ return null;
+ }
+
+ /**
+ * @see Content::replaceSection
+ *
+ * @since 1.21
+ */
+ public function replaceSection( $section, Content $with, $sectionTitle = '' ) {
+ return null;
+ }
+
+ /**
+ * @see Content::preSaveTransform
+ *
+ * @since 1.21
+ */
+ public function preSaveTransform( Title $title, User $user, ParserOptions $popts ) {
+ return $this;
+ }
+
+ /**
+ * @see Content::addSectionHeader
+ *
+ * @since 1.21
+ */
+ public function addSectionHeader( $header ) {
+ return $this;
+ }
+
+ /**
+ * @see Content::preloadTransform
+ *
+ * @since 1.21
+ */
+ public function preloadTransform( Title $title, ParserOptions $popts ) {
+ return $this;
+ }
+
+ /**
+ * @see Content::prepareSave
+ *
+ * @since 1.21
+ */
+ public function prepareSave( WikiPage $page, $flags, $baseRevId, User $user ) {
+ if ( $this->isValid() ) {
+ return Status::newGood();
+ } else {
+ return Status::newFatal( "invalid-content-data" );
+ }
+ }
+
+ /**
+ * @see Content::getDeletionUpdates
+ *
+ * @since 1.21
+ *
+ * @param $page \WikiPage the deleted page
+ * @param $parserOutput null|\ParserOutput optional parser output object
+ * for efficient access to meta-information about the content object.
+ * Provide if you have one handy.
+ *
+ * @return array A list of DataUpdate instances that will clean up the
+ * database after deletion.
+ */
+ public function getDeletionUpdates( WikiPage $page,
+ ParserOutput $parserOutput = null )
+ {
+ return array(
+ new LinksDeletionUpdate( $page ),
+ );
+ }
+
+ /**
+ * This default implementation always returns false. Subclasses may override this to supply matching logic.
+ *
+ * @see Content::matchMagicWord
+ *
+ * @since 1.21
+ *
+ * @param MagicWord $word
+ *
+ * @return bool
+ */
+ public function matchMagicWord( MagicWord $word ) {
+ return false;
+ }
+}
--- /dev/null
+<?php
+/**
+ * A content object represents page content, e.g. the text to show on a page.
+ * Content objects have no knowledge about how they relate to wiki pages.
+ *
+ * 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.21
+ *
+ * @file
+ * @ingroup Content
+ *
+ * @author Daniel Kinzler
+ */
+interface Content {
+
+ /**
+ * @since 1.21
+ *
+ * @return string A string representing the content in a way useful for
+ * building a full text search index. If no useful representation exists,
+ * this method returns an empty string.
+ *
+ * @todo: test that this actually works
+ * @todo: make sure this also works with LuceneSearch / WikiSearch
+ */
+ public function getTextForSearchIndex();
+
+ /**
+ * @since 1.21
+ *
+ * @return string The wikitext to include when another page includes this
+ * content, or false if the content is not includable in a wikitext page.
+ *
+ * @TODO: allow native handling, bypassing wikitext representation, like
+ * for includable special pages.
+ * @TODO: allow transclusion into other content models than Wikitext!
+ * @TODO: used in WikiPage and MessageCache to get message text. Not so
+ * nice. What should we use instead?!
+ */
+ public function getWikitextForTransclusion();
+
+ /**
+ * Returns a textual representation of the content suitable for use in edit
+ * summaries and log messages.
+ *
+ * @since 1.21
+ *
+ * @param $maxLength int Maximum length of the summary text
+ * @return string The summary text
+ */
+ public function getTextForSummary( $maxLength = 250 );
+
+ /**
+ * Returns native representation of the data. Interpretation depends on
+ * the data model used, as given by getDataModel().
+ *
+ * @since 1.21
+ *
+ * @return mixed The native representation of the content. Could be a
+ * string, a nested array structure, an object, a binary blob...
+ * anything, really.
+ *
+ * @NOTE: Caller must be aware of content model!
+ */
+ public function getNativeData();
+
+ /**
+ * Returns the content's nominal size in bogo-bytes.
+ *
+ * @return int
+ */
+ public function getSize();
+
+ /**
+ * Returns the ID of the content model used by this Content object.
+ * Corresponds to the CONTENT_MODEL_XXX constants.
+ *
+ * @since 1.21
+ *
+ * @return String The model id
+ */
+ public function getModel();
+
+ /**
+ * Convenience method that returns the ContentHandler singleton for handling
+ * the content model that this Content object uses.
+ *
+ * Shorthand for ContentHandler::getForContent( $this )
+ *
+ * @since 1.21
+ *
+ * @return ContentHandler
+ */
+ public function getContentHandler();
+
+ /**
+ * Convenience method that returns the default serialization format for the
+ * content model that this Content object uses.
+ *
+ * Shorthand for $this->getContentHandler()->getDefaultFormat()
+ *
+ * @since 1.21
+ *
+ * @return String
+ */
+ public function getDefaultFormat();
+
+ /**
+ * Convenience method that returns the list of serialization formats
+ * supported for the content model that this Content object uses.
+ *
+ * Shorthand for $this->getContentHandler()->getSupportedFormats()
+ *
+ * @since 1.21
+ *
+ * @return Array of supported serialization formats
+ */
+ public function getSupportedFormats();
+
+ /**
+ * Returns true if $format is a supported serialization format for this
+ * Content object, false if it isn't.
+ *
+ * Note that this should always return true if $format is null, because null
+ * stands for the default serialization.
+ *
+ * Shorthand for $this->getContentHandler()->isSupportedFormat( $format )
+ *
+ * @since 1.21
+ *
+ * @param $format string The format to check
+ * @return bool Whether the format is supported
+ */
+ public function isSupportedFormat( $format );
+
+ /**
+ * Convenience method for serializing this Content object.
+ *
+ * Shorthand for $this->getContentHandler()->serializeContent( $this, $format )
+ *
+ * @since 1.21
+ *
+ * @param $format null|string The desired serialization format (or null for
+ * the default format).
+ * @return string Serialized form of this Content object
+ */
+ public function serialize( $format = null );
+
+ /**
+ * Returns true if this Content object represents empty content.
+ *
+ * @since 1.21
+ *
+ * @return bool Whether this Content object is empty
+ */
+ public function isEmpty();
+
+ /**
+ * Returns whether the content is valid. This is intended for local validity
+ * checks, not considering global consistency.
+ *
+ * Content needs to be valid before it can be saved.
+ *
+ * This default implementation always returns true.
+ *
+ * @since 1.21
+ *
+ * @return boolean
+ */
+ public function isValid();
+
+ /**
+ * Returns true if this Content objects is conceptually equivalent to the
+ * given Content object.
+ *
+ * Contract:
+ *
+ * - Will return false if $that is null.
+ * - Will return true if $that === $this.
+ * - Will return false if $that->getModel() != $this->getModel().
+ * - Will return false if $that->getNativeData() is not equal to $this->getNativeData(),
+ * where the meaning of "equal" depends on the actual data model.
+ *
+ * Implementations should be careful to make equals() transitive and reflexive:
+ *
+ * - $a->equals( $b ) <=> $b->equals( $a )
+ * - $a->equals( $b ) && $b->equals( $c ) ==> $a->equals( $c )
+ *
+ * @since 1.21
+ *
+ * @param $that Content The Content object to compare to
+ * @return bool True if this Content object is equal to $that, false otherwise.
+ */
+ public function equals( Content $that = null );
+
+ /**
+ * Return a copy of this Content object. The following must be true for the
+ * object returned:
+ *
+ * if $copy = $original->copy()
+ *
+ * - get_class($original) === get_class($copy)
+ * - $original->getModel() === $copy->getModel()
+ * - $original->equals( $copy )
+ *
+ * If and only if the Content object is immutable, the copy() method can and
+ * should return $this. That is, $copy === $original may be true, but only
+ * for immutable content objects.
+ *
+ * @since 1.21
+ *
+ * @return Content. A copy of this object
+ */
+ public function copy( );
+
+ /**
+ * Returns true if this content is countable as a "real" wiki page, provided
+ * that it's also in a countable location (e.g. a current revision in the
+ * main namespace).
+ *
+ * @since 1.21
+ *
+ * @param $hasLinks Bool: If it is known whether this content contains
+ * links, provide this information here, to avoid redundant parsing to
+ * find out.
+ * @return boolean
+ */
+ public function isCountable( $hasLinks = null );
+
+
+ /**
+ * Parse the Content object and generate a ParserOutput from the result.
+ * $result->getText() can be used to obtain the generated HTML. If no HTML
+ * is needed, $generateHtml can be set to false; in that case,
+ * $result->getText() may return null.
+ *
+ * @param $title Title The page title to use as a context for rendering
+ * @param $revId null|int The revision being rendered (optional)
+ * @param $options null|ParserOptions Any parser options
+ * @param $generateHtml Boolean Whether to generate HTML (default: true). If false,
+ * the result of calling getText() on the ParserOutput object returned by
+ * this method is undefined.
+ *
+ * @since 1.21
+ *
+ * @return ParserOutput
+ */
+ public function getParserOutput( Title $title,
+ $revId = null,
+ ParserOptions $options = null, $generateHtml = true );
+ # TODO: make RenderOutput and RenderOptions base classes
+
+ /**
+ * Returns a list of DataUpdate objects for recording information about this
+ * Content in some secondary data store. If the optional second argument,
+ * $old, is given, the updates may model only the changes that need to be
+ * made to replace information about the old content with information about
+ * the new content.
+ *
+ * This default implementation calls
+ * $this->getParserOutput( $content, $title, null, null, false ),
+ * and then calls getSecondaryDataUpdates( $title, $recursive ) on the
+ * resulting ParserOutput object.
+ *
+ * Subclasses may implement this to determine the necessary updates more
+ * efficiently, or make use of information about the old content.
+ *
+ * @param $title Title The context for determining the necessary updates
+ * @param $old Content|null An optional Content object representing the
+ * previous content, i.e. the content being replaced by this Content
+ * object.
+ * @param $recursive boolean Whether to include recursive updates (default:
+ * false).
+ * @param $parserOutput ParserOutput|null Optional ParserOutput object.
+ * Provide if you have one handy, to avoid re-parsing of the content.
+ *
+ * @return Array. A list of DataUpdate objects for putting information
+ * about this content object somewhere.
+ *
+ * @since 1.21
+ */
+ public function getSecondaryDataUpdates( Title $title,
+ Content $old = null,
+ $recursive = true, ParserOutput $parserOutput = null
+ );
+
+ /**
+ * Construct the redirect destination from this content and return an
+ * array of Titles, or null if this content doesn't represent a redirect.
+ * The last element in the array is the final destination after all redirects
+ * have been resolved (up to $wgMaxRedirects times).
+ *
+ * @since 1.21
+ *
+ * @return Array of Titles, with the destination last
+ */
+ public function getRedirectChain();
+
+ /**
+ * Construct the redirect destination from this content and return a Title,
+ * or null if this content doesn't represent a redirect.
+ * This will only return the immediate redirect target, useful for
+ * the redirect table and other checks that don't need full recursion.
+ *
+ * @since 1.21
+ *
+ * @return Title: The corresponding Title
+ */
+ public function getRedirectTarget();
+
+ /**
+ * Construct the redirect destination from this content and return the
+ * Title, or null if this content doesn't represent a redirect.
+ *
+ * This will recurse down $wgMaxRedirects times or until a non-redirect
+ * target is hit in order to provide (hopefully) the Title of the final
+ * destination instead of another redirect.
+ *
+ * There is usually no need to override the default behaviour, subclasses that
+ * want to implement redirects should override getRedirectTarget().
+ *
+ * @since 1.21
+ *
+ * @return Title
+ */
+ public function getUltimateRedirectTarget();
+
+ /**
+ * Returns whether this Content represents a redirect.
+ * Shorthand for getRedirectTarget() !== null.
+ *
+ * @since 1.21
+ *
+ * @return bool
+ */
+ public function isRedirect();
+
+ /**
+ * If this Content object is a redirect, this method updates the redirect target.
+ * Otherwise, it does nothing.
+ *
+ * @since 1.21
+ *
+ * @param Title $target the new redirect target
+ *
+ * @return Content a new Content object with the updated redirect (or $this if this Content object isn't a redirect)
+ */
+ public function updateRedirect( Title $target );
+
+ /**
+ * Returns the section with the given ID.
+ *
+ * @since 1.21
+ *
+ * @param $sectionId string The section's ID, given as a numeric string.
+ * The ID "0" retrieves the section before the first heading, "1" the
+ * text between the first heading (included) and the second heading
+ * (excluded), etc.
+ * @return Content|Boolean|null The section, or false if no such section
+ * exist, or null if sections are not supported.
+ */
+ public function getSection( $sectionId );
+
+ /**
+ * Replaces a section of the content and returns a Content object with the
+ * section replaced.
+ *
+ * @since 1.21
+ *
+ * @param $section Empty/null/false or a section number (0, 1, 2, T1, T2...), or "new"
+ * @param $with Content: new content of the section
+ * @param $sectionTitle String: new section's subject, only if $section is 'new'
+ * @return string Complete article text, or null if error
+ */
+ public function replaceSection( $section, Content $with, $sectionTitle = '' );
+
+ /**
+ * Returns a Content object with pre-save transformations applied (or this
+ * object if no transformations apply).
+ *
+ * @since 1.21
+ *
+ * @param $title Title
+ * @param $user User
+ * @param $popts null|ParserOptions
+ * @return Content
+ */
+ public function preSaveTransform( Title $title, User $user, ParserOptions $popts );
+
+ /**
+ * Returns a new WikitextContent object with the given section heading
+ * prepended, if supported. The default implementation just returns this
+ * Content object unmodified, ignoring the section header.
+ *
+ * @since 1.21
+ *
+ * @param $header string
+ * @return Content
+ */
+ public function addSectionHeader( $header );
+
+ /**
+ * Returns a Content object with preload transformations applied (or this
+ * object if no transformations apply).
+ *
+ * @since 1.21
+ *
+ * @param $title Title
+ * @param $popts null|ParserOptions
+ * @return Content
+ */
+ public function preloadTransform( Title $title, ParserOptions $popts );
+
+ /**
+ * Prepare Content for saving. Called before Content is saved by WikiPage::doEditContent() and in
+ * similar places.
+ *
+ * This may be used to check the content's consistency with global state. This function should
+ * NOT write any information to the database.
+ *
+ * Note that this method will usually be called inside the same transaction bracket that will be used
+ * to save the new revision.
+ *
+ * Note that this method is called before any update to the page table is performed. This means that
+ * $page may not yet know a page ID.
+ *
+ * @since 1.21
+ *
+ * @param WikiPage $page The page to be saved.
+ * @param int $flags bitfield for use with EDIT_XXX constants, see WikiPage::doEditContent()
+ * @param int $baseRevId the ID of the current revision
+ * @param User $user
+ *
+ * @return Status A status object indicating whether the content was successfully prepared for saving.
+ * If the returned status indicates an error, a rollback will be performed and the
+ * transaction aborted.
+ *
+ * @see see WikiPage::doEditContent()
+ */
+ public function prepareSave( WikiPage $page, $flags, $baseRevId, User $user );
+
+ /**
+ * Returns a list of updates to perform when this content is deleted.
+ * The necessary updates may be taken from the Content object, or depend on
+ * the current state of the database.
+ *
+ * @since 1.21
+ *
+ * @param $page \WikiPage the deleted page
+ * @param $parserOutput null|\ParserOutput optional parser output object
+ * for efficient access to meta-information about the content object.
+ * Provide if you have one handy.
+ *
+ * @return array A list of DataUpdate instances that will clean up the
+ * database after deletion.
+ */
+ public function getDeletionUpdates( WikiPage $page,
+ ParserOutput $parserOutput = null );
+
+ /**
+ * Returns true if this Content object matches the given magic word.
+ *
+ * @since 1.21
+ *
+ * @param MagicWord $word the magic word to match
+ *
+ * @return bool whether this Content object matches the given magic word.
+ */
+ public function matchMagicWord( MagicWord $word );
+
+ # TODO: ImagePage and CategoryPage interfere with per-content action handlers
+ # TODO: nice&sane integration of GeSHi syntax highlighting
+ # [11:59] <vvv> Hooks are ugly; make CodeHighlighter interface and a
+ # config to set the class which handles syntax highlighting
+ # [12:00] <vvv> And default it to a DummyHighlighter
+}
--- /dev/null
+<?php
+
+/**
+ * Exception representing a failure to serialize or unserialize a content object.
+ */
+class MWContentSerializationException extends MWException {
+
+}
+
+/**
+ * A content handler knows how do deal with a specific type of content on a wiki
+ * page. Content is stored in the database in a serialized form (using a
+ * serialization format a.k.a. MIME type) and is unserialized into its native
+ * PHP representation (the content model), which is wrapped in an instance of
+ * the appropriate subclass of Content.
+ *
+ * ContentHandler instances are stateless singletons that serve, among other
+ * things, as a factory for Content objects. Generally, there is one subclass
+ * of ContentHandler and one subclass of Content for every type of content model.
+ *
+ * Some content types have a flat model, that is, their native representation
+ * is the same as their serialized form. Examples would be JavaScript and CSS
+ * code. As of now, this also applies to wikitext (MediaWiki's default content
+ * type), but wikitext content may be represented by a DOM or AST structure in
+ * the future.
+ *
+ * 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.21
+ *
+ * @file
+ * @ingroup Content
+ *
+ * @author Daniel Kinzler
+ */
+abstract class ContentHandler {
+
+ /**
+ * Switch for enabling deprecation warnings. Used by ContentHandler::deprecated()
+ * and ContentHandler::runLegacyHooks().
+ *
+ * Once the ContentHandler code has settled in a bit, this should be set to true to
+ * make extensions etc. show warnings when using deprecated functions and hooks.
+ */
+ protected static $enableDeprecationWarnings = false;
+
+ /**
+ * Convenience function for getting flat text from a Content object. This
+ * should only be used in the context of backwards compatibility with code
+ * that is not yet able to handle Content objects!
+ *
+ * If $content is null, this method returns the empty string.
+ *
+ * If $content is an instance of TextContent, this method returns the flat
+ * text as returned by $content->getNativeData().
+ *
+ * If $content is not a TextContent object, the behavior of this method
+ * depends on the global $wgContentHandlerTextFallback:
+ * - If $wgContentHandlerTextFallback is 'fail' and $content is not a
+ * TextContent object, an MWException is thrown.
+ * - If $wgContentHandlerTextFallback is 'serialize' and $content is not a
+ * TextContent object, $content->serialize() is called to get a string
+ * form of the content.
+ * - If $wgContentHandlerTextFallback is 'ignore' and $content is not a
+ * TextContent object, this method returns null.
+ * - otherwise, the behaviour is undefined.
+ *
+ * @since 1.21
+ *
+ * @static
+ * @param $content Content|null
+ * @return null|string the textual form of $content, if available
+ * @throws MWException if $content is not an instance of TextContent and
+ * $wgContentHandlerTextFallback was set to 'fail'.
+ */
+ public static function getContentText( Content $content = null ) {
+ global $wgContentHandlerTextFallback;
+
+ if ( is_null( $content ) ) {
+ return '';
+ }
+
+ if ( $content instanceof TextContent ) {
+ return $content->getNativeData();
+ }
+
+ if ( $wgContentHandlerTextFallback == 'fail' ) {
+ throw new MWException(
+ "Attempt to get text from Content with model " .
+ $content->getModel()
+ );
+ }
+
+ if ( $wgContentHandlerTextFallback == 'serialize' ) {
+ return $content->serialize();
+ }
+
+ return null;
+ }
+
+ /**
+ * Convenience function for creating a Content object from a given textual
+ * representation.
+ *
+ * $text will be deserialized into a Content object of the model specified
+ * by $modelId (or, if that is not given, $title->getContentModel()) using
+ * the given format.
+ *
+ * @since 1.21
+ *
+ * @static
+ *
+ * @param $text string the textual representation, will be
+ * unserialized to create the Content object
+ * @param $title null|Title the title of the page this text belongs to.
+ * Required if $modelId is not provided.
+ * @param $modelId null|string the model to deserialize to. If not provided,
+ * $title->getContentModel() is used.
+ * @param $format null|string the format to use for deserialization. If not
+ * given, the model's default format is used.
+ *
+ * @return Content a Content object representing $text
+ *
+ * @throw MWException if $model or $format is not supported or if $text can
+ * not be unserialized using $format.
+ */
+ public static function makeContent( $text, Title $title = null,
+ $modelId = null, $format = null )
+ {
+ if ( is_null( $modelId ) ) {
+ if ( is_null( $title ) ) {
+ throw new MWException( "Must provide a Title object or a content model ID." );
+ }
+
+ $modelId = $title->getContentModel();
+ }
+
+ $handler = ContentHandler::getForModelID( $modelId );
+ return $handler->unserializeContent( $text, $format );
+ }
+
+ /**
+ * Returns the name of the default content model to be used for the page
+ * with the given title.
+ *
+ * Note: There should rarely be need to call this method directly.
+ * To determine the actual content model for a given page, use
+ * Title::getContentModel().
+ *
+ * Which model is to be used by default for the page is determined based
+ * on several factors:
+ * - The global setting $wgNamespaceContentModels specifies a content model
+ * per namespace.
+ * - The hook ContentHandlerDefaultModelFor may be used to override the page's default
+ * model.
+ * - Pages in NS_MEDIAWIKI and NS_USER default to the CSS or JavaScript
+ * model if they end in .js or .css, respectively.
+ * - Pages in NS_MEDIAWIKI default to the wikitext model otherwise.
+ * - The hook TitleIsCssOrJsPage may be used to force a page to use the CSS
+ * or JavaScript model. This is a compatibility feature. The ContentHandlerDefaultModelFor
+ * hook should be used instead if possible.
+ * - The hook TitleIsWikitextPage may be used to force a page to use the
+ * wikitext model. This is a compatibility feature. The ContentHandlerDefaultModelFor
+ * hook should be used instead if possible.
+ *
+ * If none of the above applies, the wikitext model is used.
+ *
+ * Note: this is used by, and may thus not use, Title::getContentModel()
+ *
+ * @since 1.21
+ *
+ * @static
+ * @param $title Title
+ * @return null|string default model name for the page given by $title
+ */
+ public static function getDefaultModelFor( Title $title ) {
+ global $wgNamespaceContentModels;
+
+ // NOTE: this method must not rely on $title->getContentModel() directly or indirectly,
+ // because it is used to initialize the mContentModel member.
+
+ $ns = $title->getNamespace();
+
+ $ext = false;
+ $m = null;
+ $model = null;
+
+ if ( !empty( $wgNamespaceContentModels[ $ns ] ) ) {
+ $model = $wgNamespaceContentModels[ $ns ];
+ }
+
+ // Hook can determine default model
+ if ( !wfRunHooks( 'ContentHandlerDefaultModelFor', array( $title, &$model ) ) ) {
+ if ( !is_null( $model ) ) {
+ return $model;
+ }
+ }
+
+ // Could this page contain custom CSS or JavaScript, based on the title?
+ $isCssOrJsPage = NS_MEDIAWIKI == $ns && preg_match( '!\.(css|js)$!u', $title->getText(), $m );
+ if ( $isCssOrJsPage ) {
+ $ext = $m[1];
+ }
+
+ // Hook can force JS/CSS
+ wfRunHooks( 'TitleIsCssOrJsPage', array( $title, &$isCssOrJsPage ) );
+
+ // Is this a .css subpage of a user page?
+ $isJsCssSubpage = NS_USER == $ns
+ && !$isCssOrJsPage
+ && preg_match( "/\\/.*\\.(js|css)$/", $title->getText(), $m );
+ if ( $isJsCssSubpage ) {
+ $ext = $m[1];
+ }
+
+ // Is this wikitext, according to $wgNamespaceContentModels or the DefaultModelFor hook?
+ $isWikitext = is_null( $model ) || $model == CONTENT_MODEL_WIKITEXT;
+ $isWikitext = $isWikitext && !$isCssOrJsPage && !$isJsCssSubpage;
+
+ // Hook can override $isWikitext
+ wfRunHooks( 'TitleIsWikitextPage', array( $title, &$isWikitext ) );
+
+ if ( !$isWikitext ) {
+ switch ( $ext ) {
+ case 'js':
+ return CONTENT_MODEL_JAVASCRIPT;
+ case 'css':
+ return CONTENT_MODEL_CSS;
+ default:
+ return is_null( $model ) ? CONTENT_MODEL_TEXT : $model;
+ }
+ }
+
+ // We established that it must be wikitext
+
+ return CONTENT_MODEL_WIKITEXT;
+ }
+
+ /**
+ * Returns the appropriate ContentHandler singleton for the given title.
+ *
+ * @since 1.21
+ *
+ * @static
+ * @param $title Title
+ * @return ContentHandler
+ */
+ public static function getForTitle( Title $title ) {
+ $modelId = $title->getContentModel();
+ return ContentHandler::getForModelID( $modelId );
+ }
+
+ /**
+ * Returns the appropriate ContentHandler singleton for the given Content
+ * object.
+ *
+ * @since 1.21
+ *
+ * @static
+ * @param $content Content
+ * @return ContentHandler
+ */
+ public static function getForContent( Content $content ) {
+ $modelId = $content->getModel();
+ return ContentHandler::getForModelID( $modelId );
+ }
+
+ /**
+ * @var Array A Cache of ContentHandler instances by model id
+ */
+ static $handlers;
+
+ /**
+ * Returns the ContentHandler singleton for the given model ID. Use the
+ * CONTENT_MODEL_XXX constants to identify the desired content model.
+ *
+ * ContentHandler singletons are taken from the global $wgContentHandlers
+ * array. Keys in that array are model names, the values are either
+ * ContentHandler singleton objects, or strings specifying the appropriate
+ * subclass of ContentHandler.
+ *
+ * If a class name is encountered when looking up the singleton for a given
+ * model name, the class is instantiated and the class name is replaced by
+ * the resulting singleton in $wgContentHandlers.
+ *
+ * If no ContentHandler is defined for the desired $modelId, the
+ * ContentHandler may be provided by the ContentHandlerForModelID hook.
+ * If no ContentHandler can be determined, an MWException is raised.
+ *
+ * @since 1.21
+ *
+ * @static
+ * @param $modelId String The ID of the content model for which to get a
+ * handler. Use CONTENT_MODEL_XXX constants.
+ * @return ContentHandler The ContentHandler singleton for handling the
+ * model given by $modelId
+ * @throws MWException if no handler is known for $modelId.
+ */
+ public static function getForModelID( $modelId ) {
+ global $wgContentHandlers;
+
+ if ( isset( ContentHandler::$handlers[$modelId] ) ) {
+ return ContentHandler::$handlers[$modelId];
+ }
+
+ if ( empty( $wgContentHandlers[$modelId] ) ) {
+ $handler = null;
+
+ wfRunHooks( 'ContentHandlerForModelID', array( $modelId, &$handler ) );
+
+ if ( $handler === null ) {
+ throw new MWException( "No handler for model '$modelId'' registered in \$wgContentHandlers" );
+ }
+
+ if ( !( $handler instanceof ContentHandler ) ) {
+ throw new MWException( "ContentHandlerForModelID must supply a ContentHandler instance" );
+ }
+ } else {
+ $class = $wgContentHandlers[$modelId];
+ $handler = new $class( $modelId );
+
+ if ( !( $handler instanceof ContentHandler ) ) {
+ throw new MWException( "$class from \$wgContentHandlers is not compatible with ContentHandler" );
+ }
+ }
+
+ ContentHandler::$handlers[$modelId] = $handler;
+ return ContentHandler::$handlers[$modelId];
+ }
+
+ /**
+ * Returns the localized name for a given content model.
+ *
+ * Model names are localized using system messages. Message keys
+ * have the form content-model-$name, where $name is getContentModelName( $id ).
+ *
+ * @static
+ * @param $name String The content model ID, as given by a CONTENT_MODEL_XXX
+ * constant or returned by Revision::getContentModel().
+ *
+ * @return string The content format's localized name.
+ * @throws MWException if the model id isn't known.
+ */
+ public static function getLocalizedName( $name ) {
+ $key = "content-model-$name";
+
+ $msg = wfMessage( $key );
+
+ return $msg->exists() ? $msg->plain() : $name;
+ }
+
+ public static function getContentModels() {
+ global $wgContentHandlers;
+
+ return array_keys( $wgContentHandlers );
+ }
+
+ public static function getAllContentFormats() {
+ global $wgContentHandlers;
+
+ $formats = array();
+
+ foreach ( $wgContentHandlers as $model => $class ) {
+ $handler = ContentHandler::getForModelID( $model );
+ $formats = array_merge( $formats, $handler->getSupportedFormats() );
+ }
+
+ $formats = array_unique( $formats );
+ return $formats;
+ }
+
+ // ------------------------------------------------------------------------
+
+ protected $mModelID;
+ protected $mSupportedFormats;
+
+ /**
+ * Constructor, initializing the ContentHandler instance with its model ID
+ * and a list of supported formats. Values for the parameters are typically
+ * provided as literals by subclass's constructors.
+ *
+ * @param $modelId String (use CONTENT_MODEL_XXX constants).
+ * @param $formats array List for supported serialization formats
+ * (typically as MIME types)
+ */
+ public function __construct( $modelId, $formats ) {
+ $this->mModelID = $modelId;
+ $this->mSupportedFormats = $formats;
+
+ $this->mModelName = preg_replace( '/(Content)?Handler$/', '', get_class( $this ) );
+ $this->mModelName = preg_replace( '/[_\\\\]/', '', $this->mModelName );
+ $this->mModelName = strtolower( $this->mModelName );
+ }
+
+ /**
+ * Serializes a Content object of the type supported by this ContentHandler.
+ *
+ * @since 1.21
+ *
+ * @abstract
+ * @param $content Content The Content object to serialize
+ * @param $format null|String The desired serialization format
+ * @return string Serialized form of the content
+ */
+ public abstract function serializeContent( Content $content, $format = null );
+
+ /**
+ * Unserializes a Content object of the type supported by this ContentHandler.
+ *
+ * @since 1.21
+ *
+ * @abstract
+ * @param $blob string serialized form of the content
+ * @param $format null|String the format used for serialization
+ * @return Content the Content object created by deserializing $blob
+ */
+ public abstract function unserializeContent( $blob, $format = null );
+
+ /**
+ * Creates an empty Content object of the type supported by this
+ * ContentHandler.
+ *
+ * @since 1.21
+ *
+ * @return Content
+ */
+ public abstract function makeEmptyContent();
+
+ /**
+ * Creates a new Content object that acts as a redirect to the given page,
+ * or null of redirects are not supported by this content model.
+ *
+ * This default implementation always returns null. Subclasses supporting redirects
+ * must override this method.
+ *
+ * @since 1.21
+ *
+ * @param Title $destination the page to redirect to.
+ *
+ * @return Content
+ */
+ public function makeRedirectContent( Title $destination ) {
+ return null;
+ }
+
+ /**
+ * Returns the model id that identifies the content model this
+ * ContentHandler can handle. Use with the CONTENT_MODEL_XXX constants.
+ *
+ * @since 1.21
+ *
+ * @return String The model ID
+ */
+ public function getModelID() {
+ return $this->mModelID;
+ }
+
+ /**
+ * Throws an MWException if $model_id is not the ID of the content model
+ * supported by this ContentHandler.
+ *
+ * @since 1.21
+ *
+ * @param String $model_id The model to check
+ *
+ * @throws MWException
+ */
+ protected function checkModelID( $model_id ) {
+ if ( $model_id !== $this->mModelID ) {
+ throw new MWException( "Bad content model: " .
+ "expected {$this->mModelID} " .
+ "but got $model_id." );
+ }
+ }
+
+ /**
+ * Returns a list of serialization formats supported by the
+ * serializeContent() and unserializeContent() methods of this
+ * ContentHandler.
+ *
+ * @since 1.21
+ *
+ * @return array of serialization formats as MIME type like strings
+ */
+ public function getSupportedFormats() {
+ return $this->mSupportedFormats;
+ }
+
+ /**
+ * The format used for serialization/deserialization by default by this
+ * ContentHandler.
+ *
+ * This default implementation will return the first element of the array
+ * of formats that was passed to the constructor.
+ *
+ * @since 1.21
+ *
+ * @return string the name of the default serialization format as a MIME type
+ */
+ public function getDefaultFormat() {
+ return $this->mSupportedFormats[0];
+ }
+
+ /**
+ * Returns true if $format is a serialization format supported by this
+ * ContentHandler, and false otherwise.
+ *
+ * Note that if $format is null, this method always returns true, because
+ * null means "use the default format".
+ *
+ * @since 1.21
+ *
+ * @param $format string the serialization format to check
+ * @return bool
+ */
+ public function isSupportedFormat( $format ) {
+
+ if ( !$format ) {
+ return true; // this means "use the default"
+ }
+
+ return in_array( $format, $this->mSupportedFormats );
+ }
+
+ /**
+ * Throws an MWException if isSupportedFormat( $format ) is not true.
+ * Convenient for checking whether a format provided as a parameter is
+ * actually supported.
+ *
+ * @param $format string the serialization format to check
+ *
+ * @throws MWException
+ */
+ protected function checkFormat( $format ) {
+ if ( !$this->isSupportedFormat( $format ) ) {
+ throw new MWException(
+ "Format $format is not supported for content model "
+ . $this->getModelID()
+ );
+ }
+ }
+
+ /**
+ * Returns overrides for action handlers.
+ * Classes listed here will be used instead of the default one when
+ * (and only when) $wgActions[$action] === true. This allows subclasses
+ * to override the default action handlers.
+ *
+ * @since 1.21
+ *
+ * @return Array
+ */
+ public function getActionOverrides() {
+ return array();
+ }
+
+ /**
+ * Factory for creating an appropriate DifferenceEngine for this content model.
+ *
+ * @since 1.21
+ *
+ * @param $context IContextSource context to use, anything else will be
+ * ignored
+ * @param $old Integer Old ID we want to show and diff with.
+ * @param $new int|string String either 'prev' or 'next'.
+ * @param $rcid Integer ??? FIXME (default 0)
+ * @param $refreshCache boolean If set, refreshes the diff cache
+ * @param $unhide boolean If set, allow viewing deleted revs
+ *
+ * @return DifferenceEngine
+ */
+ public function createDifferenceEngine( IContextSource $context,
+ $old = 0, $new = 0,
+ $rcid = 0, # FIXME: use everywhere!
+ $refreshCache = false, $unhide = false
+ ) {
+ $this->checkModelID( $context->getTitle()->getContentModel() );
+
+ $diffEngineClass = $this->getDiffEngineClass();
+
+ return new $diffEngineClass( $context, $old, $new, $rcid, $refreshCache, $unhide );
+ }
+
+ /**
+ * Get the language in which the content of the given page is written.
+ *
+ * This default implementation just returns $wgContLang (except for pages in the MediaWiki namespace)
+ *
+ * Note that the pages language is not cacheable, since it may in some cases depend on user settings.
+ *
+ * Also note that the page language may or may not depend on the actual content of the page,
+ * that is, this method may load the content in order to determine the language.
+ *
+ * @since 1.21
+ *
+ * @param Title $title the page to determine the language for.
+ * @param Content|null $content the page's content, if you have it handy, to avoid reloading it.
+ *
+ * @return Language the page's language
+ */
+ public function getPageLanguage( Title $title, Content $content = null ) {
+ global $wgContLang;
+
+ if ( $title->getNamespace() == NS_MEDIAWIKI ) {
+ // Parse mediawiki messages with correct target language
+ list( /* $unused */, $lang ) = MessageCache::singleton()->figureMessage( $title->getText() );
+ return wfGetLangObj( $lang );
+ }
+
+ return $wgContLang;
+ }
+
+ /**
+ * Get the language in which the content of this page is written when
+ * viewed by user. Defaults to $this->getPageLanguage(), but if the user
+ * specified a preferred variant, the variant will be used.
+ *
+ * This default implementation just returns $this->getPageLanguage( $title, $content ) unless
+ * the user specified a preferred variant.
+ *
+ * Note that the pages view language is not cacheable, since it depends on user settings.
+ *
+ * Also note that the page language may or may not depend on the actual content of the page,
+ * that is, this method may load the content in order to determine the language.
+ *
+ * @since 1.21
+ *
+ * @param Title $title the page to determine the language for.
+ * @param Content|null $content the page's content, if you have it handy, to avoid reloading it.
+ *
+ * @return Language the page's language for viewing
+ */
+ public function getPageViewLanguage( Title $title, Content $content = null ) {
+ $pageLang = $this->getPageLanguage( $title, $content );
+
+ if ( $title->getNamespace() !== NS_MEDIAWIKI ) {
+ // If the user chooses a variant, the content is actually
+ // in a language whose code is the variant code.
+ $variant = $pageLang->getPreferredVariant();
+ if ( $pageLang->getCode() !== $variant ) {
+ $pageLang = Language::factory( $variant );
+ }
+ }
+
+ return $pageLang;
+ }
+
+ /**
+ * Determines whether the content type handled by this ContentHandler
+ * can be used on the given page.
+ *
+ * This default implementation always returns true.
+ * Subclasses may override this to restrict the use of this content model to specific locations,
+ * typically based on the namespace or some other aspect of the title, such as a special suffix
+ * (e.g. ".svg" for SVG content).
+ *
+ * @param Title $title the page's title.
+ *
+ * @return bool true if content of this kind can be used on the given page, false otherwise.
+ */
+ public function canBeUsedOn( Title $title ) {
+ return true;
+ }
+
+ /**
+ * Returns the name of the diff engine to use.
+ *
+ * @since 1.21
+ *
+ * @return string
+ */
+ protected function getDiffEngineClass() {
+ return 'DifferenceEngine';
+ }
+
+ /**
+ * Attempts to merge differences between three versions.
+ * Returns a new Content object for a clean merge and false for failure or
+ * a conflict.
+ *
+ * This default implementation always returns false.
+ *
+ * @since 1.21
+ *
+ * @param $oldContent Content|string String
+ * @param $myContent Content|string String
+ * @param $yourContent Content|string String
+ *
+ * @return Content|Bool
+ */
+ public function merge3( Content $oldContent, Content $myContent, Content $yourContent ) {
+ return false;
+ }
+
+ /**
+ * Return an applicable auto-summary if one exists for the given edit.
+ *
+ * @since 1.21
+ *
+ * @param $oldContent Content|null: the previous text of the page.
+ * @param $newContent Content|null: The submitted text of the page.
+ * @param $flags int Bit mask: a bit mask of flags submitted for the edit.
+ *
+ * @return string An appropriate auto-summary, or an empty string.
+ */
+ public function getAutosummary( Content $oldContent = null, Content $newContent = null, $flags ) {
+ global $wgContLang;
+
+ // Decide what kind of auto-summary is needed.
+
+ // Redirect auto-summaries
+
+ /**
+ * @var $ot Title
+ * @var $rt Title
+ */
+
+ $ot = !is_null( $oldContent ) ? $oldContent->getRedirectTarget() : null;
+ $rt = !is_null( $newContent ) ? $newContent->getRedirectTarget() : null;
+
+ if ( is_object( $rt ) ) {
+ if ( !is_object( $ot )
+ || !$rt->equals( $ot )
+ || $ot->getFragment() != $rt->getFragment() )
+ {
+ $truncatedtext = $newContent->getTextForSummary(
+ 250
+ - strlen( wfMessage( 'autoredircomment' )->inContentLanguage()->text() )
+ - strlen( $rt->getFullText() ) );
+
+ return wfMessage( 'autoredircomment', $rt->getFullText() )
+ ->rawParams( $truncatedtext )->inContentLanguage()->text();
+ }
+ }
+
+ // New page auto-summaries
+ if ( $flags & EDIT_NEW && $newContent->getSize() > 0 ) {
+ // If they're making a new article, give its text, truncated, in
+ // the summary.
+
+ $truncatedtext = $newContent->getTextForSummary(
+ 200 - strlen( wfMessage( 'autosumm-new' )->inContentLanguage()->text() ) );
+
+ return wfMessage( 'autosumm-new' )->rawParams( $truncatedtext )
+ ->inContentLanguage()->text();
+ }
+
+ // Blanking auto-summaries
+ if ( !empty( $oldContent ) && $oldContent->getSize() > 0 && $newContent->getSize() == 0 ) {
+ return wfMessage( 'autosumm-blank' )->inContentLanguage()->text();
+ } elseif ( !empty( $oldContent )
+ && $oldContent->getSize() > 10 * $newContent->getSize()
+ && $newContent->getSize() < 500 )
+ {
+ // Removing more than 90% of the article
+
+ $truncatedtext = $newContent->getTextForSummary(
+ 200 - strlen( wfMessage( 'autosumm-replace' )->inContentLanguage()->text() ) );
+
+ return wfMessage( 'autosumm-replace' )->rawParams( $truncatedtext )
+ ->inContentLanguage()->text();
+ }
+
+ // If we reach this point, there's no applicable auto-summary for our
+ // case, so our auto-summary is empty.
+ return '';
+ }
+
+ /**
+ * Auto-generates a deletion reason
+ *
+ * @since 1.21
+ *
+ * @param $title Title: the page's title
+ * @param &$hasHistory Boolean: whether the page has a history
+ * @return mixed String containing deletion reason or empty string, or
+ * boolean false if no revision occurred
+ *
+ * @XXX &$hasHistory is extremely ugly, it's here because
+ * WikiPage::getAutoDeleteReason() and Article::getReason()
+ * have it / want it.
+ */
+ public function getAutoDeleteReason( Title $title, &$hasHistory ) {
+ $dbw = wfGetDB( DB_MASTER );
+
+ // Get the last revision
+ $rev = Revision::newFromTitle( $title );
+
+ if ( is_null( $rev ) ) {
+ return false;
+ }
+
+ // Get the article's contents
+ $content = $rev->getContent();
+ $blank = false;
+
+ $this->checkModelID( $content->getModel() );
+
+ // If the page is blank, use the text from the previous revision,
+ // which can only be blank if there's a move/import/protect dummy
+ // revision involved
+ if ( $content->getSize() == 0 ) {
+ $prev = $rev->getPrevious();
+
+ if ( $prev ) {
+ $content = $prev->getContent();
+ $blank = true;
+ }
+ }
+
+ // Find out if there was only one contributor
+ // Only scan the last 20 revisions
+ $res = $dbw->select( 'revision', 'rev_user_text',
+ array(
+ 'rev_page' => $title->getArticleID(),
+ $dbw->bitAnd( 'rev_deleted', Revision::DELETED_USER ) . ' = 0'
+ ),
+ __METHOD__,
+ array( 'LIMIT' => 20 )
+ );
+
+ if ( $res === false ) {
+ // This page has no revisions, which is very weird
+ return false;
+ }
+
+ $hasHistory = ( $res->numRows() > 1 );
+ $row = $dbw->fetchObject( $res );
+
+ if ( $row ) { // $row is false if the only contributor is hidden
+ $onlyAuthor = $row->rev_user_text;
+ // Try to find a second contributor
+ foreach ( $res as $row ) {
+ if ( $row->rev_user_text != $onlyAuthor ) { // Bug 22999
+ $onlyAuthor = false;
+ break;
+ }
+ }
+ } else {
+ $onlyAuthor = false;
+ }
+
+ // Generate the summary with a '$1' placeholder
+ if ( $blank ) {
+ // The current revision is blank and the one before is also
+ // blank. It's just not our lucky day
+ $reason = wfMessage( 'exbeforeblank', '$1' )->inContentLanguage()->text();
+ } else {
+ if ( $onlyAuthor ) {
+ $reason = wfMessage(
+ 'excontentauthor',
+ '$1',
+ $onlyAuthor
+ )->inContentLanguage()->text();
+ } else {
+ $reason = wfMessage( 'excontent', '$1' )->inContentLanguage()->text();
+ }
+ }
+
+ if ( $reason == '-' ) {
+ // Allow these UI messages to be blanked out cleanly
+ return '';
+ }
+
+ // Max content length = max comment length - length of the comment (excl. $1)
+ $text = $content->getTextForSummary( 255 - ( strlen( $reason ) - 2 ) );
+
+ // Now replace the '$1' placeholder
+ $reason = str_replace( '$1', $text, $reason );
+
+ return $reason;
+ }
+
+ /**
+ * Get the Content object that needs to be saved in order to undo all revisions
+ * between $undo and $undoafter. Revisions must belong to the same page,
+ * must exist and must not be deleted.
+ *
+ * @since 1.21
+ *
+ * @param $current Revision The current text
+ * @param $undo Revision The revision to undo
+ * @param $undoafter Revision Must be an earlier revision than $undo
+ *
+ * @return mixed String on success, false on failure
+ */
+ public function getUndoContent( Revision $current, Revision $undo, Revision $undoafter ) {
+ $cur_content = $current->getContent();
+
+ if ( empty( $cur_content ) ) {
+ return false; // no page
+ }
+
+ $undo_content = $undo->getContent();
+ $undoafter_content = $undoafter->getContent();
+
+ $this->checkModelID( $cur_content->getModel() );
+ $this->checkModelID( $undo_content->getModel() );
+ $this->checkModelID( $undoafter_content->getModel() );
+
+ if ( $cur_content->equals( $undo_content ) ) {
+ // No use doing a merge if it's just a straight revert.
+ return $undoafter_content;
+ }
+
+ $undone_content = $this->merge3( $undo_content, $undoafter_content, $cur_content );
+
+ return $undone_content;
+ }
+
+ /**
+ * Get parser options suitable for rendering the primary article wikitext
+ *
+ * @param IContextSource|User|string $context One of the following:
+ * - IContextSource: Use the User and the Language of the provided
+ * context
+ * - User: Use the provided User object and $wgLang for the language,
+ * so use an IContextSource object if possible.
+ * - 'canonical': Canonical options (anonymous user with default
+ * preferences and content language).
+ *
+ * @param IContextSource|User|string $context
+ *
+ * @throws MWException
+ * @return ParserOptions
+ */
+ public function makeParserOptions( $context ) {
+ global $wgContLang;
+
+ if ( $context instanceof IContextSource ) {
+ $options = ParserOptions::newFromContext( $context );
+ } elseif ( $context instanceof User ) { // settings per user (even anons)
+ $options = ParserOptions::newFromUser( $context );
+ } elseif ( $context === 'canonical' ) { // canonical settings
+ $options = ParserOptions::newFromUserAndLang( new User, $wgContLang );
+ } else {
+ throw new MWException( "Bad context for parser options: $context" );
+ }
+
+ $options->enableLimitReport(); // show inclusion/loop reports
+ $options->setTidy( true ); // fix bad HTML
+
+ return $options;
+ }
+
+ /**
+ * Returns true for content models that support caching using the
+ * ParserCache mechanism. See WikiPage::isParserCacheUsed().
+ *
+ * @since 1.21
+ *
+ * @return bool
+ */
+ public function isParserCacheSupported() {
+ return false;
+ }
+
+ /**
+ * Returns true if this content model supports sections.
+ *
+ * This default implementation returns false.
+ *
+ * @return boolean whether sections are supported.
+ */
+ public function supportsSections() {
+ return false;
+ }
+
+ /**
+ * Logs a deprecation warning, visible if $wgDevelopmentWarnings, but only if
+ * self::$enableDeprecationWarnings is set to true.
+ *
+ * @param String $func The name of the deprecated function
+ * @param string $version The version since the method is deprecated. Usually 1.21
+ * for ContentHandler related stuff.
+ * @param String|bool $component: Component to which the function belongs.
+ * If false, it is assumed the function is in MediaWiki core.
+ *
+ * @see ContentHandler::$enableDeprecationWarnings
+ * @see wfDeprecated
+ */
+ public static function deprecated( $func, $version, $component = false ) {
+ if ( self::$enableDeprecationWarnings ) {
+ wfDeprecated( $func, $version, $component, 3 );
+ }
+ }
+
+ /**
+ * Call a legacy hook that uses text instead of Content objects.
+ * Will log a warning when a matching hook function is registered.
+ * If the textual representation of the content is changed by the
+ * hook function, a new Content object is constructed from the new
+ * text.
+ *
+ * @param $event String: event name
+ * @param $args Array: parameters passed to hook functions
+ * @param $warn bool: whether to log a warning.
+ * Default to self::$enableDeprecationWarnings.
+ * May be set to false for testing.
+ *
+ * @return Boolean True if no handler aborted the hook
+ *
+ * @see ContentHandler::$enableDeprecationWarnings
+ */
+ public static function runLegacyHooks( $event, $args = array(),
+ $warn = null ) {
+
+ if ( $warn === null ) {
+ $warn = self::$enableDeprecationWarnings;
+ }
+
+ if ( !Hooks::isRegistered( $event ) ) {
+ return true; // nothing to do here
+ }
+
+ if ( $warn ) {
+ // Log information about which handlers are registered for the legacy hook,
+ // so we can find and fix them.
+
+ $handlers = Hooks::getHandlers( $event );
+ $handlerInfo = array();
+
+ wfSuppressWarnings();
+
+ foreach ( $handlers as $handler ) {
+ $info = '';
+
+ if ( is_array( $handler ) ) {
+ if ( is_object( $handler[0] ) ) {
+ $info = get_class( $handler[0] );
+ } else {
+ $info = $handler[0];
+ }
+
+ if ( isset( $handler[1] ) ) {
+ $info .= '::' . $handler[1];
+ }
+ } else if ( is_object( $handler ) ) {
+ $info = get_class( $handler[0] );
+ $info .= '::on' . $event;
+ } else {
+ $info = $handler;
+ }
+
+ $handlerInfo[] = $info;
+ }
+
+ wfRestoreWarnings();
+
+ wfWarn( "Using obsolete hook $event via ContentHandler::runLegacyHooks()! Handlers: " . implode(', ', $handlerInfo), 2 );
+ }
+
+ // convert Content objects to text
+ $contentObjects = array();
+ $contentTexts = array();
+
+ foreach ( $args as $k => $v ) {
+ if ( $v instanceof Content ) {
+ /* @var Content $v */
+
+ $contentObjects[$k] = $v;
+
+ $v = $v->serialize();
+ $contentTexts[ $k ] = $v;
+ $args[ $k ] = $v;
+ }
+ }
+
+ // call the hook functions
+ $ok = wfRunHooks( $event, $args );
+
+ // see if the hook changed the text
+ foreach ( $contentTexts as $k => $orig ) {
+ /* @var Content $content */
+
+ $modified = $args[ $k ];
+ $content = $contentObjects[$k];
+
+ if ( $modified !== $orig ) {
+ // text was changed, create updated Content object
+ $content = $content->getContentHandler()->unserializeContent( $modified );
+ }
+
+ $args[ $k ] = $content;
+ }
+
+ return $ok;
+ }
+}
+
--- /dev/null
+<?php
+/**
+ * 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.21
+ *
+ * @file
+ * @ingroup Content
+ *
+ * @author Daniel Kinzler
+ */
+class CssContent extends TextContent {
+ public function __construct( $text ) {
+ parent::__construct( $text, CONTENT_MODEL_CSS );
+ }
+
+ /**
+ * Returns a Content object with pre-save transformations applied using
+ * Parser::preSaveTransform().
+ *
+ * @param $title Title
+ * @param $user User
+ * @param $popts ParserOptions
+ * @return Content
+ */
+ public function preSaveTransform( Title $title, User $user, ParserOptions $popts ) {
+ global $wgParser;
+ // @todo: make pre-save transformation optional for script pages
+
+ $text = $this->getNativeData();
+ $pst = $wgParser->preSaveTransform( $text, $title, $user, $popts );
+
+ return new CssContent( $pst );
+ }
+
+
+ protected function getHtml( ) {
+ $html = "";
+ $html .= "<pre class=\"mw-code mw-css\" dir=\"ltr\">\n";
+ $html .= $this->getHighlightHtml( );
+ $html .= "\n</pre>\n";
+
+ return $html;
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * @since 1.21
+ */
+class CssContentHandler extends TextContentHandler {
+
+ public function __construct( $modelId = CONTENT_MODEL_CSS ) {
+ parent::__construct( $modelId, array( CONTENT_FORMAT_CSS ) );
+ }
+
+ public function unserializeContent( $text, $format = null ) {
+ $this->checkFormat( $format );
+
+ return new CssContent( $text );
+ }
+
+ public function makeEmptyContent() {
+ return new CssContent( '' );
+ }
+
+ /**
+ * Returns the english language, because CSS is english, and should be handled as such.
+ *
+ * @return Language wfGetLangObj( 'en' )
+ *
+ * @see ContentHandler::getPageLanguage()
+ */
+ public function getPageLanguage( Title $title, Content $content = null ) {
+ return wfGetLangObj( 'en' );
+ }
+
+ /**
+ * Returns the english language, because CSS is english, and should be handled as such.
+ *
+ * @return Language wfGetLangObj( 'en' )
+ *
+ * @see ContentHandler::getPageViewLanguage()
+ */
+ public function getPageViewLanguage( Title $title, Content $content = null ) {
+ return wfGetLangObj( 'en' );
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+/**
+ * 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.21
+ *
+ * @file
+ * @ingroup Content
+ *
+ * @author Daniel Kinzler
+ */
+class JavaScriptContent extends TextContent {
+ public function __construct( $text ) {
+ parent::__construct( $text, CONTENT_MODEL_JAVASCRIPT );
+ }
+
+ /**
+ * Returns a Content object with pre-save transformations applied using
+ * Parser::preSaveTransform().
+ *
+ * @param Title $title
+ * @param User $user
+ * @param ParserOptions $popts
+ * @return Content
+ */
+ public function preSaveTransform( Title $title, User $user, ParserOptions $popts ) {
+ global $wgParser;
+ // @todo: make pre-save transformation optional for script pages
+ // See bug #32858
+
+ $text = $this->getNativeData();
+ $pst = $wgParser->preSaveTransform( $text, $title, $user, $popts );
+
+ return new JavaScriptContent( $pst );
+ }
+
+
+ protected function getHtml( ) {
+ $html = "";
+ $html .= "<pre class=\"mw-code mw-js\" dir=\"ltr\">\n";
+ $html .= $this->getHighlightHtml( );
+ $html .= "\n</pre>\n";
+
+ return $html;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+# XXX: make ScriptContentHandler base class, do highlighting stuff there?
+
+/**
+ * @since 1.21
+ */
+class JavaScriptContentHandler extends TextContentHandler {
+
+ public function __construct( $modelId = CONTENT_MODEL_JAVASCRIPT ) {
+ parent::__construct( $modelId, array( CONTENT_FORMAT_JAVASCRIPT ) );
+ }
+
+ public function unserializeContent( $text, $format = null ) {
+ $this->checkFormat( $format );
+
+ return new JavaScriptContent( $text );
+ }
+
+ public function makeEmptyContent() {
+ return new JavaScriptContent( '' );
+ }
+
+ /**
+ * Returns the english language, because JS is english, and should be handled as such.
+ *
+ * @return Language wfGetLangObj( 'en' )
+ *
+ * @see ContentHandler::getPageLanguage()
+ */
+ public function getPageLanguage( Title $title, Content $content = null ) {
+ return wfGetLangObj( 'en' );
+ }
+
+ /**
+ * Returns the english language, because CSS is english, and should be handled as such.
+ *
+ * @return Language wfGetLangObj( 'en' )
+ *
+ * @see ContentHandler::getPageViewLanguage()
+ */
+ public function getPageViewLanguage( Title $title, Content $content = null ) {
+ return wfGetLangObj( 'en' );
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+/**
+ * Wrapper allowing us to handle a system message as a Content object. Note that this is generally *not* used
+ * to represent content from the MediaWiki namespace, and that there is no MessageContentHandler. MessageContent
+ * is just intended as glue for wrapping a message programatically.
+ *
+ * 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.21
+ *
+ * @file
+ * @ingroup Content
+ *
+ * @author Daniel Kinzler
+ */
+class MessageContent extends AbstractContent {
+
+ /**
+ * @var Message
+ */
+ protected $mMessage;
+
+ /**
+ * @param Message|String $msg A Message object, or a message key
+ * @param array|null $params An optional array of message parameters
+ */
+ public function __construct( $msg, $params = null ) {
+ # XXX: messages may be wikitext, html or plain text! and maybe even something else entirely.
+ parent::__construct( CONTENT_MODEL_WIKITEXT );
+
+ if ( is_string( $msg ) ) {
+ $this->mMessage = wfMessage( $msg );
+ } else {
+ $this->mMessage = clone $msg;
+ }
+
+ if ( $params ) {
+ $this->mMessage = $this->mMessage->params( $params );
+ }
+ }
+
+ /**
+ * Returns the message as rendered HTML
+ *
+ * @return string The message text, parsed into html
+ */
+ public function getHtml() {
+ return $this->mMessage->parse();
+ }
+
+ /**
+ * Returns the message as rendered HTML
+ *
+ * @return string The message text, parsed into html
+ */
+ public function getWikitext() {
+ return $this->mMessage->text();
+ }
+
+ /**
+ * Returns the message object, with any parameters already substituted.
+ *
+ * @return Message The message object.
+ */
+ public function getNativeData() {
+ //NOTE: Message objects are mutable. Cloning here makes MessageContent immutable.
+ return clone $this->mMessage;
+ }
+
+ /**
+ * @see Content::getTextForSearchIndex
+ */
+ public function getTextForSearchIndex() {
+ return $this->mMessage->plain();
+ }
+
+ /**
+ * @see Content::getWikitextForTransclusion
+ */
+ public function getWikitextForTransclusion() {
+ return $this->getWikitext();
+ }
+
+ /**
+ * @see Content::getTextForSummary
+ */
+ public function getTextForSummary( $maxlength = 250 ) {
+ return substr( $this->mMessage->plain(), 0, $maxlength );
+ }
+
+ /**
+ * @see Content::getSize
+ *
+ * @return int
+ */
+ public function getSize() {
+ return strlen( $this->mMessage->plain() );
+ }
+
+ /**
+ * @see Content::copy
+ *
+ * @return Content. A copy of this object
+ */
+ public function copy() {
+ // MessageContent is immutable (because getNativeData() returns a clone of the Message object)
+ return $this;
+ }
+
+ /**
+ * @see Content::isCountable
+ *
+ * @return bool false
+ */
+ public function isCountable( $hasLinks = null ) {
+ return false;
+ }
+
+ /**
+ * @see Content::getParserOutput
+ *
+ * @return ParserOutput
+ */
+ public function getParserOutput(
+ Title $title, $revId = null,
+ ParserOptions $options = null, $generateHtml = true
+ ) {
+
+ if ( $generateHtml ) {
+ $html = $this->getHtml();
+ } else {
+ $html = '';
+ }
+
+ $po = new ParserOutput( $html );
+ return $po;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+/**
+ * Content object implementation for representing flat text.
+ *
+ * TextContent instances are immutable
+ *
+ * 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.21
+ *
+ * @file
+ * @ingroup Content
+ *
+ * @author Daniel Kinzler
+ */
+class TextContent extends AbstractContent {
+
+ public function __construct( $text, $model_id = CONTENT_MODEL_TEXT ) {
+ parent::__construct( $model_id );
+
+ if ( !is_string( $text ) ) {
+ throw new MWException( "TextContent expects a string in the constructor." );
+ }
+
+ $this->mText = $text;
+ }
+
+ public function copy() {
+ return $this; # NOTE: this is ok since TextContent are immutable.
+ }
+
+ public function getTextForSummary( $maxlength = 250 ) {
+ global $wgContLang;
+
+ $text = $this->getNativeData();
+
+ $truncatedtext = $wgContLang->truncate(
+ preg_replace( "/[\n\r]/", ' ', $text ),
+ max( 0, $maxlength ) );
+
+ return $truncatedtext;
+ }
+
+ /**
+ * returns the text's size in bytes.
+ *
+ * @return int The size
+ */
+ public function getSize( ) {
+ $text = $this->getNativeData( );
+ return strlen( $text );
+ }
+
+ /**
+ * Returns true if this content is not a redirect, and $wgArticleCountMethod
+ * is "any".
+ *
+ * @param $hasLinks Bool: if it is known whether this content contains links,
+ * provide this information here, to avoid redundant parsing to find out.
+ *
+ * @return bool True if the content is countable
+ */
+ public function isCountable( $hasLinks = null ) {
+ global $wgArticleCountMethod;
+
+ if ( $this->isRedirect( ) ) {
+ return false;
+ }
+
+ if ( $wgArticleCountMethod === 'any' ) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns the text represented by this Content object, as a string.
+ *
+ * @param the raw text
+ */
+ public function getNativeData( ) {
+ $text = $this->mText;
+ return $text;
+ }
+
+ /**
+ * Returns the text represented by this Content object, as a string.
+ *
+ * @param the raw text
+ */
+ public function getTextForSearchIndex( ) {
+ return $this->getNativeData();
+ }
+
+ /**
+ * Returns the text represented by this Content object, as a string.
+ *
+ * @param the raw text
+ */
+ public function getWikitextForTransclusion( ) {
+ return $this->getNativeData();
+ }
+
+ /**
+ * Diff this content object with another content object..
+ *
+ * @since 1.21diff
+ *
+ * @param $that Content the other content object to compare this content object to
+ * @param $lang Language the language object to use for text segmentation.
+ * If not given, $wgContentLang is used.
+ *
+ * @return DiffResult a diff representing the changes that would have to be
+ * made to this content object to make it equal to $that.
+ */
+ public function diff( Content $that, Language $lang = null ) {
+ global $wgContLang;
+
+ $this->checkModelID( $that->getModel() );
+
+ # @todo: could implement this in DifferenceEngine and just delegate here?
+
+ if ( !$lang ) $lang = $wgContLang;
+
+ $otext = $this->getNativeData();
+ $ntext = $this->getNativeData();
+
+ # Note: Use native PHP diff, external engines don't give us abstract output
+ $ota = explode( "\n", $wgContLang->segmentForDiff( $otext ) );
+ $nta = explode( "\n", $wgContLang->segmentForDiff( $ntext ) );
+
+ $diff = new Diff( $ota, $nta );
+ return $diff;
+ }
+
+
+ /**
+ * Returns a generic ParserOutput object, wrapping the HTML returned by
+ * getHtml().
+ *
+ * @param $title Title Context title for parsing
+ * @param $revId int|null Revision ID (for {{REVISIONID}})
+ * @param $options ParserOptions|null Parser options
+ * @param $generateHtml bool Whether or not to generate HTML
+ *
+ * @return ParserOutput representing the HTML form of the text
+ */
+ public function getParserOutput( Title $title,
+ $revId = null,
+ ParserOptions $options = null, $generateHtml = true
+ ) {
+ # Generic implementation, relying on $this->getHtml()
+
+ if ( $generateHtml ) {
+ $html = $this->getHtml();
+ } else {
+ $html = '';
+ }
+
+ $po = new ParserOutput( $html );
+ return $po;
+ }
+
+ /**
+ * Generates an HTML version of the content, for display. Used by
+ * getParserOutput() to construct a ParserOutput object.
+ *
+ * This default implementation just calls getHighlightHtml(). Content
+ * models that have another mapping to HTML (as is the case for markup
+ * languages like wikitext) should override this method to generate the
+ * appropriate HTML.
+ *
+ * @return string An HTML representation of the content
+ */
+ protected function getHtml() {
+ return $this->getHighlightHtml();
+ }
+
+ /**
+ * Generates a syntax-highlighted version of the content, as HTML.
+ * Used by the default implementation of getHtml().
+ *
+ * @return string an HTML representation of the content's markup
+ */
+ protected function getHighlightHtml( ) {
+ # TODO: make Highlighter interface, use highlighter here, if available
+ return htmlspecialchars( $this->getNativeData() );
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * @since 1.21
+ */
+class TextContentHandler extends ContentHandler {
+
+ public function __construct( $modelId = CONTENT_MODEL_TEXT, $formats = array( CONTENT_FORMAT_TEXT ) ) {
+ parent::__construct( $modelId, $formats );
+ }
+
+ /**
+ * Returns the content's text as-is.
+ *
+ * @param $content Content
+ * @param $format string|null
+ * @return mixed
+ */
+ public function serializeContent( Content $content, $format = null ) {
+ $this->checkFormat( $format );
+ return $content->getNativeData();
+ }
+
+ /**
+ * Attempts to merge differences between three versions. Returns a new
+ * Content object for a clean merge and false for failure or a conflict.
+ *
+ * All three Content objects passed as parameters must have the same
+ * content model.
+ *
+ * This text-based implementation uses wfMerge().
+ *
+ * @param $oldContent \Content|string String
+ * @param $myContent \Content|string String
+ * @param $yourContent \Content|string String
+ *
+ * @return Content|Bool
+ */
+ public function merge3( Content $oldContent, Content $myContent, Content $yourContent ) {
+ $this->checkModelID( $oldContent->getModel() );
+ $this->checkModelID( $myContent->getModel() );
+ $this->checkModelID( $yourContent->getModel() );
+
+ $format = $this->getDefaultFormat();
+
+ $old = $this->serializeContent( $oldContent, $format );
+ $mine = $this->serializeContent( $myContent, $format );
+ $yours = $this->serializeContent( $yourContent, $format );
+
+ $ok = wfMerge( $old, $mine, $yours, $result );
+
+ if ( !$ok ) {
+ return false;
+ }
+
+ if ( !$result ) {
+ return $this->makeEmptyContent();
+ }
+
+ $mergedContent = $this->unserializeContent( $result, $format );
+ return $mergedContent;
+ }
+
+ /**
+ * Unserializes a Content object of the type supported by this ContentHandler.
+ *
+ * @since 1.21
+ *
+ * @param $text string serialized form of the content
+ * @param $format null|String the format used for serialization
+ *
+ * @return Content the TextContent object wrapping $text
+ */
+ public function unserializeContent( $text, $format = null ) {
+ $this->checkFormat( $format );
+
+ return new TextContent( $text );
+ }
+
+ /**
+ * Creates an empty TextContent object.
+ *
+ * @since 1.21
+ *
+ * @return Content
+ */
+ public function makeEmptyContent() {
+ return new TextContent( '' );
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/**
+ * 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.21
+ *
+ * @file
+ * @ingroup Content
+ *
+ * @author Daniel Kinzler
+ */
+class WikitextContent extends TextContent {
+
+ public function __construct( $text ) {
+ parent::__construct( $text, CONTENT_MODEL_WIKITEXT );
+ }
+
+ /**
+ * @see Content::getSection()
+ */
+ public function getSection( $section ) {
+ global $wgParser;
+
+ $text = $this->getNativeData();
+ $sect = $wgParser->getSection( $text, $section, false );
+
+ if ( $sect === false ) {
+ return false;
+ } else {
+ return new WikitextContent( $sect );
+ }
+ }
+
+ /**
+ * @see Content::replaceSection()
+ */
+ public function replaceSection( $section, Content $with, $sectionTitle = '' ) {
+ wfProfileIn( __METHOD__ );
+
+ $myModelId = $this->getModel();
+ $sectionModelId = $with->getModel();
+
+ if ( $sectionModelId != $myModelId ) {
+ throw new MWException( "Incompatible content model for section: " .
+ "document uses $myModelId but " .
+ "section uses $sectionModelId." );
+ }
+
+ $oldtext = $this->getNativeData();
+ $text = $with->getNativeData();
+
+ if ( $section === '' ) {
+ return $with; # XXX: copy first?
+ } if ( $section == 'new' ) {
+ # Inserting a new section
+ $subject = $sectionTitle ? wfMessage( 'newsectionheaderdefaultlevel' )
+ ->rawParams( $sectionTitle )->inContentLanguage()->text() . "\n\n" : '';
+ if ( wfRunHooks( 'PlaceNewSection', array( $this, $oldtext, $subject, &$text ) ) ) {
+ $text = strlen( trim( $oldtext ) ) > 0
+ ? "{$oldtext}\n\n{$subject}{$text}"
+ : "{$subject}{$text}";
+ }
+ } else {
+ # Replacing an existing section; roll out the big guns
+ global $wgParser;
+
+ $text = $wgParser->replaceSection( $oldtext, $section, $text );
+ }
+
+ $newContent = new WikitextContent( $text );
+
+ wfProfileOut( __METHOD__ );
+ return $newContent;
+ }
+
+ /**
+ * Returns a new WikitextContent object with the given section heading
+ * prepended.
+ *
+ * @param $header string
+ * @return Content
+ */
+ public function addSectionHeader( $header ) {
+ $text = wfMessage( 'newsectionheaderdefaultlevel' )
+ ->rawParams( $header )->inContentLanguage()->text();
+ $text .= "\n\n";
+ $text .= $this->getNativeData();
+
+ return new WikitextContent( $text );
+ }
+
+ /**
+ * Returns a Content object with pre-save transformations applied using
+ * Parser::preSaveTransform().
+ *
+ * @param $title Title
+ * @param $user User
+ * @param $popts ParserOptions
+ * @return Content
+ */
+ public function preSaveTransform( Title $title, User $user, ParserOptions $popts ) {
+ global $wgParser;
+
+ $text = $this->getNativeData();
+ $pst = $wgParser->preSaveTransform( $text, $title, $user, $popts );
+
+ return new WikitextContent( $pst );
+ }
+
+ /**
+ * Returns a Content object with preload transformations applied (or this
+ * object if no transformations apply).
+ *
+ * @param $title Title
+ * @param $popts ParserOptions
+ * @return Content
+ */
+ public function preloadTransform( Title $title, ParserOptions $popts ) {
+ global $wgParser;
+
+ $text = $this->getNativeData();
+ $plt = $wgParser->getPreloadText( $text, $title, $popts );
+
+ return new WikitextContent( $plt );
+ }
+
+ /**
+ * Implement redirect extraction for wikitext.
+ *
+ * @return null|Title
+ *
+ * @note: migrated here from Title::newFromRedirectInternal()
+ *
+ * @see Content::getRedirectTarget
+ * @see AbstractContent::getRedirectTarget
+ */
+ public function getRedirectTarget() {
+ global $wgMaxRedirects;
+ if ( $wgMaxRedirects < 1 ) {
+ // redirects are disabled, so quit early
+ return null;
+ }
+ $redir = MagicWord::get( 'redirect' );
+ $text = trim( $this->getNativeData() );
+ if ( $redir->matchStartAndRemove( $text ) ) {
+ // Extract the first link and see if it's usable
+ // Ensure that it really does come directly after #REDIRECT
+ // Some older redirects included a colon, so don't freak about that!
+ $m = array();
+ if ( preg_match( '!^\s*:?\s*\[{2}(.*?)(?:\|.*?)?\]{2}!', $text, $m ) ) {
+ // Strip preceding colon used to "escape" categories, etc.
+ // and URL-decode links
+ if ( strpos( $m[1], '%' ) !== false ) {
+ // Match behavior of inline link parsing here;
+ $m[1] = rawurldecode( ltrim( $m[1], ':' ) );
+ }
+ $title = Title::newFromText( $m[1] );
+ // If the title is a redirect to bad special pages or is invalid, return null
+ if ( !$title instanceof Title || !$title->isValidRedirectTarget() ) {
+ return null;
+ }
+ return $title;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * @see Content::updateRedirect()
+ *
+ * This implementation replaces the first link on the page with the given new target
+ * if this Content object is a redirect. Otherwise, this method returns $this.
+ *
+ * @since 1.21
+ *
+ * @param Title $target
+ *
+ * @return Content a new Content object with the updated redirect (or $this if this Content object isn't a redirect)
+ */
+ public function updateRedirect( Title $target ) {
+ if ( !$this->isRedirect() ) {
+ return $this;
+ }
+
+ # Fix the text
+ # Remember that redirect pages can have categories, templates, etc.,
+ # so the regex has to be fairly general
+ $newText = preg_replace( '/ \[ \[ [^\]]* \] \] /x',
+ '[[' . $target->getFullText() . ']]',
+ $this->getNativeData(), 1 );
+
+ return new WikitextContent( $newText );
+ }
+
+ /**
+ * Returns true if this content is not a redirect, and this content's text
+ * is countable according to the criteria defined by $wgArticleCountMethod.
+ *
+ * @param $hasLinks Bool if it is known whether this content contains
+ * links, provide this information here, to avoid redundant parsing to
+ * find out.
+ * @param $title null|\Title
+ *
+ * @internal param \IContextSource $context context for parsing if necessary
+ *
+ * @return bool True if the content is countable
+ */
+ public function isCountable( $hasLinks = null, Title $title = null ) {
+ global $wgArticleCountMethod;
+
+ if ( $this->isRedirect( ) ) {
+ return false;
+ }
+
+ $text = $this->getNativeData();
+
+ switch ( $wgArticleCountMethod ) {
+ case 'any':
+ return true;
+ case 'comma':
+ return strpos( $text, ',' ) !== false;
+ case 'link':
+ if ( $hasLinks === null ) { # not known, find out
+ if ( !$title ) {
+ $context = RequestContext::getMain();
+ $title = $context->getTitle();
+ }
+
+ $po = $this->getParserOutput( $title, null, null, false );
+ $links = $po->getLinks();
+ $hasLinks = !empty( $links );
+ }
+
+ return $hasLinks;
+ }
+
+ return false;
+ }
+
+ public function getTextForSummary( $maxlength = 250 ) {
+ $truncatedtext = parent::getTextForSummary( $maxlength );
+
+ # clean up unfinished links
+ # XXX: make this optional? wasn't there in autosummary, but required for
+ # deletion summary.
+ $truncatedtext = preg_replace( '/\[\[([^\]]*)\]?$/', '$1', $truncatedtext );
+
+ return $truncatedtext;
+ }
+
+ /**
+ * Returns a ParserOutput object resulting from parsing the content's text
+ * using $wgParser.
+ *
+ * @since 1.21
+ *
+ * @param $content Content the content to render
+ * @param $title \Title
+ * @param $revId null
+ * @param $options null|ParserOptions
+ * @param $generateHtml bool
+ *
+ * @internal param \IContextSource|null $context
+ * @return ParserOutput representing the HTML form of the text
+ */
+ public function getParserOutput( Title $title,
+ $revId = null,
+ ParserOptions $options = null, $generateHtml = true
+ ) {
+ global $wgParser;
+
+ if ( !$options ) {
+ //NOTE: use canonical options per default to produce cacheable output
+ $options = $this->getContentHandler()->makeParserOptions( 'canonical' );
+ }
+
+ $po = $wgParser->parse( $this->getNativeData(), $title, $options, true, true, $revId );
+ return $po;
+ }
+
+ protected function getHtml() {
+ throw new MWException(
+ "getHtml() not implemented for wikitext. "
+ . "Use getParserOutput()->getText()."
+ );
+ }
+
+ /**
+ * @see Content::matchMagicWord()
+ *
+ * This implementation calls $word->match() on the this TextContent object's text.
+ *
+ * @param MagicWord $word
+ *
+ * @return bool whether this Content object matches the given magic word.
+ */
+ public function matchMagicWord( MagicWord $word ) {
+ return $word->match( $this->getNativeData() );
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * @since 1.21
+ */
+class WikitextContentHandler extends TextContentHandler {
+
+ public function __construct( $modelId = CONTENT_MODEL_WIKITEXT ) {
+ parent::__construct( $modelId, array( CONTENT_FORMAT_WIKITEXT ) );
+ }
+
+ public function unserializeContent( $text, $format = null ) {
+ $this->checkFormat( $format );
+
+ return new WikitextContent( $text );
+ }
+
+ /**
+ * @see ContentHandler::makeEmptyContent
+ *
+ * @return Content
+ */
+ public function makeEmptyContent() {
+ return new WikitextContent( '' );
+ }
+
+
+ /**
+ * Returns a WikitextContent object representing a redirect to the given destination page.
+ *
+ * @see ContentHandler::makeRedirectContent
+ *
+ * @param Title $destination the page to redirect to.
+ *
+ * @return Content
+ */
+ public function makeRedirectContent( Title $destination ) {
+ $mwRedir = MagicWord::get( 'redirect' );
+ $redirectText = $mwRedir->getSynonym( 0 ) . ' [[' . $destination->getPrefixedText() . "]]\n";
+
+ return new WikitextContent( $redirectText );
+ }
+
+ /**
+ * Returns true because wikitext supports sections.
+ *
+ * @return boolean whether sections are supported.
+ */
+ public function supportsSections() {
+ return true;
+ }
+
+ /**
+ * Returns true, because wikitext supports caching using the
+ * ParserCache mechanism.
+ *
+ * @since 1.21
+ * @return bool
+ */
+ public function isParserCacheSupported() {
+ return true;
+ }
+}
\ No newline at end of file
$args = func_get_args();
return call_user_func_array( array( $this->getContext(), 'msg' ), $args );
}
-
}
-
* Set the Language object
*
* @param $l Mixed Language instance or language code
+ * @throws MWException
* @since 1.19
*/
public function setLanguage( $l ) {
* canUseWikiPage() to check whether this method can be called safely.
*
* @since 1.19
+ * @throws MWException
* @return WikiPage
*/
public function getWikiPage() {
* Set the Language object
*
* @param $l Mixed Language instance or language code
+ * @throws MWException
* @since 1.19
*/
public function setLanguage( $l ) {
public function getSkin() {
if ( $this->skin === null ) {
wfProfileIn( __METHOD__ . '-createskin' );
-
+
$skin = null;
wfRunHooks( 'RequestContextCreateSkin', array( $this, &$skin ) );
}
}
-
* Clone the table structure
*/
public function cloneTableStructure() {
-
foreach( $this->tablesToClone as $tbl ) {
# Clean up from previous aborted run. So that table escaping
# works correctly across DB engines, we need to change the pre-
# fix back and forth so tableName() works right.
-
+
self::changePrefix( $this->oldTablePrefix );
$oldTableName = $this->db->tableName( $tbl, 'raw' );
-
+
self::changePrefix( $this->newTablePrefix );
$newTableName = $this->db->tableName( $tbl, 'raw' );
-
+
if( $this->dropCurrentTables && !in_array( $this->db->getType(), array( 'postgres', 'oracle' ) ) ) {
$this->db->dropTable( $tbl, __METHOD__ );
wfDebug( __METHOD__." dropping {$newTableName}\n", true);
# Create new table
wfDebug( __METHOD__." duplicating $oldTableName to $newTableName\n", true );
$this->db->duplicateTableStructure( $oldTableName, $newTableName, $this->useTemporaryTables );
-
}
-
}
/**
*/
private $mTrxDoneWrites = false;
+ /**
+ * Record if the current transaction was started implicitly due to DBO_TRX being set.
+ *
+ * @var Bool
+ * @see DatabaseBase::mTrxLevel
+ */
+ private $mTrxAutomatic = false;
+
# ------------------------------------------------------------------------------
# Accessors
# ------------------------------------------------------------------------------
$this->mOpened = false;
if ( $this->mConn ) {
if ( $this->trxLevel() ) {
- $this->commit( __METHOD__ );
+ if ( !$this->mTrxAutomatic ) {
+ wfWarn( "Transaction still in progress (from {$this->mTrxFname}), " .
+ " performing implicit commit before closing connection!" );
+ }
+
+ $this->commit( __METHOD__, 'flush' );
}
+
$ret = $this->closeConnection();
$this->mConn = false;
return $ret;
/**
* @param $error String: fallback error message, used if none is given by DB
+ * @throws DBConnectionError
*/
function reportConnectionError( $error = 'Unknown error' ) {
$myError = $this->lastError();
* comment (you can use __METHOD__ or add some extra info)
* @param $tempIgnore Boolean: Whether to avoid throwing an exception on errors...
* maybe best to catch the exception instead?
+ * @throws MWException
* @return boolean|ResultWrapper. true for a successful write query, ResultWrapper object
* for a successful read query, or false on failure if $tempIgnore set
- * @throws DBQueryError Thrown when the database returns an error of any kind
*/
public function query( $sql, $fname = '', $tempIgnore = false ) {
$isMaster = !is_null( $this->getLBInfo( 'master' ) );
wfDebug("Implicit transaction start.\n");
}
$this->begin( __METHOD__ . " ($fname)" );
+ $this->mTrxAutomatic = true;
}
}
if ( false === $ret && $this->wasErrorReissuable() ) {
# Transaction is gone, like it or not
$this->mTrxLevel = 0;
+ $this->trxIdleCallbacks = array(); // cancel
wfDebug( "Connection lost, reconnecting...\n" );
if ( $this->ping() ) {
* @param $sql String
* @param $fname String
* @param $tempIgnore Boolean
+ * @throws DBQueryError
*/
public function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) {
# Ignore errors during error handling to avoid infinite recursion
* while we're doing this.
*
* @param $matches Array
+ * @throws DBUnexpectedError
* @return String
*/
protected function fillPreparedArg( $matches ) {
/**
* Makes an encoded list of strings from an array
* @param $a Array containing the data
- * @param $mode int Constant
+ * @param int $mode Constant
* - LIST_COMMA: comma separated, no field names
* - LIST_AND: ANDed WHERE clause (without the WHERE). See
* the documentation for $conds in DatabaseBase::select().
* - LIST_SET: comma separated with field names, like a SET clause
* - LIST_NAMES: comma separated field names
*
+ * @throws MWException|DBUnexpectedError
* @return string
*/
public function makeList( $a, $mode = LIST_COMMA ) {
* ANDed together in the WHERE clause
* @param $fname String: Calling function name (use __METHOD__) for
* logs/profiling
+ * @throws DBUnexpectedError
*/
public function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds,
$fname = 'DatabaseBase::deleteJoin' )
* the format. Use $conds == "*" to delete all rows
* @param $fname String name of the calling function
*
+ * @throws DBUnexpectedError
* @return bool
*/
public function delete( $table, $conds, $fname = 'DatabaseBase::delete' ) {
* @param $limit Integer the SQL limit
* @param $offset Integer|bool the SQL offset (default false)
*
+ * @throws DBUnexpectedError
* @return string
*/
public function limitResult( $sql, $limit, $offset = false ) {
* Note that when the DBO_TRX flag is set (which is usually the case for web requests, but not for maintenance scripts),
* any previous database query will have started a transaction automatically.
*
- * Nesting of transactions is not supported. Attempts to nest transactions will cause warnings if DBO_TRX is not set
- * or the extsting transaction contained write operations.
+ * Nesting of transactions is not supported. Attempts to nest transactions will cause a warning, unless the current
+ * transaction was started automatically because of the DBO_TRX flag.
*
* @param $fname string
*/
global $wgDebugDBTransactions;
if ( $this->mTrxLevel ) { // implicit commit
- if ( $this->mTrxDoneWrites || ( $this->mFlags & DBO_TRX ) === 0 ) {
- // In theory, we should always warn about nesting BEGIN statements.
- // However, it is sometimes hard to avoid so we only warn if:
- //
- // a) the transaction has done writes. This gives warnings about bad transactions
- // that could cause partial writes but not about read queries seeing more
- // than one DB snapshot (when in REPEATABLE-READ) due to nested BEGINs.
- //
- // b) the DBO_TRX flag is not set. Explicit transactions should always be properly
- // started and comitted.
- /*wfWarn( "$fname: Transaction already in progress (from {$this->mTrxFname}), " .
- " performing implicit commit!" );*/
- } elseif ( $wgDebugDBTransactions ) {
- wfDebug( "$fname: Transaction already in progress (from {$this->mTrxFname}), " .
- " performing implicit commit!\n" );
+ if ( !$this->mTrxAutomatic ) {
+ // We want to warn about inadvertently nested begin/commit pairs, but not about auto-committing
+ // implicit transactions that were started by query() because DBO_TRX was set.
+
+ wfWarn( "$fname: Transaction already in progress (from {$this->mTrxFname}), " .
+ " performing implicit commit!" );
+ } else {
+ // if the transaction was automatic and has done write operations,
+ // log it if $wgDebugDBTransactions is enabled.
+
+ if ( $this->mTrxDoneWrites && $wgDebugDBTransactions ) {
+ wfDebug( "$fname: Automatic transaction with writes in progress (from {$this->mTrxFname}), " .
+ " performing implicit commit!\n" );
+ }
}
$this->doCommit( $fname );
$this->doBegin( $fname );
$this->mTrxFname = $fname;
$this->mTrxDoneWrites = false;
+ $this->mTrxAutomatic = false;
}
/**
* Nesting of transactions is not supported.
*
* @param $fname string
- */
- final public function commit( $fname = 'DatabaseBase::commit' ) {
- if ( !$this->mTrxLevel ) {
- wfWarn( "$fname: No transaction to commit, something got out of sync!" );
+ * @param $flush String Flush flag, set to 'flush' to disable warnings about explicitly committing implicit
+ * transactions, or calling commit when no transaction is in progress.
+ * This will silently break any ongoing explicit transaction. Only set the flush flag if you are sure
+ * that it is safe to ignore these warnings in your context.
+ */
+ final public function commit( $fname = 'DatabaseBase::commit', $flush = '' ) {
+ if ( $flush != 'flush' ) {
+ if ( !$this->mTrxLevel ) {
+ wfWarn( "$fname: No transaction to commit, something got out of sync!" );
+ } elseif( $this->mTrxAutomatic ) {
+ wfWarn( "$fname: Explicit commit of implicit transaction. Something may be out of sync!" );
+ }
+ } else {
+ if ( !$this->mTrxLevel ) {
+ return; // nothing to do
+ } elseif( !$this->mTrxAutomatic ) {
+ wfWarn( "$fname: Flushing an explicit transaction, getting out of sync!" );
+ }
}
+
$this->doCommit( $fname );
$this->runOnTransactionIdleCallbacks();
}
* @param $newName String: name of table to be created
* @param $temporary Boolean: whether the new table should be temporary
* @param $fname String: calling function name
+ * @throws MWException
* @return Boolean: true if operation was successful
*/
public function duplicateTableStructure( $oldName, $newName, $temporary = false,
*
* @param $prefix string Only show tables with this prefix, e.g. mw_
* @param $fname String: calling function name
+ * @throws MWException
*/
function listTables( $prefix = null, $fname = 'DatabaseBase::listTables' ) {
throw new MWException( 'DatabaseBase::listTables is not implemented in descendant class' );
* on object's error ignore settings).
*
* @param $filename String: File name to open
- * @param $lineCallback Callback: Optional function called before reading each line
- * @param $resultCallback Callback: Optional function called for each MySQL result
- * @param $fname String: Calling function name or false if name should be
+ * @param bool|callable $lineCallback Optional function called before reading each line
+ * @param bool|callable $resultCallback Optional function called for each MySQL result
+ * @param bool|string $fname Calling function name or false if name should be
* generated dynamically using $filename
+ * @throws MWException
* @return bool|string
*/
public function sourceFile(
* @param $user String
* @param $password String
* @param $dbName String: database name
+ * @throws DBConnectionError
* @return DatabaseBase a fresh connection
*/
public function open( $server, $user, $password, $dbName ) {
/**
* The DBMS-dependent part of query()
* @param $sql String: SQL query.
+ * @throws DBUnexpectedError
* @return object Result object for fetch functions or false on failure
*/
protected function doQuery( $sql ) {
* LIST_SET - comma separated with field names, like a SET clause
* LIST_NAMES - comma separated field names
* LIST_SET_PREPARED - like LIST_SET, except with ? tokens as values
+ * @param array $a
+ * @param int $mode
+ * @throws DBUnexpectedError
* @return string
*/
function makeList( $a, $mode = LIST_COMMA ) {
*
* @param $sql string SQL query we will append the limit too
* @param $limit integer the SQL limit
- * @param $offset integer the SQL offset (default false)
+ * @param bool|int $offset SQL offset (default false)
+ * @throws DBUnexpectedError
* @return string
*/
public function limitResult( $sql, $limit, $offset=false ) {
* DELETE query wrapper
*
* Use $conds == "*" to delete all rows
+ * @param array $table
+ * @param array|string $conds
+ * @param string $fname
+ * @throws DBUnexpectedError
* @return bool|\ResultWrapper
*/
public function delete( $table, $conds, $fname = 'DatabaseIbm_db2::delete' ) {
/**
* Frees memory associated with a statement resource
* @param $res Object: statement resource to free
+ * @throws DBUnexpectedError
* @return Boolean success or failure
*/
public function freeResult( $res ) {
/**
* Usually aborts on failure
+ * @param String $server
+ * @param String $user
+ * @param String $password
+ * @param String $dbName
+ * @throws DBConnectionError
* @return bool|DatabaseBase|null
*/
function open( $server, $user, $password, $dbName ) {
*
* Usually aborts on failure
* If errors are explicitly ignored, returns success
+ * @param String $table
+ * @param Array $arrToInsert
+ * @param string $fname
+ * @param array $options
+ * @throws DBQueryError
* @return bool
*/
function insert( $table, $arrToInsert, $fname = 'DatabaseMssql::insert', $options = array() ) {
* Source items may be literals rather than field names, but strings should be quoted with Database::addQuotes()
* $conds may be "*" to copy the whole table
* srcTable may be an array of tables.
+ * @param string $destTable
+ * @param array|string $srcTable
+ * @param array $varMap
+ * @param array $conds
+ * @param string $fname
+ * @param array $insertOptions
+ * @param array $selectOptions
+ * @throws DBQueryError
* @return null|\ResultWrapper
*/
function insertSelect( $destTable, $srcTable, $varMap, $conds, $fname = 'DatabaseMssql::insertSelect',
* Escapes a identifier for use inm SQL.
* Throws an exception if it is invalid.
* Reference: http://msdn.microsoft.com/en-us/library/aa224033%28v=SQL.80%29.aspx
+ * @param $identifier
+ * @throws MWException
* @return string
*/
private function escapeIdentifier( $identifier ) {
* @param $delVar string
* @param $joinVar string
* @param $conds array|string
- * @param $fname bool
+ * @param bool|string $fname bool
+ * @throws DBUnexpectedError
* @return bool|ResultWrapper
*/
function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = 'DatabaseBase::deleteJoin' ) {
/**
* Usually aborts on failure
+ * @param string $server
+ * @param string $user
+ * @param string $password
+ * @param string $dbName
+ * @throws DBConnectionError
* @return DatabaseBase|null
*/
function open( $server, $user, $password, $dbName ) {
/**
* Usually aborts on failure
+ * @param string $server
+ * @param string $user
+ * @param string $password
+ * @param string $dbName
+ * @throws DBConnectionError
* @return DatabaseBase|null
*/
function open( $server, $user, $password, $dbName ) {
/** Open an SQLite database and return a resource handle to it
* NOTE: only $dbName is used, the other parameters are irrelevant for SQLite databases
*
- * @param $server
- * @param $user
- * @param $pass
- * @param $dbName
+ * @param string $server
+ * @param string $user
+ * @param string $pass
+ * @param string $dbName
*
+ * @throws DBConnectionError
* @return PDO
*/
function open( $server, $user, $pass, $dbName ) {
*
* @param $fileName string
*
+ * @throws DBConnectionError
* @return PDO|bool SQL connection or false if failed
*/
function openFile( $fileName ) {
*/
public function setReadDb( $db );
+
+ /**
+ * Get the ID of the any foreign wiki to use as a target for database operations
+ *
+ * @since 1.20
+ *
+ * @return String|bool The target wiki, in a form that LBFactory understands (or false if the local wiki is used)
+ */
+ public function getTargetWiki();
+
+ /**
+ * Set the ID of the any foreign wiki to use as a target for database operations
+ *
+ * @param String|bool $wiki The target wiki, in a form that LBFactory understands (or false if the local wiki shall be used)
+ *
+ * @since 1.20
+ */
+ public function setTargetWiki( $wiki );
+
+ /**
+ * Get the database type used for read operations.
+ * This is to be used instead of wfGetDB.
+ *
+ * @see LoadBalancer::getConnection
+ *
+ * @since 1.20
+ *
+ * @return DatabaseBase The database object
+ */
+ public function getReadDbConnection();
+
+ /**
+ * Get the database type used for read operations.
+ * This is to be used instead of wfGetDB.
+ *
+ * @see LoadBalancer::getConnection
+ *
+ * @since 1.20
+ *
+ * @return DatabaseBase The database object
+ */
+ public function getWriteDbConnection();
+
+ /**
+ * Get the database type used for read operations.
+ *
+ * @see wfGetLB
+ *
+ * @since 1.20
+ *
+ * @return LoadBalancer The database load balancer object
+ */
+ public function getLoadBalancer();
+
+ /**
+ * Releases the lease on the given database connection. This is useful mainly
+ * for connections to a foreign wiki. It does nothing for connections to the local wiki.
+ *
+ * @see LoadBalancer::reuseConnection
+ *
+ * @param DatabaseBase $db the database
+ *
+ * @since 1.20
+ */
+ public function releaseConnection( DatabaseBase $db );
+
/**
* Update the records matching the provided conditions by
* setting the fields that are keys in the $values param to
/**
* @param $conf array
+ * @throws MWException
*/
function __construct( $conf ) {
$this->chronProt = new ChronologyProtector;
}
/**
- * @param $cluster
- * @param $wiki
+ * @param String $cluster
+ * @param bool $wiki
+ * @throws MWException
* @return LoadBalancer
*/
function newExternalLB( $cluster, $wiki = false ) {
* servers Required. Array of server info structures.
* masterWaitTimeout Replication lag wait timeout
* loadMonitor Name of a class used to fetch server lag and load.
+ * @throws MWException
*/
function __construct( $params ) {
if ( !isset( $params['servers'] ) ) {
* Side effect: opens connections to databases
* @param $group bool
* @param $wiki bool
+ * @throws MWException
* @return bool|int|string
*/
function getReaderIndex( $group = false, $wiki = false ) {
*
* @param $i Integer: server index
* @param $groups Array: query groups
- * @param $wiki String: wiki ID
+ * @param bool|string $wiki Wiki ID
*
+ * @throws MWException
* @return DatabaseBase
*/
public function &getConnection( $i, $groups = array(), $wiki = false ) {
* the same number of times as getConnection() to work.
*
* @param DatabaseBase $conn
+ * @throws MWException
*/
public function reuseConnection( $conn ) {
$serverIndex = $conn->getLBInfo('serverIndex');
*
* @param $server
* @param $dbNameOverride bool
+ * @throws MWException
* @return DatabaseBase
*/
function reallyOpenConnection( $server, $dbNameOverride = false ) {
foreach ( $this->mConns as $conns2 ) {
foreach ( $conns2 as $conns3 ) {
foreach ( $conns3 as $conn ) {
- $conn->commit( __METHOD__ );
+ if ( $conn->trxLevel() ) {
+ $conn->commit( __METHOD__, 'flush' );
+ }
}
}
}
}
foreach ( $conns2[$masterIndex] as $conn ) {
if ( $conn->trxLevel() && $conn->doneWrites() ) {
- $conn->commit( __METHOD__ );
+ $conn->commit( __METHOD__, 'flush' );
}
}
}
protected static $instanceCache = array();
/**
- * The database connection to use for read operations.
+ * ID of the database connection to use for read operations.
* Can be changed via @see setReadDb.
*
* @since 1.20
*/
protected $readDb = DB_SLAVE;
+ /**
+ * The ID of any foreign wiki to use as a target for database operations,
+ * or false to use the local wiki.
+ *
+ * @since 1.20
+ * @var String|bool
+ */
+ protected $wiki = false;
+
/**
* Returns a list of default field values.
* field name => field value
$fields = (array)$fields;
}
- return wfGetDB( $this->getReadDb() )->select(
+ $dbr = $this->getReadDbConnection();
+ $result = $dbr->select(
$this->getName(),
$this->getPrefixedFields( $fields ),
$this->getPrefixedValues( $conditions ),
is_null( $functionName ) ? __METHOD__ : $functionName,
$options
);
+
+ $this->releaseConnection( $dbr );
+ return $result;
}
/**
*/
public function rawSelectRow( array $fields, array $conditions = array(),
array $options = array(), $functionName = null ) {
- $dbr = wfGetDB( $this->getReadDb() );
+ $dbr = $this->getReadDbConnection();
- return $dbr->selectRow(
+ $result = $dbr->selectRow(
$this->getName(),
$fields,
$conditions,
is_null( $functionName ) ? __METHOD__ : $functionName,
$options
);
+
+ $this->releaseConnection( $dbr );
+ return $result;
}
/**
* @return boolean Success indicator
*/
public function delete( array $conditions, $functionName = null ) {
- return wfGetDB( DB_MASTER )->delete(
+ $dbw = $this->getWriteDbConnection();
+
+ $result = $dbw->delete(
$this->getName(),
$conditions === array() ? '*' : $this->getPrefixedValues( $conditions ),
$functionName
) !== false; // DatabaseBase::delete does not always return true for success as documented...
+
+ $this->releaseConnection( $dbw );
+ return $result;
}
-
+
/**
* Get API parameters for the fields supported by this object.
*
}
/**
- * Get the database type used for read operations.
+ * Get the database ID used for read operations.
*
* @since 1.20
*
}
/**
- * Set the database type to use for read operations.
+ * Set the database ID to use for read operations, use DB_XXX constants or an index to the load balancer setup.
*
* @param integer $db
*
$this->readDb = $db;
}
+ /**
+ * Get the ID of the any foreign wiki to use as a target for database operations
+ *
+ * @since 1.20
+ *
+ * @return String|bool The target wiki, in a form that LBFactory understands (or false if the local wiki is used)
+ */
+ public function getTargetWiki() {
+ return $this->wiki;
+ }
+
+ /**
+ * Set the ID of the any foreign wiki to use as a target for database operations
+ *
+ * @param String|bool $wiki The target wiki, in a form that LBFactory understands (or false if the local wiki shall be used)
+ *
+ * @since 1.20
+ */
+ public function setTargetWiki( $wiki ) {
+ $this->wiki = $wiki;
+ }
+
+ /**
+ * Get the database type used for read operations.
+ * This is to be used instead of wfGetDB.
+ *
+ * @see LoadBalancer::getConnection
+ *
+ * @since 1.20
+ *
+ * @return DatabaseBase The database object
+ */
+ public function getReadDbConnection() {
+ return $this->getLoadBalancer()->getConnection( $this->getReadDb(), array(), $this->getTargetWiki() );
+ }
+
+ /**
+ * Get the database type used for read operations.
+ * This is to be used instead of wfGetDB.
+ *
+ * @see LoadBalancer::getConnection
+ *
+ * @since 1.20
+ *
+ * @return DatabaseBase The database object
+ */
+ public function getWriteDbConnection() {
+ return $this->getLoadBalancer()->getConnection( DB_MASTER, array(), $this->getTargetWiki() );
+ }
+
+ /**
+ * Get the database type used for read operations.
+ *
+ * @see wfGetLB
+ *
+ * @since 1.20
+ *
+ * @return LoadBalancer The database load balancer object
+ */
+ public function getLoadBalancer() {
+ return wfGetLB( $this->getTargetWiki() );
+ }
+
+ /**
+ * Releases the lease on the given database connection. This is useful mainly
+ * for connections to a foreign wiki. It does nothing for connections to the local wiki.
+ *
+ * @see LoadBalancer::reuseConnection
+ *
+ * @param DatabaseBase $db the database
+ *
+ * @since 1.20
+ */
+ public function releaseConnection( DatabaseBase $db ) {
+ if ( $this->wiki !== false ) {
+ // recycle connection to foreign wiki
+ $this->getLoadBalancer()->reuseConnection( $db );
+ }
+ }
+
/**
* Update the records matching the provided conditions by
* setting the fields that are keys in the $values param to
* @return boolean Success indicator
*/
public function update( array $values, array $conditions = array() ) {
- $dbw = wfGetDB( DB_MASTER );
+ $dbw = $this->getWriteDbConnection();
- return $dbw->update(
+ $result = $dbw->update(
$this->getName(),
$this->getPrefixedValues( $values ),
$this->getPrefixedValues( $conditions ),
__METHOD__
) !== false; // DatabaseBase::update does not always return true for success as documented...
+
+ $this->releaseConnection( $dbw );
+ return $result;
}
/**
* @param array $conditions
*/
public function updateSummaryFields( $summaryFields = null, array $conditions = array() ) {
+ $slave = $this->getReadDb();
$this->setReadDb( DB_MASTER );
/**
$item->save();
}
- $this->setReadDb( DB_SLAVE );
+ $this->setReadDb( $slave );
}
/**
* @private
*/
var $mOldid, $mNewid;
- var $mOldtext, $mNewtext;
+ var $mOldContent, $mNewContent;
protected $mDiffLang;
/**
# we'll use the application/x-external-editor interface to call
# an external diff tool like kompare, kdiff3, etc.
if ( ExternalEdit::useExternalEngine( $this->getContext(), 'diff' ) ) {
+ //TODO: come up with a good solution for non-text content here.
+ // at least, the content format needs to be passed to the client somehow.
+ // Currently, action=raw will just fail for non-text content.
+
$urls = array(
'File' => array( 'Extension' => 'wiki', 'URL' =>
# This should be mOldPage, but it may not be set, see below.
$out->setRevisionTimestamp( $this->mNewRev->getTimestamp() );
$out->setArticleFlag( true );
+ // NOTE: only needed for B/C: custom rendering of JS/CSS via hook
if ( $this->mNewPage->isCssJsSubpage() || $this->mNewPage->isCssOrJsPage() ) {
// Stolen from Article::view --AG 2007-10-11
// Give hooks a chance to customise the output
// @TODO: standardize this crap into one function
- if ( wfRunHooks( 'ShowRawCssJs', array( $this->mNewtext, $this->mNewPage, $out ) ) ) {
- // Wrap the whole lot in a <pre> and don't parse
- $m = array();
- preg_match( '!\.(css|js)$!u', $this->mNewPage->getText(), $m );
- $out->addHTML( "<pre class=\"mw-code mw-{$m[1]}\" dir=\"ltr\">\n" );
- $out->addHTML( htmlspecialchars( $this->mNewtext ) );
- $out->addHTML( "\n</pre>\n" );
+ if ( ContentHandler::runLegacyHooks( 'ShowRawCssJs', array( $this->mNewContent, $this->mNewPage, $out ) ) ) {
+ // NOTE: deprecated hook, B/C only
+ // use the content object's own rendering
+ $po = $this->mNewRev->getContent()->getParserOutput( $this->mNewRev->getTitle(), $this->mNewRev->getId() );
+ $out->addHTML( $po->getText() );
}
- } elseif ( !wfRunHooks( 'ArticleViewCustom', array( $this->mNewtext, $this->mNewPage, $out ) ) ) {
+ } elseif( !wfRunHooks( 'ArticleContentViewCustom', array( $this->mNewContent, $this->mNewPage, $out ) ) ) {
+ // Handled by extension
+ } elseif( !ContentHandler::runLegacyHooks( 'ArticleViewCustom', array( $this->mNewContent, $this->mNewPage, $out ) ) ) {
+ // NOTE: deprecated hook, B/C only
// Handled by extension
} else {
// Normal page
$wikiPage = WikiPage::factory( $this->mNewPage );
}
- $parserOptions = $wikiPage->makeParserOptions( $this->getContext() );
+ $parserOutput = $this->getParserOutput( $wikiPage, $this->mNewRev );
- if ( !$this->mNewRev->isCurrent() ) {
- $parserOptions->setEditSection( false );
- }
+ # Also try to load it as a redirect
+ $rt = $this->mNewContent->getRedirectTarget();
- $parserOutput = $wikiPage->getParserOutput( $parserOptions, $this->mNewid );
+ if ( $rt ) {
+ $article = Article::newFromTitle( $this->mNewPage, $this->getContext() );
+ $out->addHTML( $article->viewRedirect( $rt ) );
- # WikiPage::getParserOutput() should not return false, but just in case
- if( $parserOutput ) {
+ # WikiPage::getParserOutput() should not return false, but just in case
+ if ( $parserOutput ) {
+ # Show categories etc.
+ $out->addParserOutputNoText( $parserOutput );
+ }
+ } else if ( $parserOutput ) {
$out->addParserOutput( $parserOutput );
}
}
wfProfileOut( __METHOD__ );
}
+ protected function getParserOutput( WikiPage $page, Revision $rev ) {
+ $parserOptions = $page->makeParserOptions( $this->getContext() );
+
+ if ( !$rev->isCurrent() || !$rev->getTitle()->quickUserCan( "edit" ) ) {
+ $parserOptions->setEditSection( false );
+ }
+
+ $parserOutput = $page->getParserOutput( $parserOptions, $rev->getId() );
+ return $parserOutput;
+ }
+
/**
* Get the diff text, send it to the OutputPage object
* Returns false if the diff could not be generated, otherwise returns true
return false;
}
- $difftext = $this->generateDiffBody( $this->mOldtext, $this->mNewtext );
+ $difftext = $this->generateContentDiffBody( $this->mOldContent, $this->mNewContent );
// Save to cache for 7 days
if ( !wfRunHooks( 'AbortDiffCache', array( &$this ) ) ) {
}
}
+ /**
+ * Generate a diff, no caching.
+ *
+ * This implementation uses generateTextDiffBody() to generate a diff based on the default
+ * serialization of the given Content objects. This will fail if $old or $new are not
+ * instances of TextContent.
+ *
+ * Subclasses may override this to provide a different rendering for the diff,
+ * perhaps taking advantage of the content's native form. This is required for all content
+ * models that are not text based.
+ *
+ * @param $old Content: old content
+ * @param $new Content: new content
+ *
+ * @since 1.21
+ * @throws MWException if $old or $new are not instances of TextContent.
+ */
+ function generateContentDiffBody( Content $old, Content $new ) {
+ if ( !( $old instanceof TextContent ) ) {
+ throw new MWException( "Diff not implemented for " . get_class( $old ) . "; "
+ . "override generateContentDiffBody to fix this." );
+ }
+
+ if ( !( $new instanceof TextContent ) ) {
+ throw new MWException( "Diff not implemented for " . get_class( $new ) . "; "
+ . "override generateContentDiffBody to fix this." );
+ }
+
+ $otext = $old->serialize();
+ $ntext = $new->serialize();
+
+ return $this->generateTextDiffBody( $otext, $ntext );
+ }
+
/**
* Generate a diff, no caching
*
* @param $otext String: old text, must be already segmented
* @param $ntext String: new text, must be already segmented
- * @return bool|string
+ * @deprecated since 1.21, use generateContentDiffBody() instead!
*/
function generateDiffBody( $otext, $ntext ) {
+ ContentHandler::deprecated( __METHOD__, "1.21" );
+
+ return $this->generateTextDiffBody( $otext, $ntext );
+ }
+
+ /**
+ * Generate a diff, no caching
+ *
+ * @todo move this to TextDifferenceEngine, make DifferenceEngine abstract. At some point.
+ *
+ * @param $otext String: old text, must be already segmented
+ * @param $ntext String: new text, must be already segmented
+ * @return bool|string
+ */
+ function generateTextDiffBody( $otext, $ntext ) {
global $wgExternalDiffEngine, $wgContLang;
wfProfileIn( __METHOD__ );
* the visibility of the revision and a link to edit the page.
* @return String HTML fragment
*/
- private function getRevisionHeader( Revision $rev, $complete = '' ) {
+ protected function getRevisionHeader( Revision $rev, $complete = '' ) {
$lang = $this->getLanguage();
$user = $this->getUser();
$revtimestamp = $rev->getTimestamp();
/**
* Use specified text instead of loading from the database
+ * @deprecated since 1.21, use setContent() instead.
*/
function setText( $oldText, $newText ) {
- $this->mOldtext = $oldText;
- $this->mNewtext = $newText;
+ ContentHandler::deprecated( __METHOD__, "1.21" );
+
+ $oldContent = ContentHandler::makeContent( $oldText, $this->getTitle() );
+ $newContent = ContentHandler::makeContent( $newText, $this->getTitle() );
+
+ $this->setContent( $oldContent, $newContent );
+ }
+
+ /**
+ * Use specified text instead of loading from the database
+ * @since 1.21
+ */
+ function setContent( Content $oldContent, Content $newContent ) {
+ $this->mOldContent = $oldContent;
+ $this->mNewContent = $newContent;
+
$this->mTextLoaded = 2;
$this->mRevisionsLoaded = true;
}
return false;
}
if ( $this->mOldRev ) {
- $this->mOldtext = $this->mOldRev->getText( Revision::FOR_THIS_USER, $this->getUser() );
- if ( $this->mOldtext === false ) {
+ $this->mOldContent = $this->mOldRev->getContent( Revision::FOR_THIS_USER, $this->getUser() );
+ if ( $this->mOldContent === false ) {
return false;
}
}
if ( $this->mNewRev ) {
- $this->mNewtext = $this->mNewRev->getText( Revision::FOR_THIS_USER, $this->getUser() );
- if ( $this->mNewtext === false ) {
+ $this->mNewContent = $this->mNewRev->getContent( Revision::FOR_THIS_USER, $this->getUser() );
+ if ( $this->mNewContent === false ) {
return false;
}
}
if ( !$this->loadRevisionData() ) {
return false;
}
- $this->mNewtext = $this->mNewRev->getText( Revision::FOR_THIS_USER, $this->getUser() );
+ $this->mNewContent = $this->mNewRev->getContent( Revision::FOR_THIS_USER, $this->getUser() );
return true;
}
}
return "mwstore://{$this->name}";
}
+ /**
+ * Get the storage path for the given container for this backend
+ *
+ * @param $container string Container name
+ * @return string Storage path
+ * @since 1.21
+ */
+ final public function getContainerStoragePath( $container ) {
+ return $this->getRootStoragePath() . "/{$container}";
+ }
+
/**
* Get the file journal object for this backend
*
*
* @param $type string One of (attachment, inline)
* @param $filename string Suggested file name (should not contain slashes)
+ * @throws MWException
* @return string
* @since 1.20
*/
/**
* @see FileBackend::fileExists()
* @param $params array
+ * @return bool|null
*/
public function fileExists( array $params ) {
$realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
/**
* @see FileBackendStore::executeOpHandlesInternal()
+ * @param array $fileOpHandles
+ * @throws MWException
* @return Array List of corresponding Status objects
*/
protected function doExecuteOpHandlesInternal( array $fileOpHandles ) {
* Clean up the temporary file only after an object goes out of scope
*
* @param $object Object
- * @return void
+ * @return TempFSFile This object
*/
public function bind( $object ) {
if ( is_object( $object ) ) {
+ if ( !isset( $object->tempFSFileReferences ) ) {
+ // Init first since $object might use __get() and return only a copy variable
+ $object->tempFSFileReferences = array();
+ }
$object->tempFSFileReferences[] = $this;
}
+ return $this;
}
/**
* Set flag to not clean up after the temporary file
*
- * @return void
+ * @return TempFSFile This object
*/
public function preserve() {
$this->canDelete = false;
+ return $this;
}
/**
* Set flag clean up after the temporary file
*
- * @return void
+ * @return TempFSFile This object
*/
public function autocollect() {
$this->canDelete = true;
+ return $this;
}
/**
* - wikiId : Wiki ID string that all resources are relative to. [optional]
*
* @param Array $config
+ * @throws MWException
*/
public function __construct( array $config ) {
parent::__construct( $config );
* e.g. s/z/a/ for sza251lrxrc1jad41h5mgilp8nysje52.jpg
*
* @param $key string
+ * @throws MWException
* @return string
*/
public function getDeletedHashPath( $key ) {
$timestamp, # time of upload
$dataLoaded, # Whether or not all this has been loaded from the database (loadFromXxx)
$deleted, # Bitfield akin to rev_deleted
+ $sha1, # sha1 hash of file content
$pageCount,
$archive_name;
$this->deleted = 0;
$this->dataLoaded = false;
$this->exists = false;
+ $this->sha1 = '';
if( $title instanceof Title ) {
$this->title = File::normalizeTitle( $title, 'exception' );
/**
* Loads a file object from the filearchive table
+ * @throws MWException
* @return bool|null True on success or null
*/
public function load() {
'fa_user',
'fa_user_text',
'fa_timestamp',
- 'fa_deleted' ),
+ 'fa_deleted',
+ 'fa_sha1' ),
$conds,
__METHOD__,
array( 'ORDER BY' => 'fa_timestamp DESC' ) );
$row = $ret->fetchObject();
// initialize fields for filestore image object
- $this->id = intval($row->fa_id);
- $this->name = $row->fa_name;
- $this->archive_name = $row->fa_archive_name;
- $this->group = $row->fa_storage_group;
- $this->key = $row->fa_storage_key;
- $this->size = $row->fa_size;
- $this->bits = $row->fa_bits;
- $this->width = $row->fa_width;
- $this->height = $row->fa_height;
- $this->metadata = $row->fa_metadata;
- $this->mime = "$row->fa_major_mime/$row->fa_minor_mime";
- $this->media_type = $row->fa_media_type;
- $this->description = $row->fa_description;
- $this->user = $row->fa_user;
- $this->user_text = $row->fa_user_text;
- $this->timestamp = $row->fa_timestamp;
- $this->deleted = $row->fa_deleted;
+ $this->loadFromRow( $row );
} else {
throw new MWException( 'This title does not correspond to an image page.' );
}
*/
public static function newFromRow( $row ) {
$file = new ArchivedFile( Title::makeTitle( NS_FILE, $row->fa_name ) );
-
- $file->id = intval($row->fa_id);
- $file->name = $row->fa_name;
- $file->archive_name = $row->fa_archive_name;
- $file->group = $row->fa_storage_group;
- $file->key = $row->fa_storage_key;
- $file->size = $row->fa_size;
- $file->bits = $row->fa_bits;
- $file->width = $row->fa_width;
- $file->height = $row->fa_height;
- $file->metadata = $row->fa_metadata;
- $file->mime = "$row->fa_major_mime/$row->fa_minor_mime";
- $file->media_type = $row->fa_media_type;
- $file->description = $row->fa_description;
- $file->user = $row->fa_user;
- $file->user_text = $row->fa_user_text;
- $file->timestamp = $row->fa_timestamp;
- $file->deleted = $row->fa_deleted;
-
+ $file->loadFromRow( $row );
return $file;
}
+ /**
+ * Load ArchivedFile object fields from a DB row.
+ *
+ * @param $row Object database row
+ * @since 1.21
+ */
+ public function loadFromRow( $row ) {
+ $this->id = intval($row->fa_id);
+ $this->name = $row->fa_name;
+ $this->archive_name = $row->fa_archive_name;
+ $this->group = $row->fa_storage_group;
+ $this->key = $row->fa_storage_key;
+ $this->size = $row->fa_size;
+ $this->bits = $row->fa_bits;
+ $this->width = $row->fa_width;
+ $this->height = $row->fa_height;
+ $this->metadata = $row->fa_metadata;
+ $this->mime = "$row->fa_major_mime/$row->fa_minor_mime";
+ $this->media_type = $row->fa_media_type;
+ $this->description = $row->fa_description;
+ $this->user = $row->fa_user;
+ $this->user_text = $row->fa_user_text;
+ $this->timestamp = $row->fa_timestamp;
+ $this->deleted = $row->fa_deleted;
+ if( isset( $row->fa_sha1 ) ) {
+ $this->sha1 = $row->fa_sha1;
+ } else {
+ // old row, populate from key
+ $this->sha1 = LocalRepo::getHashFromKey( $this->key );
+ }
+ }
+
/**
* Return the associated title object
*
return wfTimestamp( TS_MW, $this->timestamp );
}
+ /**
+ * Get the SHA-1 base 36 hash of the file
+ *
+ * @return string
+ * @since 1.21
+ */
+ function getSha1() {
+ $this->load();
+ return $this->sha1;
+ }
+
/**
* Return the user ID of the uploader.
*
} else {
# New file; create the description page.
# There's already a log entry, so don't make a second RC entry
- # Squid and file cache for the description page are purged by doEdit.
- $status = $wikiPage->doEdit( $pageText, $comment, EDIT_NEW | EDIT_SUPPRESS_RC, false, $user );
+ # Squid and file cache for the description page are purged by doEditContent.
+ $content = ContentHandler::makeContent( $pageText, $descTitle );
+ $status = $wikiPage->doEditContent( $content, $comment, EDIT_NEW | EDIT_SUPPRESS_RC, false, $user );
if ( isset( $status->value['revision'] ) ) { // XXX; doEdit() uses a transaction
$dbw->begin();
global $wgParser;
$revision = Revision::newFromTitle( $this->title, false, Revision::READ_NORMAL );
if ( !$revision ) return false;
- $text = $revision->getText();
- if ( !$text ) return false;
- $pout = $wgParser->parse( $text, $this->title, new ParserOptions() );
+ $content = $revision->getContent();
+ if ( !$content ) return false;
+ $pout = $content->getParserOutput( $this->title, null, new ParserOptions() );
return $pout->getText();
}
'fa_description' => 'img_description',
'fa_user' => 'img_user',
'fa_user_text' => 'img_user_text',
- 'fa_timestamp' => 'img_timestamp'
+ 'fa_timestamp' => 'img_timestamp',
+ 'fa_sha1' => 'img_sha1',
), $where, __METHOD__ );
}
'fa_user' => 'oi_user',
'fa_user_text' => 'oi_user_text',
'fa_timestamp' => 'oi_timestamp',
+ 'fa_sha1' => 'oi_sha1',
), $where, __METHOD__ );
}
}
$deletedRel = $this->file->repo->getDeletedHashPath( $row->fa_storage_key ) . $row->fa_storage_key;
$deletedUrl = $this->file->repo->getVirtualUrl() . '/deleted/' . $deletedRel;
- $sha1 = substr( $row->fa_storage_key, 0, strcspn( $row->fa_storage_key, '.' ) );
+ if( isset( $row->fa_sha1 ) ) {
+ $sha1 = $row->fa_sha1;
+ } else {
+ // old row, populate from key
+ $sha1 = LocalRepo::getHashFromKey( $row->fa_storage_key );
+ }
# Fix leading zero
if ( strlen( $sha1 ) == 32 && $sha1[0] == '0' ) {
protected $shared = false;
+ /**
+ * Scripts to run after database update
+ * Should be a subclass of LoggedUpdateMaintenance
+ */
protected $postDatabaseUpdateMaintenance = array(
'DeleteDefaultMessages',
'PopulateRevisionLength',
'PopulateRevisionSha1',
'PopulateImageSha1',
'FixExtLinksProtocolRelative',
+ 'PopulateFilearchiveSha1',
);
/**
/**
* Add a maintenance script to be run after the database updates are complete.
*
+ * Script should subclass LoggedUpdateMaintenance
+ *
* @since 1.19
*
* @param $class string Name of a Maintenance subclass
array( 'modifyField', 'user_properties', 'up_property', 'patch-up_property.sql' ),
array( 'addTable', 'uploadstash', 'patch-uploadstash.sql' ),
array( 'addTable', 'user_former_groups', 'patch-user_former_groups.sql'),
- array( 'doRebuildLocalisationCache' ),
+ array( 'doRebuildLocalisationCache' ),
// 1.19
array( 'addIndex', 'logging', 'type_action', 'patch-logging-type-action-index.sql'),
array( 'addTable', 'config', 'patch-config.sql' ),
// 1.21
+ array( 'addField', 'revision', 'rev_content_format', 'patch-revision-rev_content_format.sql' ),
+ array( 'addField', 'revision', 'rev_content_model', 'patch-revision-rev_content_model.sql' ),
+ array( 'addField', 'archive', 'ar_content_format', 'patch-archive-ar_content_format.sql' ),
+ array( 'addField', 'archive', 'ar_content_model', 'patch-archive-ar_content_model.sql' ),
+ array( 'addField', 'page', 'page_content_model', 'patch-page-page_content_model.sql' ),
);
}
}
$status = Status::newGood();
try {
$page = WikiPage::factory( Title::newMainPage() );
- $page->doEdit( wfMessage( 'mainpagetext' )->inContentLanguage()->text() . "\n\n" .
- wfMessage( 'mainpagedocfooter' )->inContentLanguage()->text(),
+ $content = new WikitextContent (
+ wfMessage( 'mainpagetext' )->inContentLanguage()->text() . "\n\n" .
+ wfMessage( 'mainpagedocfooter' )->inContentLanguage()->text()
+ );
+
+ $page->doEditContent( $content,
'',
EDIT_NEW,
false,
- User::newFromName( 'MediaWiki default' )
- );
+ User::newFromName( 'MediaWiki default' ) );
} catch (MWException $e) {
//using raw, because $wgShowExceptionDetails can not be set yet
$status->fatal( 'config-install-mainpage-failed', $e->getMessage() );
array( 'dropField', 'category', 'cat_hidden', 'patch-cat_hidden.sql' ),
// 1.21
+ array( 'addField', 'revision', 'rev_content_format', 'patch-revision-rev_content_format.sql' ),
+ array( 'addField', 'revision', 'rev_content_model', 'patch-revision-rev_content_model.sql' ),
+ array( 'addField', 'archive', 'ar_content_format', 'patch-archive-ar_content_format.sql' ),
+ array( 'addField', 'archive', 'ar_content_model', 'patch-archive-ar_content_model.sql' ),
+ array( 'addField', 'page', 'page_content_model', 'patch-page-page_content_model.sql' ),
array( 'dropField', 'site_stats', 'ss_admins', 'patch-drop-ss_admins.sql' ),
array( 'dropField', 'recentchanges', 'rc_moved_to_title', 'patch-rc_moved.sql' ),
+ array( 'addTable', 'sites', 'patch-sites.sql' ),
+ array( 'addField', 'filearchive', 'fa_sha1', 'patch-fa_sha1.sql' ),
+ array( 'addField', 'job', 'job_token', 'patch-job_token.sql' ),
);
}
protected $internalDefaults = array(
'_OracleDefTS' => 'USERS',
'_OracleTempTS' => 'TEMP',
- '_InstallUser' => 'SYSDBA',
+ '_InstallUser' => 'SYSTEM',
);
public $minimumVersion = '9.0.1'; // 9iR1
array( 'addTable', 'config', 'patch-config.sql' ),
array( 'addIndex', 'ipblocks', 'i05', 'patch-ipblocks_i05_index.sql' ),
array( 'addIndex', 'revision', 'i05', 'patch-revision_i05_index.sql' ),
-
- // 1.21
+ array( 'dropField', 'category', 'cat_hidden', 'patch-cat_hidden.sql' ),
+
+ //1.21
+ array( 'addField', 'revision', 'rev_content_format', 'patch-revision-rev_content_format.sql' ),
+ array( 'addField', 'revision', 'rev_content_model', 'patch-revision-rev_content_model.sql' ),
+ array( 'addField', 'archive', 'ar_content_format', 'patch-archive-ar_content_format.sql' ),
+ array( 'addField', 'archive', 'ar_content_model', 'patch-archive-ar_content_model.sql' ),
+ array( 'addField', 'page', 'page_content_model', 'patch-page-page_content_model.sql' ),
+ array( 'dropField', 'site_stats', 'ss_admins', 'patch-ss_admins.sql' ),
+ array( 'dropField', 'recentchanges', 'rc_moved_to_title', 'patch-rc_moved.sql' ),
// KEEP THIS AT THE BOTTOM!!
array( 'doRebuildDuplicateFunction' ),
* other similar objects in the new DB.
* - create-tables: A connection with a role suitable for creating tables.
*
+ * @throws MWException
* @return Status object. On success, a connection object will be in the
* value member.
*/
array( 'addPgField', 'archive', 'ar_len', 'INTEGER' ),
array( 'addPgField', 'archive', 'ar_page_id', 'INTEGER' ),
array( 'addPgField', 'archive', 'ar_parent_id', 'INTEGER' ),
+ array( 'addPgField', 'archive', 'ar_content_model', 'TEXT' ),
+ array( 'addPgField', 'archive', 'ar_content_format', 'TEXT' ),
array( 'addPgField', 'categorylinks', 'cl_sortkey_prefix', "TEXT NOT NULL DEFAULT ''"),
array( 'addPgField', 'categorylinks', 'cl_collation', "TEXT NOT NULL DEFAULT 0"),
array( 'addPgField', 'categorylinks', 'cl_type', "TEXT NOT NULL DEFAULT 'page'"),
array( 'addPgField', 'oldimage', 'oi_metadata', "BYTEA NOT NULL DEFAULT ''" ),
array( 'addPgField', 'oldimage', 'oi_minor_mime', "TEXT NOT NULL DEFAULT 'unknown'" ),
array( 'addPgField', 'oldimage', 'oi_sha1', "TEXT NOT NULL DEFAULT ''" ),
+ array( 'addPgField', 'page', 'page_content_model', 'TEXT' ),
array( 'addPgField', 'page_restrictions', 'pr_id', "INTEGER NOT NULL UNIQUE DEFAULT nextval('page_restrictions_pr_id_seq')" ),
array( 'addPgField', 'profiling', 'pf_memory', 'NUMERIC(18,10) NOT NULL DEFAULT 0' ),
array( 'addPgField', 'recentchanges', 'rc_deleted', 'SMALLINT NOT NULL DEFAULT 0' ),
array( 'addPgField', 'revision', 'rev_deleted', 'SMALLINT NOT NULL DEFAULT 0' ),
array( 'addPgField', 'revision', 'rev_len', 'INTEGER' ),
array( 'addPgField', 'revision', 'rev_parent_id', 'INTEGER DEFAULT NULL' ),
+ array( 'addPgField', 'revision', 'rev_content_model', 'TEXT' ),
+ array( 'addPgField', 'revision', 'rev_content_format', 'TEXT' ),
array( 'addPgField', 'site_stats', 'ss_active_users', "INTEGER DEFAULT '-1'" ),
array( 'addPgField', 'user_newtalk', 'user_last_timestamp', 'TIMESTAMPTZ' ),
array( 'addPgField', 'logging', 'log_user_text', "TEXT NOT NULL DEFAULT ''" ),
array( 'dropField', 'category', 'cat_hidden', 'patch-cat_hidden.sql' ),
// 1.21
- array( 'dropField', 'site_stats', 'ss_admins', 'patch-drop-ss_admins.sql' ),
+ array( 'addField', 'revision', 'rev_content_format', 'patch-revision-rev_content_format.sql' ),
+ array( 'addField', 'revision', 'rev_content_model', 'patch-revision-rev_content_model.sql' ),
+ array( 'addField', 'archive', 'ar_content_format', 'patch-archive-ar_content_format.sql' ),
+ array( 'addField', 'archive', 'ar_content_model', 'patch-archive-ar_content_model.sql' ),
+ array( 'addField', 'page', 'page_content_model', 'patch-page-page_content_model.sql' ),
+
+ array( 'dropField', 'site_stats', 'ss_admins', 'patch-drop-ss_admins.sql' ),
array( 'dropField', 'recentchanges', 'rc_moved_to_title', 'patch-rc_moved.sql' ),
+ array( 'addTable', 'sites', 'patch-sites.sql' ),
+ array( 'addField', 'filearchive', 'fa_sha1', 'patch-fa_sha1.sql' ),
+ array( 'addField', 'job', 'job_token', 'patch-job_token.sql' ),
);
}
wfDebug( __METHOD__.": target redirect already deleted, ignoring\n" );
return true;
}
- $text = $targetRev->getText();
- $currentDest = Title::newFromRedirect( $text );
+ $content = $targetRev->getContent();
+ $currentDest = $content->getRedirectTarget();
if ( !$currentDest || !$currentDest->equals( $this->redirTitle ) ) {
wfDebug( __METHOD__.": Redirect has changed since the job was queued\n" );
return true;
# Check for a suppression tag (used e.g. in periodically archived discussions)
$mw = MagicWord::get( 'staticredirect' );
- if ( $mw->match( $text ) ) {
+ if ( $content->matchMagicWord( $mw ) ) {
wfDebug( __METHOD__.": skipping: suppressed with __STATICREDIRECT__\n" );
return true;
}
$currentDest->getFragment(), $newTitle->getInterwiki() );
# Fix the text
- # Remember that redirect pages can have categories, templates, etc.,
- # so the regex has to be fairly general
- $newText = preg_replace( '/ \[ \[ [^\]]* \] \] /x',
- '[[' . $newTitle->getFullText() . ']]',
- $text, 1 );
-
- if ( $newText === $text ) {
- $this->setLastError( 'Text unchanged???' );
+ $newContent = $content->updateRedirect( $newTitle );
+
+ if ( $newContent->equals( $content ) ) {
+ $this->setLastError( 'Content unchanged???' );
return false;
}
$reason = wfMessage( 'double-redirect-fixed-' . $this->reason,
$this->redirTitle->getPrefixedText(), $newTitle->getPrefixedText()
)->inContentLanguage()->text();
- $article->doEdit( $newText, $reason, EDIT_UPDATE | EDIT_SUPPRESS_RC, false, $this->getUser() );
+ $article->doEditContent( $newContent, $reason, EDIT_UPDATE | EDIT_SUPPRESS_RC, false, $this->getUser() );
$wgUser = $oldUser;
return true;
/**
* Class to both describe a background job and handle jobs.
+ * The queue aspects of this class are now deprecated.
*
* @ingroup JobQueue
*/
abstract class Job {
-
/**
* @var Title
*/
* Run the job
* @return boolean success
*/
- abstract function run();
+ abstract public function run();
/*-------------------------------------------------------------------------
* Static functions
*------------------------------------------------------------------------*/
- /**
- * Pop a job of a certain type. This tries less hard than pop() to
- * actually find a job; it may be adversely affected by concurrent job
- * runners.
- *
- * @param $type string
- *
- * @return Job
- */
- static function pop_type( $type ) {
- wfProfilein( __METHOD__ );
-
- $dbw = wfGetDB( DB_MASTER );
-
- $dbw->begin( __METHOD__ );
-
- $row = $dbw->selectRow(
- 'job',
- '*',
- array( 'job_cmd' => $type ),
- __METHOD__,
- array( 'LIMIT' => 1, 'FOR UPDATE' )
- );
-
- if ( $row === false ) {
- $dbw->commit( __METHOD__ );
- wfProfileOut( __METHOD__ );
- return false;
- }
-
- /* Ensure we "own" this row */
- $dbw->delete( 'job', array( 'job_id' => $row->job_id ), __METHOD__ );
- $affected = $dbw->affectedRows();
- $dbw->commit( __METHOD__ );
-
- if ( $affected == 0 ) {
- wfProfileOut( __METHOD__ );
- return false;
- }
-
- wfIncrStats( 'job-pop' );
- $namespace = $row->job_namespace;
- $dbkey = $row->job_title;
- $title = Title::makeTitleSafe( $namespace, $dbkey );
- $job = Job::factory( $row->job_cmd, $title, Job::extractBlob( $row->job_params ),
- $row->job_id );
-
- $job->removeDuplicates();
-
- wfProfileOut( __METHOD__ );
- return $job;
- }
-
- /**
- * Pop a job off the front of the queue
- *
- * @param $offset Integer: Number of jobs to skip
- * @return Job or false if there's no jobs
- */
- static function pop( $offset = 0 ) {
- wfProfileIn( __METHOD__ );
-
- $dbr = wfGetDB( DB_SLAVE );
-
- /* Get a job from the slave, start with an offset,
- scan full set afterwards, avoid hitting purged rows
-
- NB: If random fetch previously was used, offset
- will always be ahead of few entries
- */
-
- $conditions = self::defaultQueueConditions();
-
- $offset = intval( $offset );
- $options = array( 'ORDER BY' => 'job_id', 'USE INDEX' => 'PRIMARY' );
-
- $row = $dbr->selectRow( 'job', '*',
- array_merge( $conditions, array( "job_id >= $offset" ) ),
- __METHOD__,
- $options
- );
-
- // Refetching without offset is needed as some of job IDs could have had delayed commits
- // and have lower IDs than jobs already executed, blame concurrency :)
- //
- if ( $row === false ) {
- if ( $offset != 0 ) {
- $row = $dbr->selectRow( 'job', '*', $conditions, __METHOD__, $options );
- }
-
- if ( $row === false ) {
- wfProfileOut( __METHOD__ );
- return false;
- }
- }
-
- // Try to delete it from the master
- $dbw = wfGetDB( DB_MASTER );
- $dbw->begin( __METHOD__ );
- $dbw->delete( 'job', array( 'job_id' => $row->job_id ), __METHOD__ );
- $affected = $dbw->affectedRows();
- $dbw->commit( __METHOD__ );
-
- if ( !$affected ) {
- $dbw->begin( __METHOD__ );
-
- // Failed, someone else beat us to it
- // Try getting a random row
- $row = $dbw->selectRow( 'job', array( 'minjob' => 'MIN(job_id)',
- 'maxjob' => 'MAX(job_id)' ), '1=1', __METHOD__ );
- if ( $row === false || is_null( $row->minjob ) || is_null( $row->maxjob ) ) {
- // No jobs to get
- $dbw->rollback( __METHOD__ );
- wfProfileOut( __METHOD__ );
- return false;
- }
- // Get the random row
- $row = $dbw->selectRow( 'job', '*',
- 'job_id >= ' . mt_rand( $row->minjob, $row->maxjob ), __METHOD__ );
- if ( $row === false ) {
- // Random job gone before we got the chance to select it
- // Give up
- $dbw->rollback( __METHOD__ );
- wfProfileOut( __METHOD__ );
- return false;
- }
- // Delete the random row
- $dbw->delete( 'job', array( 'job_id' => $row->job_id ), __METHOD__ );
- $affected = $dbw->affectedRows();
- $dbw->commit( __METHOD__ );
-
- if ( !$affected ) {
- // Random job gone before we exclusively deleted it
- // Give up
- wfProfileOut( __METHOD__ );
- return false;
- }
- }
-
- // If execution got to here, there's a row in $row that has been deleted from the database
- // by this thread. Hence the concurrent pop was successful.
- wfIncrStats( 'job-pop' );
- $namespace = $row->job_namespace;
- $dbkey = $row->job_title;
- $title = Title::makeTitleSafe( $namespace, $dbkey );
-
- if ( is_null( $title ) ) {
- wfProfileOut( __METHOD__ );
- return false;
- }
-
- $job = Job::factory( $row->job_cmd, $title, Job::extractBlob( $row->job_params ), $row->job_id );
-
- // Remove any duplicates it may have later in the queue
- $job->removeDuplicates();
-
- wfProfileOut( __METHOD__ );
- return $job;
- }
-
/**
* Create the appropriate object to handle a specific job
*
* @throws MWException
* @return Job
*/
- static function factory( $command, Title $title, $params = false, $id = 0 ) {
+ public static function factory( $command, Title $title, $params = false, $id = 0 ) {
global $wgJobClasses;
if( isset( $wgJobClasses[$command] ) ) {
$class = $wgJobClasses[$command];
throw new MWException( "Invalid job command `{$command}`" );
}
- /**
- * @param $params
- * @return string
- */
- static function makeBlob( $params ) {
- if ( $params !== false ) {
- return serialize( $params );
- } else {
- return '';
- }
- }
-
- /**
- * @param $blob
- * @return bool|mixed
- */
- static function extractBlob( $blob ) {
- if ( (string)$blob !== '' ) {
- return unserialize( $blob );
- } else {
- return false;
- }
- }
-
/**
* Batch-insert a group of jobs into the queue.
* This will be wrapped in a transaction with a forced commit.
* removed later on, when the first one is popped.
*
* @param $jobs array of Job objects
+ * @deprecated 1.21
*/
- static function batchInsert( $jobs ) {
- if ( !count( $jobs ) ) {
- return;
- }
- $dbw = wfGetDB( DB_MASTER );
- $rows = array();
-
- /**
- * @var $job Job
- */
- foreach ( $jobs as $job ) {
- $rows[] = $job->insertFields();
- if ( count( $rows ) >= 50 ) {
- # Do a small transaction to avoid slave lag
- $dbw->begin( __METHOD__ );
- $dbw->insert( 'job', $rows, __METHOD__, 'IGNORE' );
- $dbw->commit( __METHOD__ );
- $rows = array();
- }
- }
- if ( $rows ) { // last chunk
- $dbw->begin( __METHOD__ );
- $dbw->insert( 'job', $rows, __METHOD__, 'IGNORE' );
- $dbw->commit( __METHOD__ );
- }
- wfIncrStats( 'job-insert', count( $jobs ) );
+ public static function batchInsert( $jobs ) {
+ return JobQueueGroup::singleton()->push( $jobs );
}
/**
* large batches of jobs can cause slave lag.
*
* @param $jobs array of Job objects
+ * @deprecated 1.21
*/
- static function safeBatchInsert( $jobs ) {
- if ( !count( $jobs ) ) {
- return;
- }
- $dbw = wfGetDB( DB_MASTER );
- $rows = array();
- foreach ( $jobs as $job ) {
- $rows[] = $job->insertFields();
- if ( count( $rows ) >= 500 ) {
- $dbw->insert( 'job', $rows, __METHOD__, 'IGNORE' );
- $rows = array();
- }
- }
- if ( $rows ) { // last chunk
- $dbw->insert( 'job', $rows, __METHOD__, 'IGNORE' );
- }
- wfIncrStats( 'job-insert', count( $jobs ) );
+ public static function safeBatchInsert( $jobs ) {
+ return JobQueueGroup::singleton()->push( $jobs, JobQueue::QoS_Atomic );
}
-
/**
- * SQL conditions to apply on most JobQueue queries
+ * Pop a job of a certain type. This tries less hard than pop() to
+ * actually find a job; it may be adversely affected by concurrent job
+ * runners.
*
- * Whenever we exclude jobs types from the default queue, we want to make
- * sure that queries to the job queue actually ignore them.
+ * @param $type string
+ * @return Job
+ * @deprecated 1.21
+ */
+ public static function pop_type( $type ) {
+ return JobQueueGroup::singleton()->get( $type )->pop();
+ }
+
+ /**
+ * Pop a job off the front of the queue.
+ * This is subject to $wgJobTypesExcludedFromDefaultQueue.
*
- * @return array SQL conditions suitable for Database:: methods
+ * @return Job or false if there's no jobs
+ * @deprecated 1.21
*/
- static function defaultQueueConditions( ) {
- global $wgJobTypesExcludedFromDefaultQueue;
- $conditions = array();
- if ( count( $wgJobTypesExcludedFromDefaultQueue ) > 0 ) {
- $dbr = wfGetDB( DB_SLAVE );
- foreach ( $wgJobTypesExcludedFromDefaultQueue as $cmdType ) {
- $conditions[] = "job_cmd != " . $dbr->addQuotes( $cmdType );
- }
- }
- return $conditions;
+ public static function pop() {
+ return JobQueueGroup::singleton()->pop();
}
/*-------------------------------------------------------------------------
* @param $params array|bool
* @param $id int
*/
- function __construct( $command, $title, $params = false, $id = 0 ) {
+ public function __construct( $command, $title, $params = false, $id = 0 ) {
$this->command = $command;
$this->title = $title;
$this->params = $params;
$this->id = $id;
- // A bit of premature generalisation
- // Oh well, the whole class is premature generalisation really
- $this->removeDuplicates = true;
+ $this->removeDuplicates = false; // expensive jobs may set this to true
}
/**
- * Insert a single job into the queue.
- * @return bool true on success
+ * @return integer May be 0 for jobs stored outside the DB
*/
- function insert() {
- $fields = $this->insertFields();
+ public function getId() {
+ return $this->id;
+ }
- $dbw = wfGetDB( DB_MASTER );
+ /**
+ * @return string
+ */
+ public function getType() {
+ return $this->command;
+ }
- if ( $this->removeDuplicates ) {
- $res = $dbw->select( 'job', array( '1' ), $fields, __METHOD__ );
- if ( $dbw->numRows( $res ) ) {
- return true;
- }
- }
- wfIncrStats( 'job-insert' );
- return $dbw->insert( 'job', $fields, __METHOD__ );
+ /**
+ * @return Title
+ */
+ public function getTitle() {
+ return $this->title;
}
/**
* @return array
*/
- protected function insertFields() {
- $dbw = wfGetDB( DB_MASTER );
- return array(
- 'job_id' => $dbw->nextSequenceValue( 'job_job_id_seq' ),
- 'job_cmd' => $this->command,
- 'job_namespace' => $this->title->getNamespace(),
- 'job_title' => $this->title->getDBkey(),
- 'job_timestamp' => $dbw->timestamp(),
- 'job_params' => Job::makeBlob( $this->params )
- );
+ public function getParams() {
+ return $this->params;
}
/**
- * Remove jobs in the job queue which are duplicates of this job.
- * This is deadlock-prone and so starts its own transaction.
+ * @return bool
*/
- function removeDuplicates() {
- if ( !$this->removeDuplicates ) {
- return;
- }
+ public function ignoreDuplicates() {
+ return $this->removeDuplicates;
+ }
- $fields = $this->insertFields();
- unset( $fields['job_id'] );
- unset( $fields['job_timestamp'] );
- $dbw = wfGetDB( DB_MASTER );
- $dbw->begin( __METHOD__ );
- $dbw->delete( 'job', $fields, __METHOD__ );
- $affected = $dbw->affectedRows();
- $dbw->commit( __METHOD__ );
- if ( $affected ) {
- wfIncrStats( 'job-dup-delete', $affected );
- }
+ /**
+ * Insert a single job into the queue.
+ * @return bool true on success
+ * @deprecated 1.21
+ */
+ public function insert() {
+ return JobQueueGroup::singleton()->push( $this );
}
/**
* @return string
*/
- function toString() {
+ public function toString() {
$paramString = '';
if ( $this->params ) {
foreach ( $this->params as $key => $value ) {
$this->error = $error;
}
- function getLastError() {
+ public function getLastError() {
return $this->error;
}
}
--- /dev/null
+<?php
+/**
+ * Job queue base 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
+ * @defgroup JobQueue JobQueue
+ * @author Aaron Schulz
+ */
+
+/**
+ * Class to handle enqueueing and running of background jobs
+ *
+ * @ingroup JobQueue
+ * @since 1.20
+ */
+abstract class JobQueue {
+ protected $wiki; // string; wiki ID
+ protected $type; // string; job type
+
+ const QoS_Atomic = 1; // integer; "all-or-nothing" job insertions
+
+ /**
+ * @param $params array
+ */
+ protected function __construct( array $params ) {
+ $this->wiki = $params['wiki'];
+ $this->type = $params['type'];
+ }
+
+ /**
+ * Get a job queue object of the specified type.
+ * $params includes:
+ * class : what job class to use (determines job type)
+ * wiki : wiki ID of the wiki the jobs are for (defaults to current wiki)
+ * type : The name of the job types this queue handles
+ *
+ * @param $params array
+ * @return JobQueue
+ * @throws MWException
+ */
+ final public static function factory( array $params ) {
+ $class = $params['class'];
+ if ( !MWInit::classExists( $class ) ) {
+ throw new MWException( "Invalid job queue class '$class'." );
+ }
+ $obj = new $class( $params );
+ if ( !( $obj instanceof self ) ) {
+ throw new MWException( "Class '$class' is not a " . __CLASS__ . " class." );
+ }
+ return $obj;
+ }
+
+ /**
+ * @return string Wiki ID
+ */
+ final public function getWiki() {
+ return $this->wiki;
+ }
+
+ /**
+ * @return string Job type that this queue handles
+ */
+ final public function getType() {
+ return $this->type;
+ }
+
+ /**
+ * @return bool Quickly check if the queue is empty
+ */
+ final public function isEmpty() {
+ wfProfileIn( __METHOD__ );
+ $res = $this->doIsEmpty();
+ wfProfileOut( __METHOD__ );
+ return $res;
+ }
+
+ /**
+ * @see JobQueue::isEmpty()
+ * @return bool
+ */
+ abstract protected function doIsEmpty();
+
+ /**
+ * Push a batch of jobs into the queue
+ *
+ * @param $jobs array List of Jobs
+ * @param $flags integer Bitfield (supports JobQueue::QoS_Atomic)
+ * @return bool
+ */
+ final public function batchPush( array $jobs, $flags = 0 ) {
+ foreach ( $jobs as $job ) {
+ if ( $job->getType() !== $this->type ) {
+ throw new MWException( "Got '{$job->getType()}' job; expected '{$this->type}'." );
+ }
+ }
+ wfProfileIn( __METHOD__ );
+ $ok = $this->doBatchPush( $jobs, $flags );
+ if ( $ok ) {
+ wfIncrStats( 'job-insert', count( $jobs ) );
+ }
+ wfProfileOut( __METHOD__ );
+ return $ok;
+ }
+
+ /**
+ * @see JobQueue::batchPush()
+ * @return bool
+ */
+ abstract protected function doBatchPush( array $jobs, $flags );
+
+ /**
+ * Pop a job off of the queue
+ *
+ * @return Job|bool Returns false on failure
+ */
+ final public function pop() {
+ wfProfileIn( __METHOD__ );
+ $job = $this->doPop();
+ if ( $job ) {
+ wfIncrStats( 'job-pop' );
+ }
+ wfProfileOut( __METHOD__ );
+ return $job;
+ }
+
+ /**
+ * @see JobQueue::pop()
+ * @return Job
+ */
+ abstract protected function doPop();
+
+ /**
+ * Acknowledge that a job was completed
+ *
+ * @param $job Job
+ * @return bool
+ */
+ final public function ack( Job $job ) {
+ if ( $job->getType() !== $this->type ) {
+ throw new MWException( "Got '{$job->getType()}' job; expected '{$this->type}'." );
+ }
+ wfProfileIn( __METHOD__ );
+ $ok = $this->doAck( $job );
+ wfProfileOut( __METHOD__ );
+ return $ok;
+ }
+
+ /**
+ * @see JobQueue::ack()
+ * @return bool
+ */
+ abstract protected function doAck( Job $job );
+
+ /**
+ * Wait for any slaves or backup servers to catch up
+ *
+ * @return void
+ */
+ final public function waitForBackups() {
+ wfProfileIn( __METHOD__ );
+ $this->doWaitForBackups();
+ wfProfileOut( __METHOD__ );
+ }
+
+ /**
+ * @see JobQueue::waitForBackups()
+ * @return void
+ */
+ protected function doWaitForBackups() {}
+}
--- /dev/null
+<?php
+/**
+ * Database-backed job queue 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
+ * @author Aaron Schulz
+ */
+
+/**
+ * Class to handle job queues stored in the DB
+ *
+ * @ingroup JobQueue
+ * @since 1.20
+ */
+class JobQueueDB extends JobQueue {
+ const CACHE_TTL = 30; // integer; seconds
+ const MAX_JOB_RANDOM = 2147483647; // 2^31 - 1; used for job_random
+
+ /**
+ * @see JobQueue::doIsEmpty()
+ * @return bool
+ */
+ protected function doIsEmpty() {
+ global $wgMemc;
+
+ $key = $this->getEmptinessCacheKey();
+
+ $isEmpty = $wgMemc->get( $key );
+ if ( $isEmpty === 'true' ) {
+ return true;
+ } elseif ( $isEmpty === 'false' ) {
+ return false;
+ }
+
+ $found = $this->getSlaveDB()->selectField(
+ 'job', '1', array( 'job_cmd' => $this->type ), __METHOD__
+ );
+
+ $wgMemc->add( $key, $found ? 'false' : 'true', self::CACHE_TTL );
+ }
+
+ /**
+ * @see JobQueue::doBatchPush()
+ * @return bool
+ */
+ protected function doBatchPush( array $jobs, $flags ) {
+ if ( count( $jobs ) ) {
+ $dbw = $this->getMasterDB();
+
+ $rows = array();
+ foreach ( $jobs as $job ) {
+ $rows[] = $this->insertFields( $job );
+ }
+ $atomic = ( $flags & self::QoS_Atomic );
+ $key = $this->getEmptinessCacheKey();
+ $ttl = self::CACHE_TTL;
+
+ $dbw->onTransactionIdle( function() use ( $dbw, $rows, $atomic, $key, $ttl ) {
+ global $wgMemc;
+
+ $autoTrx = $dbw->getFlag( DBO_TRX ); // automatic begin() enabled?
+ if ( $atomic ) {
+ $dbw->begin(); // wrap all the job additions in one transaction
+ } else {
+ $dbw->clearFlag( DBO_TRX ); // make each query its own transaction
+ }
+ try {
+ foreach ( array_chunk( $rows, 50 ) as $rowBatch ) { // avoid slave lag
+ $dbw->insert( 'job', $rowBatch, __METHOD__ );
+ }
+ } catch ( DBError $e ) {
+ if ( $atomic ) {
+ $dbw->rollback();
+ } else {
+ $dbw->setFlag( $autoTrx ? DBO_TRX : 0 ); // restore automatic begin()
+ }
+ throw $e;
+ }
+ if ( $atomic ) {
+ $dbw->commit();
+ } else {
+ $dbw->setFlag( $autoTrx ? DBO_TRX : 0 ); // restore automatic begin()
+ }
+
+ $wgMemc->set( $key, 'false', $ttl );
+ } );
+ }
+
+ return true;
+ }
+
+ /**
+ * @see JobQueue::doPop()
+ * @return Job|bool
+ */
+ protected function doPop() {
+ global $wgMemc;
+
+ $uuid = wfRandomString( 32 ); // pop attempt
+
+ $dbw = $this->getMasterDB();
+ $dbw->commit( __METHOD__, 'flush' ); // flush existing transaction
+
+ $job = false; // job popped off
+ $autoTrx = $dbw->getFlag( DBO_TRX ); // automatic begin() enabled?
+ $dbw->clearFlag( DBO_TRX ); // make each query its own transaction
+ try {
+ do { // retry when our row is invalid or deleted as a duplicate
+ $row = false; // row claimed
+ $rand = mt_rand( 0, self::MAX_JOB_RANDOM ); // encourage concurrent UPDATEs
+ $gte = (bool)mt_rand( 0, 1 ); // find rows with rand before/after $rand
+ // Try to reserve a DB row...
+ if ( $this->claim( $uuid, $rand, $gte ) || $this->claim( $uuid, $rand, !$gte ) ) {
+ // Fetch any row that we just reserved...
+ $row = $dbw->selectRow( 'job', '*',
+ array( 'job_cmd' => $this->type, 'job_token' => $uuid ), __METHOD__ );
+ // Check if another process deleted it as a duplicate
+ if ( !$row ) {
+ wfDebugLog( 'JobQueueDB', "Row deleted as duplicate by another process." );
+ continue; // try again
+ }
+ // Get the job object from the row...
+ $title = Title::makeTitleSafe( $row->job_namespace, $row->job_title );
+ if ( !$title ) {
+ $dbw->delete( 'job', array( 'job_id' => $row->job_id ), __METHOD__ );
+ wfDebugLog( 'JobQueueDB', "Row has invalid title '{$row->job_title}'." );
+ continue; // try again
+ }
+ $job = Job::factory( $row->job_cmd, $title,
+ self::extractBlob( $row->job_params ), $row->job_id );
+ // Delete any *other* duplicate jobs in the queue...
+ if ( $job->ignoreDuplicates() && strlen( $row->job_sha1 ) ) {
+ $dbw->delete( 'job',
+ array( 'job_sha1' => $row->job_sha1,
+ "job_id != {$dbw->addQuotes( $row->job_id )}" ),
+ __METHOD__
+ );
+ }
+ } else {
+ $wgMemc->set( $this->getEmptinessCacheKey(), 'true', self::CACHE_TTL );
+ }
+ break; // done
+ } while( true );
+ } catch ( DBError $e ) {
+ $dbw->setFlag( $autoTrx ? DBO_TRX : 0 ); // restore automatic begin()
+ throw $e;
+ }
+ $dbw->setFlag( $autoTrx ? DBO_TRX : 0 ); // restore automatic begin()
+
+ return $job;
+ }
+
+ /**
+ * Reserve a row with a single UPDATE without holding row locks over RTTs...
+ * @param $uuid string 32 char hex string
+ * @param $rand integer Random unsigned integer (31 bits)
+ * @param $gte bool Search for job_random >= $random (otherwise job_random <= $random)
+ * @return integer Number of affected rows
+ */
+ protected function claim( $uuid, $rand, $gte ) {
+ $dbw = $this->getMasterDB();
+ $dir = $gte ? 'ASC' : 'DESC';
+ $ineq = $gte ? '>=' : '<=';
+ if ( $dbw->getType() === 'mysql' ) {
+ // Per http://bugs.mysql.com/bug.php?id=6980, we can't use subqueries on the
+ // same table being changed in an UPDATE query in MySQL (gives Error: 1093).
+ // Oracle and Postgre have no such limitation. However, MySQL offers an
+ // alternative here by supporting ORDER BY + LIMIT for UPDATE queries.
+ // The DB wrapper functions do not support this, so it's done manually.
+ $dbw->query( "UPDATE {$dbw->tableName( 'job' )}
+ SET
+ job_token = {$dbw->addQuotes( $uuid ) },
+ job_token_timestamp = {$dbw->addQuotes( $dbw->timestamp() )}
+ WHERE (
+ job_cmd = {$dbw->addQuotes( $this->type )}
+ AND job_token = {$dbw->addQuotes( '' )}
+ AND job_random {$ineq} {$dbw->addQuotes( $rand )}
+ ) ORDER BY job_random {$dir} LIMIT 1",
+ __METHOD__
+ );
+ } else {
+ // Use a subquery to find the job, within an UPDATE to claim it.
+ // This uses as much of the DB wrapper functions as possible.
+ $dbw->update( 'job',
+ array( 'job_token' => $uuid, 'job_token_timestamp' => $dbw->timestamp() ),
+ array( 'job_id = (' .
+ $dbw->selectSQLText( 'job', 'job_id',
+ array(
+ 'job_cmd' => $this->type,
+ 'job_token' => '',
+ "job_random {$ineq} {$dbw->addQuotes( $rand )}" ),
+ __METHOD__,
+ array( 'ORDER BY' => "job_random {$dir}", 'LIMIT' => 1 ) ) .
+ ')'
+ ),
+ __METHOD__
+ );
+ }
+ return $dbw->affectedRows();
+ }
+
+ /**
+ * @see JobQueue::doAck()
+ * @return Job|bool
+ */
+ protected function doAck( Job $job ) {
+ $dbw = $this->getMasterDB();
+ if ( $dbw->trxLevel() ) {
+ wfWarn( "Attempted to ack a job in a transaction; committing first." );
+ $dbw->commit(); // push existing transaction
+ }
+
+ $autoTrx = $dbw->getFlag( DBO_TRX ); // automatic begin() enabled?
+ $dbw->clearFlag( DBO_TRX ); // make each query its own transaction
+ try {
+ // Delete a row with a single DELETE without holding row locks over RTTs...
+ $dbw->delete( 'job', array( 'job_cmd' => $this->type, 'job_id' => $job->getId() ) );
+ } catch ( Exception $e ) {
+ $dbw->setFlag( $autoTrx ? DBO_TRX : 0 ); // restore automatic begin()
+ throw $e;
+ }
+ $dbw->setFlag( $autoTrx ? DBO_TRX : 0 ); // restore automatic begin()
+
+ return true;
+ }
+
+ /**
+ * @see JobQueue::doWaitForBackups()
+ * @return void
+ */
+ protected function doWaitForBackups() {
+ wfWaitForSlaves();
+ }
+
+ /**
+ * @return DatabaseBase
+ */
+ protected function getSlaveDB() {
+ return wfGetDB( DB_SLAVE, array(), $this->wiki );
+ }
+
+ /**
+ * @return DatabaseBase
+ */
+ protected function getMasterDB() {
+ return wfGetDB( DB_MASTER, array(), $this->wiki );
+ }
+
+ /**
+ * @param $job Job
+ * @return array
+ */
+ protected function insertFields( Job $job ) {
+ // Rows that describe the nature of the job
+ $descFields = array(
+ 'job_cmd' => $job->getType(),
+ 'job_namespace' => $job->getTitle()->getNamespace(),
+ 'job_title' => $job->getTitle()->getDBkey(),
+ 'job_params' => self::makeBlob( $job->getParams() ),
+ );
+ // Additional job metadata
+ $dbw = $this->getMasterDB();
+ $metaFields = array(
+ 'job_id' => $dbw->nextSequenceValue( 'job_job_id_seq' ),
+ 'job_timestamp' => $dbw->timestamp(),
+ 'job_sha1' => wfBaseConvert( sha1( serialize( $descFields ) ), 16, 36, 32 ),
+ 'job_random' => mt_rand( 0, self::MAX_JOB_RANDOM )
+ );
+ return ( $descFields + $metaFields );
+ }
+
+ /**
+ * @return string
+ */
+ private function getEmptinessCacheKey() {
+ list( $db, $prefix ) = wfSplitWikiID( $this->wiki );
+ return wfForeignMemcKey( $db, $prefix, 'jobqueue', $this->type, 'isempty' );
+ }
+
+ /**
+ * @param $params
+ * @return string
+ */
+ protected static function makeBlob( $params ) {
+ if ( $params !== false ) {
+ return serialize( $params );
+ } else {
+ return '';
+ }
+ }
+
+ /**
+ * @param $blob
+ * @return bool|mixed
+ */
+ protected static function extractBlob( $blob ) {
+ if ( (string)$blob !== '' ) {
+ return unserialize( $blob );
+ } else {
+ return false;
+ }
+ }
+}
--- /dev/null
+<?php
+/**
+ * Job queue base 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
+ * @author Aaron Schulz
+ */
+
+/**
+ * Class to handle enqueueing of background jobs
+ *
+ * @ingroup JobQueue
+ * @since 1.20
+ */
+class JobQueueGroup {
+ /** @var Array */
+ protected static $instances = array();
+
+ protected $wiki; // string; wiki ID
+
+ const TYPE_DEFAULT = 1; // integer; job not in $wgJobTypesExcludedFromDefaultQueue
+ const TYPE_ANY = 2; // integer; any job
+
+ /**
+ * @param $wiki string Wiki ID
+ */
+ protected function __construct( $wiki ) {
+ $this->wiki = $wiki;
+ }
+
+ /**
+ * @param $wiki string Wiki ID
+ * @return JobQueueGroup
+ */
+ public static function singleton( $wiki = false ) {
+ $wiki = ( $wiki === false ) ? wfWikiID() : $wiki;
+ if ( !isset( self::$instances[$wiki] ) ) {
+ self::$instances[$wiki] = new self( $wiki );
+ }
+ return self::$instances[$wiki];
+ }
+
+ /**
+ * @param $type string
+ * @return JobQueue Job queue object for a given queue type
+ */
+ public function get( $type ) {
+ global $wgJobTypeConf;
+
+ $conf = false;
+ if ( isset( $wgJobTypeConf[$type] ) ) {
+ $conf = $wgJobTypeConf[$type];
+ } else {
+ $conf = $wgJobTypeConf['default'];
+ }
+
+ return JobQueue::factory( array(
+ 'class' => $conf['class'],
+ 'wiki' => $this->wiki,
+ 'type' => $type,
+ ) );
+ }
+
+ /**
+ * Insert jobs into the respective queues of with the belong
+ *
+ * @param $jobs Job|array A single Job or a list of Jobs
+ * @return bool
+ */
+ public function push( $jobs ) {
+ $jobs = (array)$jobs;
+
+ $jobsByType = array(); // (job type => list of jobs)
+ foreach ( $jobs as $job ) {
+ if ( $job instanceof Job ) {
+ $jobsByType[$job->getType()][] = $job;
+ } else {
+ throw new MWException( "Attempted to push a non-Job object into a queue." );
+ }
+ }
+
+ $ok = true;
+ foreach ( $jobsByType as $type => $jobs ) {
+ if ( !$this->get( $type )->batchPush( $jobs ) ) {
+ $ok = false;
+ }
+ }
+
+ return $ok;
+ }
+
+ /**
+ * Pop a job off one of the job queues
+ *
+ * @param $type integer JobQueueGroup::TYPE_* constant
+ * @return Job|bool Returns false on failure
+ */
+ public function pop( $type = self::TYPE_DEFAULT ) {
+ $types = ( $type == self::TYPE_DEFAULT )
+ ? $this->getDefaultQueueTypes()
+ : $this->getQueueTypes();
+ shuffle( $types ); // avoid starvation
+
+ foreach ( $types as $type ) { // for each queue...
+ $job = $this->get( $type )->pop();
+ if ( $job ) {
+ return $job; // found
+ }
+ }
+
+ return false; // no jobs found
+ }
+
+ /**
+ * Acknowledge that a job was completed
+ *
+ * @param $job Job
+ * @return bool
+ */
+ public function ack( Job $job ) {
+ return $this->get( $job->getType() )->ack( $job );
+ }
+
+ /**
+ * Get the list of queue types
+ *
+ * @return array List of strings
+ */
+ public function getQueueTypes() {
+ global $wgJobClasses;
+
+ return array_keys( $wgJobClasses );
+ }
+
+ /**
+ * Get the list of default queue types
+ *
+ * @return array List of strings
+ */
+ public function getDefaultQueueTypes() {
+ global $wgJobTypesExcludedFromDefaultQueue;
+
+ return array_diff( $this->getQueueTypes(), $wgJobTypesExcludedFromDefaultQueue );
+ }
+}
* @ingroup JobQueue
*/
class RefreshLinksJob extends Job {
-
function __construct( $title, $params = '', $id = 0 ) {
parent::__construct( 'refreshLinks', $title, $params, $id );
+ $this->removeDuplicates = true; // job is expensive
}
/**
}
public static function runForTitleInternal( Title $title, Revision $revision, $fname ) {
- global $wgParser, $wgContLang;
+ global $wgContLang;
wfProfileIn( $fname . '-parse' );
$options = ParserOptions::newFromUserAndLang( new User, $wgContLang );
- $parserOutput = $wgParser->parse(
- $revision->getText(), $title, $options, true, true, $revision->getId() );
+ $content = $revision->getContent();
+ $parserOutput = $content->getParserOutput( $title, $revision->getId(), $options, false );
wfProfileOut( $fname . '-parse' );
wfProfileIn( $fname . '-update' );
- $updates = $parserOutput->getSecondaryDataUpdates( $title, false );
+ $updates = $content->getSecondaryDataUpdates( $title, null, false, $parserOutput );
DataUpdate::runUpdates( $updates );
wfProfileOut( $fname . '-update' );
}
/**
* Constructor.
- *
+ *
* @since 1.19
- *
+ *
* @param string $type
* @param string $subtype
*/
* '4:color' => 'blue',
* 'animal' => 'dog'
* );
- *
+ *
* @since 1.19
- *
+ *
* @param $parameters array Associative array
*/
public function setParameters( $parameters ) {
/**
* Set the user that performed the action being logged.
- *
+ *
* @since 1.19
- *
+ *
* @param User $performer
*/
public function setPerformer( User $performer ) {
/**
* Set the title of the object changed.
- *
+ *
* @since 1.19
- *
+ *
* @param Title $target
*/
public function setTarget( Title $target ) {
/**
* Set the timestamp of when the logged action took place.
- *
+ *
* @since 1.19
- *
+ *
* @param string $timestamp
*/
public function setTimestamp( $timestamp ) {
/**
* Set a comment associated with the action being logged.
- *
+ *
* @since 1.19
- *
+ *
* @param string $comment
*/
public function setComment( $comment ) {
/**
* TODO: document
- *
+ *
* @since 1.19
- *
+ *
* @param integer $deleted
*/
public function setDeleted( $deleted ) {
* value in consideration.
* @param $title Title the page
* @param $parameters array query parameters
+ * @throws MWException
* @return String
*/
protected function makePageLink( Title $title = null, $parameters = array() ) {
}
/**
- * Generate text for a log entry.
+ * Generate text for a log entry.
* Only LogFormatter should call this function.
*
* @param $type String: log type
* to filter down to users.
*
* @param $path string The file path
- * @param $scene string The scene specification, or false if there is none
+ * @param bool|string $scene The scene specification, or false if there is none
+ * @throws MWException
* @return string
*/
function escapeMagickInput( $path, $scene = false ) {
* helper function for escapeMagickInput() and escapeMagickOutput().
*
* @param $path string The file path
- * @param $scene string The scene specification, or false if there is none
+ * @param bool|string $scene The scene specification, or false if there is none
+ * @throws MWException
* @return string
*/
protected function escapeMagickPath( $path, $scene = false ) {
*
* The various exceptions this throws are caught later.
* @param $filename String
+ * @throws MWException
* @return Array The metadata.
*/
static public function Tiff ( $filename ) {
*
* @param $file String: filename.
* @param $byteOrder String Type of byte ordering either 'BE' (Big Endian) or 'LE' (Little Endian). Default ''.
+ * @throws MWException
* @todo FIXME: The following are broke:
* SubjectArea. Need to test the more obscure tags.
*
/**
* @param $data
+ * @throws Exception
* @return int
*/
static function decodeBPP( $data ) {
/**
* @param $fh
- * @return
+ * @throws Exception
*/
static function skipBlock( $fh ) {
while ( !feof( $fh ) ) {
fread( $fh, $block_len );
}
}
+
/**
* Read a block. In the GIF format, a block is made up of
* several sub-blocks. Each sub block starts with one byte
* sub-blocks in the returned value. Normally this is false,
* except XMP is weird and does a hack where you need to keep
* these length bytes.
+ * @throws Exception
* @return string The data.
*/
static function readBlock( $fh, $includeLengths = false ) {
}
/**
- * Helper function for jpegSegmentSplitter
- * @param &$fh FileHandle for jpeg file
- * @return string data content of segment.
- */
+ * Helper function for jpegSegmentSplitter
+ * @param &$fh FileHandle for jpeg file
+ * @throws MWException
+ * @return string data content of segment.
+ */
private static function jpegExtractMarker( &$fh ) {
$size = wfUnpack( "nint", fread( $fh, 2 ), 2 );
if ( $size['int'] <= 2 ) {
var $file;
var $width, $height, $url, $page, $path;
+
+ /**
+ * @var array Associative array mapping optional supplementary image files
+ * from pixel density (eg 1.5 or 2) to additional URLs.
+ */
+ public $responsiveUrls = array();
+
protected $storagePath = false;
/**
* For images, desc-link and file-link are implemented as a click-through. For
* sounds and videos, they may be displayed in other ways.
*
+ * @throws MWException
* @return string
*/
function toHtml( $options = array() ) {
'alt' => $alt,
'src' => $this->url,
'width' => $this->width,
- 'height' => $this->height,
+ 'height' => $this->height
);
if ( !empty( $options['valign'] ) ) {
$attribs['style'] = "vertical-align: {$options['valign']}";
if ( !empty( $options['img-class'] ) ) {
$attribs['class'] = $options['img-class'];
}
+
+ // Additional densities for responsive images, if specified.
+ if ( !empty( $this->responsiveUrls ) ) {
+ $attribs['srcset'] = Html::srcSet( $this->responsiveUrls );
+ }
return $this->linkWrap( $linkAttribs, Xml::element( 'img', $attribs ) );
}
}
/**
- * Transform an SVG file to PNG
- * This function can be called outside of thumbnail contexts
- * @param string $srcPath
- * @param string $dstPath
- * @param string $width
- * @param string $height
- * @return bool|MediaTransformError
- */
+ * Transform an SVG file to PNG
+ * This function can be called outside of thumbnail contexts
+ * @param string $srcPath
+ * @param string $dstPath
+ * @param string $width
+ * @param string $height
+ * @throws MWException
+ * @return bool|MediaTransformError
+ */
public function rasterize( $srcPath, $dstPath, $width, $height ) {
global $wgSVGConverters, $wgSVGConverter, $wgSVGConverterPath;
$err = false;
*
* Creates an SVGReader drawing from the source provided
* @param $source String: URI from which to read
+ * @throws MWException|Exception
*/
function __construct( $source ) {
global $wgSVGMetadataCutoff;
/**
* Read the SVG
+ * @throws MWException
* @return bool
*/
public function read() {
* Read an XML snippet from an element
*
* @param String $metafield that we will fill with the result
+ * @throws MWException
*/
private function readXml( $metafield=null ) {
$this->debug ( "Read top level metadata" );
}
/**
- * @param $image
- * @param $filename
+ * @param File $image
+ * @param string $filename
+ * @throws MWException
* @return string
*/
function getMetadata( $image, $filename ) {
}
/**
- * Main function to call to parse XMP. Use getResults to
- * get results.
- *
- * Also catches any errors during processing, writes them to
- * debug log, blanks result array and returns false.
- *
- * @param $content String: XMP data
- * @param $allOfIt Boolean: If this is all the data (true) or if its split up (false). Default true
- * @param $reset Boolean: does xml parser need to be reset. Default false
- * @return Boolean success.
- */
+ * Main function to call to parse XMP. Use getResults to
+ * get results.
+ *
+ * Also catches any errors during processing, writes them to
+ * debug log, blanks result array and returns false.
+ *
+ * @param $content String: XMP data
+ * @param $allOfIt Boolean: If this is all the data (true) or if its split up (false). Default true
+ * @param $reset Boolean: does xml parser need to be reset. Default false
+ * @throws MWException
+ * @return Boolean success.
+ */
public function parse( $content, $allOfIt = true, $reset = false ) {
if ( $reset ) {
$this->resetXMLParser();
}
/**
- * Hit a closing element in MODE_STRUCT, MODE_SEQ, MODE_BAG
- * generally means we've finished processing a nested structure.
- * resets some internal variables to indicate that.
- *
- * Note this means we hit the closing element not the "</rdf:Seq>".
- *
- * @par For example, when processing:
- * @code{,xml}
- * <exif:ISOSpeedRatings> <rdf:Seq> <rdf:li>64</rdf:li>
- * </rdf:Seq> </exif:ISOSpeedRatings>
- * @endcode
- *
- * This method is called when we hit the "</exif:ISOSpeedRatings>" tag.
- *
- * @param $elm String namespace . space . tag name.
- */
+ * Hit a closing element in MODE_STRUCT, MODE_SEQ, MODE_BAG
+ * generally means we've finished processing a nested structure.
+ * resets some internal variables to indicate that.
+ *
+ * Note this means we hit the closing element not the "</rdf:Seq>".
+ *
+ * @par For example, when processing:
+ * @code{,xml}
+ * <exif:ISOSpeedRatings> <rdf:Seq> <rdf:li>64</rdf:li>
+ * </rdf:Seq> </exif:ISOSpeedRatings>
+ * @endcode
+ *
+ * This method is called when we hit the "</exif:ISOSpeedRatings>" tag.
+ *
+ * @param $elm String namespace . space . tag name.
+ * @throws MWException
+ */
private function endElementNested( $elm ) {
/* cur item must be the same as $elm, unless if in MODE_STRUCT
}
/**
- * Hit a closing element in MODE_LI (either rdf:Seq, or rdf:Bag )
- * Add information about what type of element this is.
- *
- * Note we still have to hit the outer "</property>"
- *
- * @par For example, when processing:
- * @code{,xml}
- * <exif:ISOSpeedRatings> <rdf:Seq> <rdf:li>64</rdf:li>
- * </rdf:Seq> </exif:ISOSpeedRatings>
- * @endcode
- *
- * This method is called when we hit the "</rdf:Seq>".
- * (For comparison, we call endElementModeSimple when we
- * hit the "</rdf:li>")
- *
- * @param $elm String namespace . ' ' . element name
- */
+ * Hit a closing element in MODE_LI (either rdf:Seq, or rdf:Bag )
+ * Add information about what type of element this is.
+ *
+ * Note we still have to hit the outer "</property>"
+ *
+ * @par For example, when processing:
+ * @code{,xml}
+ * <exif:ISOSpeedRatings> <rdf:Seq> <rdf:li>64</rdf:li>
+ * </rdf:Seq> </exif:ISOSpeedRatings>
+ * @endcode
+ *
+ * This method is called when we hit the "</rdf:Seq>".
+ * (For comparison, we call endElementModeSimple when we
+ * hit the "</rdf:li>")
+ *
+ * @param $elm String namespace . ' ' . element name
+ * @throws MWException
+ */
private function endElementModeLi( $elm ) {
list( $ns, $tag ) = explode( ' ', $this->curItem[0], 2 );
}
/**
- * Handler for hitting a closing element.
- *
- * generally just calls a helper function depending on what
- * mode we're in.
- *
- * Ignores the outer wrapping elements that are optional in
- * xmp and have no meaning.
- *
- * @param $parser XMLParser
- * @param $elm String namespace . ' ' . element name
- */
+ * Handler for hitting a closing element.
+ *
+ * generally just calls a helper function depending on what
+ * mode we're in.
+ *
+ * Ignores the outer wrapping elements that are optional in
+ * xmp and have no meaning.
+ *
+ * @param $parser XMLParser
+ * @param $elm String namespace . ' ' . element name
+ * @throws MWException
+ */
function endElement( $parser, $elm ) {
if ( $elm === ( self::NS_RDF . ' RDF' )
|| $elm === 'adobe:ns:meta/ xmpmeta'
}
/**
- * Handle an opening element when in MODE_SIMPLE
- *
- * This should not happen often. This is for if a simple element
- * already opened has a child element. Could happen for a
- * qualified element.
- *
- * For example:
- * <exif:DigitalZoomRatio><rdf:Description><rdf:value>0/10</rdf:value>
- * <foo:someQualifier>Bar</foo:someQualifier> </rdf:Description>
- * </exif:DigitalZoomRatio>
- *
- * This method is called when processing the <rdf:Description> element
- *
- * @param $elm String namespace and tag names separated by space.
- * @param $attribs Array Attributes of the element.
- */
+ * Handle an opening element when in MODE_SIMPLE
+ *
+ * This should not happen often. This is for if a simple element
+ * already opened has a child element. Could happen for a
+ * qualified element.
+ *
+ * For example:
+ * <exif:DigitalZoomRatio><rdf:Description><rdf:value>0/10</rdf:value>
+ * <foo:someQualifier>Bar</foo:someQualifier> </rdf:Description>
+ * </exif:DigitalZoomRatio>
+ *
+ * This method is called when processing the <rdf:Description> element
+ *
+ * @param $elm String namespace and tag names separated by space.
+ * @param $attribs Array Attributes of the element.
+ * @throws MWException
+ */
private function startElementModeSimple( $elm, $attribs ) {
if ( $elm === self::NS_RDF . ' Description' ) {
// If this value has qualifiers
}
/**
- * Starting an element when in MODE_INITIAL
- * This usually happens when we hit an element inside
- * the outer rdf:Description
- *
- * This is generally where most properties start.
- *
- * @param $ns String Namespace
- * @param $tag String tag name (without namespace prefix)
- * @param $attribs Array array of attributes
- */
+ * Starting an element when in MODE_INITIAL
+ * This usually happens when we hit an element inside
+ * the outer rdf:Description
+ *
+ * This is generally where most properties start.
+ *
+ * @param $ns String Namespace
+ * @param $tag String tag name (without namespace prefix)
+ * @param $attribs Array array of attributes
+ * @throws MWException
+ */
private function startElementModeInitial( $ns, $tag, $attribs ) {
if ( $ns !== self::NS_RDF ) {
}
/**
- * Hit an opening element when in a Struct (MODE_STRUCT)
- * This is generally for fields of a compound property.
- *
- * Example of a struct (abbreviated; flash has more properties):
- *
- * <exif:Flash> <rdf:Description> <exif:Fired>True</exif:Fired>
- * <exif:Mode>1</exif:Mode></rdf:Description></exif:Flash>
- *
- * or:
- *
- * <exif:Flash rdf:parseType='Resource'> <exif:Fired>True</exif:Fired>
- * <exif:Mode>1</exif:Mode></exif:Flash>
- *
- * @param $ns String namespace
- * @param $tag String tag name (no ns)
- * @param $attribs Array array of attribs w/ values.
- */
+ * Hit an opening element when in a Struct (MODE_STRUCT)
+ * This is generally for fields of a compound property.
+ *
+ * Example of a struct (abbreviated; flash has more properties):
+ *
+ * <exif:Flash> <rdf:Description> <exif:Fired>True</exif:Fired>
+ * <exif:Mode>1</exif:Mode></rdf:Description></exif:Flash>
+ *
+ * or:
+ *
+ * <exif:Flash rdf:parseType='Resource'> <exif:Fired>True</exif:Fired>
+ * <exif:Mode>1</exif:Mode></exif:Flash>
+ *
+ * @param $ns String namespace
+ * @param $tag String tag name (no ns)
+ * @param $attribs Array array of attribs w/ values.
+ * @throws MWException
+ */
private function startElementModeStruct( $ns, $tag, $attribs ) {
if ( $ns !== self::NS_RDF ) {
}
/**
- * Hits an opening element.
- * Generally just calls a helper based on what MODE we're in.
- * Also does some initial set up for the wrapper element
- *
- * @param $parser XMLParser
- * @param $elm String namespace "<space>" element
- * @param $attribs Array attribute name => value
- */
+ * Hits an opening element.
+ * Generally just calls a helper based on what MODE we're in.
+ * Also does some initial set up for the wrapper element
+ *
+ * @param $parser XMLParser
+ * @param $elm String namespace "<space>" element
+ * @param $attribs Array attribute name => value
+ * @throws MWException
+ */
function startElement( $parser, $elm, $attribs ) {
if ( $elm === self::NS_RDF . ' RDF'
}
/**
- * Process attributes.
- * Simple values can be stored as either a tag or attribute
- *
- * Often the initial "<rdf:Description>" tag just has all the simple
- * properties as attributes.
- *
- * @par Example:
- * @code
- * <rdf:Description rdf:about="" xmlns:exif="http://ns.adobe.com/exif/1.0/" exif:DigitalZoomRatio="0/10">
- * @endcode
- *
- * @param $attribs Array attribute=>value array.
- */
+ * Process attributes.
+ * Simple values can be stored as either a tag or attribute
+ *
+ * Often the initial "<rdf:Description>" tag just has all the simple
+ * properties as attributes.
+ *
+ * @par Example:
+ * @code
+ * <rdf:Description rdf:about="" xmlns:exif="http://ns.adobe.com/exif/1.0/" exif:DigitalZoomRatio="0/10">
+ * @endcode
+ *
+ * @param $attribs Array attribute=>value array.
+ * @throws MWException
+ */
private function doAttribs( $attribs ) {
// first check for rdf:parseType attribute, as that can change
/**
* @param $params array
+ * @throws MWException
*/
function __construct( $params ) {
if ( !defined( 'CURLOPT_TIMEOUT_MS' ) ) {
* Get a connection to the server with the specified name. Connections
* are cached, and failures are persistent to avoid multiple timeouts.
*
+ * @param $server
+ * @throws MWException
* @return Redis object, or false on failure
*/
protected function getConnectionToServer( $server ) {
* @param $content string
* @param $attributes array
* @param $parser Parser
+ * @throws MWException
* @return array
*/
static function html( $content, $attributes, $parser ) {
$linked = true;
if ( isset( $this->mLinked ) )
$linked = $this->mLinked;
-
+
$bits = array();
$key = $this->keys[$this->mSource];
for ( $p=0; $p < strlen($key); $p++ ) {
*/
function formatDate( $bits, $link = true ) {
$format = $this->targets[$this->mTarget];
-
+
if (!$link) {
// strip piped links
$format = preg_replace( '/\[\[[^|]+\|([^\]]+)\]\]/', '$1', $format );
/**
* Don't serialize the parent object, it is big, and not needed when it is
- * a parameter to mergeForeign(), which is the only application of
+ * a parameter to mergeForeign(), which is the only application of
* serializing at present.
*
* Compact the titles, only serialize the text form.
}
/**
- * Merge a LinkHolderArray from another parser instance into this one. The
- * keys will not be preserved. Any text which went with the old
- * LinkHolderArray and needs to work with the new one should be passed in
+ * Merge a LinkHolderArray from another parser instance into this one. The
+ * keys will not be preserved. Any text which went with the old
+ * LinkHolderArray and needs to work with the new one should be passed in
* the $texts array. The strings in this array will have their link holders
* converted for use in the destination link holder. The resulting array of
* strings will be returned.
$maxId = $newKey > $maxId ? $newKey : $maxId;
}
}
- $texts = preg_replace_callback( '/(<!--LINK \d+:)(\d+)(-->)/',
+ $texts = preg_replace_callback( '/(<!--LINK \d+:)(\d+)(-->)/',
array( $this, 'mergeForeignCallback' ), $texts );
# Renumber interwiki links
$this->interwikis[$newKey] = $entry;
$maxId = $newKey > $maxId ? $newKey : $maxId;
}
- $texts = preg_replace_callback( '/(<!--IWLINK )(\d+)(-->)/',
+ $texts = preg_replace_callback( '/(<!--IWLINK )(\d+)(-->)/',
array( $this, 'mergeForeignCallback' ), $texts );
# Set the parent link ID to be beyond the highest used ID
# Internal links
$pos = 0;
while ( $pos < strlen( $text ) ) {
- if ( !preg_match( '/<!--LINK (\d+):(\d+)-->/',
- $text, $m, PREG_OFFSET_CAPTURE, $pos ) )
+ if ( !preg_match( '/<!--LINK (\d+):(\d+)-->/',
+ $text, $m, PREG_OFFSET_CAPTURE, $pos ) )
{
break;
}
*
* @since 1.19
*
+ * @throws MWException
* @return Language|null
*/
public function getTargetLanguage() {
*
* @param $text string
*
+ * @throws MWException
* @return string
*/
function replaceExternalLinks( $text ) {
$i = 0;
while ( $i<count( $bits ) ) {
$url = $bits[$i++];
+ // @todo FIXME: Unused variable.
$protocol = $bits[$i++];
$text = $bits[$i++];
$trail = $bits[$i++];
/**
* Process [[ ]] wikilinks (RIL)
+ * @param $s
+ * @throws MWException
* @return LinkHolderArray
*
* @private
# Interwikis
wfProfileIn( __METHOD__."-interwiki" );
if ( $iw && $this->mOptions->getInterwikiMagic() && $nottalk && Language::fetchLanguageName( $iw, null, 'mw' ) ) {
+ // XXX: the above check prevents links to sites with identifiers that are not language codes
+
# Bug 24502: filter duplicates
if ( !isset( $this->mLangLinkLanguages[$iw] ) ) {
$this->mLangLinkLanguages[$iw] = true;
$this->mOutput->addLanguageLink( $nt->getFullText() );
}
+
$s = rtrim( $s . $prefix );
$s .= trim( $trail, "\n" ) == '' ? '': $prefix . $trail;
wfProfileOut( __METHOD__."-interwiki" );
* @param $str String the string to split
* @param &$before String set to everything before the ':'
* @param &$after String set to everything after the ':'
+ * @throws MWException
* @return String the position of the ':', or false if none found
*/
function findColonNoLinks( $str, &$before, &$after ) {
* @private
*
* @param $index integer
- * @param $frame PPFrame
+ * @param bool|\PPFrame $frame
*
+ * @throws MWException
* @return string
*/
function getVariableValue( $index, $frame = false ) {
* $piece['parts']: the parameter array
* $piece['lineStart']: whether the brace was at the start of a line
* @param $frame PPFrame The current frame, contains template arguments
+ * @throws MWException
* @return String: the text of the template
* @private
*/
}
if ( $rev ) {
- $text = $rev->getText();
+ $content = $rev->getContent();
+ $text = $content->getWikitextForTransclusion();
+
+ if ( $text === false || $text === null ) {
+ $text = false;
+ break;
+ }
} elseif ( $title->getNamespace() == NS_MEDIAWIKI ) {
global $wgContLang;
$message = wfMessage( $wgContLang->lcfirst( $title->getText() ) )->inContentLanguage();
$text = false;
break;
}
+ $content = $message->content();
$text = $message->plain();
} else {
break;
}
- if ( $text === false ) {
+ if ( !$content ) {
break;
}
# Redirect?
$finalTitle = $title;
- $title = Title::newFromRedirect( $text );
+ $title = $content->getRedirectTarget();
}
return array(
'text' => $text,
* noClose Original text did not have a close tag
* @param $frame PPFrame
*
+ * @throws MWException
* @return string
*/
function extensionSubstitution( $params, $frame ) {
*
* @param $tag Mixed: the tag to use, e.g. 'hook' for "<hook>"
* @param $callback Mixed: the callback function (and object) to use for the tag
+ * @throws MWException
* @return Mixed|null The old value of the mTagHooks array associated with the hook
*/
public function setHook( $tag, $callback ) {
*
* @param $tag Mixed: the tag to use, e.g. 'hook' for "<hook>"
* @param $callback Mixed: the callback function (and object) to use for the tag
+ * @throws MWException
* @return Mixed|null The old value of the mTagHooks array associated with the hook
*/
function setTransparentTagHook( $tag, $callback ) {
* Please read the documentation in includes/parser/Preprocessor.php for more information
* about the methods available in PPFrame and PPNode.
*
+ * @throws MWException
* @return string|callback The old callback function for this name, if any
*/
public function setFunctionHook( $id, $callback, $flags = 0 ) {
* Create a tag function, e.g. "<test>some stuff</test>".
* Unlike tag hooks, tag functions are parsed at preprocessor level.
* Unlike parser functions, their content is not preprocessed.
+ * @param $tag
+ * @param $callback
+ * @param $flags
+ * @throws MWException
* @return null
*/
function setFunctionTagHook( $tag, $callback, $flags ) {
* check whether it is still valid, by calling isValidHalfParsedText().
*
* @param $data array Serialized data
+ * @throws MWException
* @return String
*/
function unserializeHalfParsedText( $data ) {
* May be a memcached client or a BagOStuff derivative.
*
* @param $memCached Object
+ * @throws MWException
*/
protected function __construct( $memCached ) {
if ( !$memCached ) {
wfDebug( "ParserOutput cache found.\n" );
- // The edit section preference may not be the appropiate one in
- // the ParserOutput, as we are not storing it in the parsercache
+ // The edit section preference may not be the appropiate one in
+ // the ParserOutput, as we are not storing it in the parsercache
// key. Force it here. See bug 31445.
$value->setEditSectionTokens( $popts->getEditSection() );
* @ingroup Parser
*/
class ParserOptions {
-
+
/**
* Use DateFormatter to format dates
*/
var $mUseDynamicDates;
-
+
/**
* Interlanguage links are removed and returned in an array
*/
var $mInterwikiMagic;
-
+
/**
* Allow external images inline?
*/
var $mAllowExternalImages;
-
+
/**
* If not, any exception?
*/
var $mAllowExternalImagesFrom;
-
+
/**
* If not or it doesn't match, should we check an on-wiki whitelist?
*/
var $mEnableImageWhitelist;
-
+
/**
* Date format index
*/
var $mDateFormat = null;
-
+
/**
* Create "edit section" links?
*/
var $mEditSection = true;
-
+
/**
* Allow inclusion of special pages?
*/
var $mAllowSpecialInclusion;
-
+
/**
* Use tidy to cleanup output HTML?
*/
var $mTidy = false;
-
+
/**
* Which lang to call for PLURAL and GRAMMAR
*/
var $mInterfaceMessage = false;
-
+
/**
* Overrides $mInterfaceMessage with arbitrary language
*/
var $mTargetLanguage = null;
-
+
/**
* Maximum size of template expansions, in bytes
*/
var $mMaxIncludeSize;
-
+
/**
* Maximum number of nodes touched by PPFrame::expand()
*/
* Maximum number of nodes generated by Preprocessor::preprocessToObj()
*/
var $mMaxGeneratedPPNodeCount;
-
+
/**
* Maximum recursion depth in PPFrame::expand()
*/
var $mMaxPPExpandDepth;
-
+
/**
* Maximum recursion depth for templates within templates
*/
var $mMaxTemplateDepth;
-
+
/**
* Maximum number of calls per parse to expensive parser functions
*/
var $mExpensiveParserFunctionLimit;
-
+
/**
* Remove HTML comments. ONLY APPLIES TO PREPROCESS OPERATIONS
*/
var $mRemoveComments = true;
-
+
/**
* Callback for template fetching. Used as first argument to call_user_func().
*/
var $mTemplateCallback =
array( 'Parser', 'statelessFetchTemplate' );
-
+
/**
* Enable limit report in an HTML comment on output
*/
var $mEnableLimitReport = false;
-
+
/**
* Timestamp used for {{CURRENTDAY}} etc.
*/
var $mTimestamp;
-
+
/**
* Target attribute for external links
*/
var $mExternalLinkTarget;
-
+
/**
- * Clean up signature texts?
+ * Clean up signature texts?
*
* 1) Strip ~~~, ~~~~ and ~~~~~ out of signatures
* 2) Substitute all transclusions
*/
var $mCleanSignatures;
-
+
/**
* Transform wiki markup when saving the page?
*/
* Automatically number headings?
*/
var $mNumberHeadings;
-
+
/**
* User math preference (as integer). Not used (1.19)
*/
var $mMath;
-
+
/**
* Thumb size preferred by the user.
*/
var $mThumbSize;
-
+
/**
* Maximum article size of an article to be marked as "stub"
*/
private $mStubThreshold;
-
+
/**
* Language object of the User language.
*/
var $mUserLang;
/**
- * @var User
+ * @var User
* Stored user object
*/
var $mUser;
-
+
/**
* Parsing the page for a "preview" operation?
*/
var $mIsPreview = false;
-
+
/**
* Parsing the page for a "preview" operation on a single section?
*/
var $mIsSectionPreview = false;
-
+
/**
* Parsing the printable version of the page?
*/
return new ParserOptions( $context->getUser(), $context->getLanguage() );
}
- /**
- * Get user options
+ /**
+ * Get user options
*
* @param $user User object
* @param $lang Language object
$mTimestamp; # Timestamp of the revision
private $mIndexPolicy = ''; # 'index' or 'noindex'? Any other value will result in no change.
private $mAccessedOptions = array(); # List of ParserOptions (stored in the keys)
- private $mSecondaryDataUpdates = array(); # List of instances of SecondaryDataObject(), used to cause some information extracted from the page in a custom place.
+ private $mSecondaryDataUpdates = array(); # List of DataUpdate, used to save info from the page somewhere else.
const EDITSECTION_REGEX = '#<(?:mw:)?editsection page="(.*?)" section="(.*?)"(?:/>|>(.*?)(</(?:mw:)?editsection>))#';
/**
* callback used by getText to replace editsection tokens
* @private
+ * @param $m
+ * @throws MWException
* @return mixed
*/
function replaceEditSectionLinksCallback( $m ) {
* extracted from the page's content, including a LinksUpdate object for all links stored in
* this ParserOutput object.
*
+ * @note: Avoid using this method directly, use ContentHandler::getSecondaryDataUpdates() instead! The content
+ * handler may provide additional update objects.
+ *
* @since 1.20
*
- * @param $title Title of the page we're updating. If not given, a title object will be created based on $this->getTitleText()
+ * @param $title Title The title of the page we're updating. If not given, a title object will be created
+ * based on $this->getTitleText()
* @param $recursive Boolean: queue jobs for recursive updates?
*
* @return Array. An array of instances of DataUpdate
* can automatically discard old data.
*/
const VERSION = '1.6.4';
-
+
# Flags for Parser::setLinkHook
# Also available as global constants from Defines.php
const SLH_PATTERN = 1;
* Create a link hook, e.g. [[Namepsace:...|display}}
* The callback function should have the form:
* function myLinkCallback( $parser, $holders, $markers,
- * Title $title, $titleText, &$sortText = null, &$leadingColon = false ) { ... }
+ * Title $title, $titleText, &$sortText = null, &$leadingColon = false ) { ... }
*
* Or with SLH_PATTERN:
* function myLinkCallback( $parser, $holders, $markers, )
- * &$titleText, &$sortText = null, &$leadingColon = false ) { ... }
+ * &$titleText, &$sortText = null, &$leadingColon = false ) { ... }
*
* The callback may either return a number of different possible values:
* String) Text result of the link
* @param $flags Integer: a combination of the following flags:
* SLH_PATTERN Use a regex link pattern rather than a namespace
*
+ * @throws MWException
* @return callback|null The old callback function for this name, if any
*/
public function setLinkHook( $ns, $callback, $flags = 0 ) {
$this->mLinkHooks[$ns] = array( $callback, $flags );
return $oldVal;
}
-
+
/**
* Get all registered link hook identifiers
*
function getLinkHooks() {
return array_keys( $this->mLinkHooks );
}
-
+
/**
* Process [[ ]] wikilinks
+ * @param $s
+ * @throws MWException
* @return LinkHolderArray
*
* @private
}
$holders = new LinkHolderArray( $this );
-
+
if( is_null( $this->mTitle ) ) {
wfProfileOut( __METHOD__ );
wfProfileOut( __METHOD__.'-setup' );
}
wfProfileOut( __METHOD__.'-setup' );
-
+
$offset = 0;
$offsetStack = array();
$markers = new LinkMarkerReplacer( $this, $holders, array( &$this, 'replaceInternalLinksCallback' ) );
$startBracketOffset = array_pop($offsetStack);
# Just to clean up the code, lets place offsets on the outer ends
$endBracketOffset += 2;
-
+
# Only do logic if we actually have a opening bracket for this
if( isset($startBracketOffset) ) {
# Extract text inside the link
# ToDO: Some LinkHooks use patterns rather than namespaces
# these need to be tested at this point here
}
-
}
# Bump our offset to after our current bracket
$offset = $bracketOffset+2;
}
-
-
+
# Now expand our tree
wfProfileIn( __METHOD__.'-expand' );
$s = $markers->expand( $s );
wfProfileOut( __METHOD__.'-expand' );
-
+
wfProfileOut( __METHOD__ );
return $holders;
}
-
+
function replaceInternalLinksCallback( $parser, $holders, $markers, $titleText, $paramText ) {
wfProfileIn( __METHOD__ );
$wt = isset($paramText) ? "[[$titleText|$paramText]]" : "[[$titleText]]";
wfProfileOut( __METHOD__ );
return $wt;
}
-
+
# Make subpage if necessary
if( $this->areSubpagesAllowed() ) {
$titleText = $this->maybeDoSubpageLink( $titleText, $paramText );
}
-
+
# Check for a leading colon and strip it if it is there
$leadingColon = $titleText[0] == ':';
if( $leadingColon ) $titleText = substr( $titleText, 1 );
-
+
wfProfileOut( __METHOD__."-misc" );
# Make title object
wfProfileIn( __METHOD__."-title" );
}
$ns = $title->getNamespace();
wfProfileOut( __METHOD__."-title" );
-
+
# Default for Namespaces is a default link
# ToDo: Default for patterns is plain wikitext
$return = true;
}
if( $return === true ) {
# True (treat as plain link) was returned, call the defaultLinkHook
- $return = CoreLinkFunctions::defaultLinkHook( $parser, $holders, $markers, $title,
+ $return = CoreLinkFunctions::defaultLinkHook( $parser, $holders, $markers, $title,
$titleText, $paramText, $leadingColon );
}
if( $return === false ) {
wfProfileOut( __METHOD__ );
return $return;
}
-
+
}
class LinkMarkerReplacer {
-
+
protected $markers, $nextId, $parser, $holders, $callback;
-
+
function __construct( $parser, $holders, $callback ) {
$this->nextId = 0;
$this->markers = array();
$this->holders = $holders;
$this->callback = $callback;
}
-
+
function addMarker($titleText, $paramText) {
$id = $this->nextId++;
$this->markers[$id] = array( $titleText, $paramText );
return "<!-- LINKMARKER $id -->";
}
-
+
function findMarker( $string ) {
return (bool) preg_match('/<!-- LINKMARKER [0-9]+ -->/', $string );
}
-
+
function expand( $string ) {
return StringUtils::delimiterReplaceCallback( "<!-- LINKMARKER ", " -->", array( &$this, 'callback' ), $string );
}
-
+
function callback( $m ) {
$id = intval($m[1]);
if( !array_key_exists($id, $this->markers) ) return $m[0];
array_unshift( $args, $this->parser );
return call_user_func_array( $this->callback, $args );
}
-
}
* cache may be implemented at a later date which takes further advantage of these strict
* dependency requirements.
*
+ * @throws MWException
* @return PPNode_DOM
*/
function preprocessToObj( $text, $flags = 0 ) {
}
// Fail if the number of elements exceeds acceptable limits
- // Do not attempt to generate the DOM
+ // Do not attempt to generate the DOM
$this->parser->mGeneratedPPNodeCount += substr_count( $xml, '<' );
$max = $this->parser->mOptions->getMaxGeneratedPPNodeCount();
if ( $this->parser->mGeneratedPPNodeCount > $max ) {
* - index String index
* - value PPNode value
*
+ * @throws MWException
* @return array
*/
function splitArg() {
* Split an "<ext>" node into an associative array containing name, attr, inner and close
* All values in the resulting array are PPNodes. Inner and close are optional.
*
+ * @throws MWException
* @return array
*/
function splitExt() {
/**
* Split a "<h>" node
+ * @throws MWException
* @return array
*/
function splitHeading() {
* cache may be implemented at a later date which takes further advantage of these strict
* dependency requirements.
*
+ * @throws MWException
* @return PPNode_Hash_Tree
*/
function preprocessToObj( $text, $flags = 0 ) {
* Create a new child frame
* $args is optionally a multi-root PPNode or array containing the template arguments
*
- * @param $args PPNode_Hash_Array|array
+ * @param array|bool|\PPNode_Hash_Array $args PPNode_Hash_Array|array
* @param $title Title|bool
*
+ * @param int $indexOffset
+ * @throws MWException
* @return PPTemplateFrame_Hash
*/
function newChild( $args = false, $title = false, $indexOffset = 0 ) {
* - index String index
* - value PPNode value
*
+ * @throws MWException
* @return array
*/
function splitArg() {
* Split an "<ext>" node into an associative array containing name, attr, inner and close
* All values in the resulting array are PPNodes. Inner and close are optional.
*
+ * @throws MWException
* @return array
*/
function splitExt() {
/**
* Split an "<h>" node
*
+ * @throws MWException
* @return array
*/
function splitHeading() {
/**
* Split a "<template>" or "<tplarg>" node
*
+ * @throws MWException
* @return array
*/
function splitTemplate() {
/**
* Split a <template> or <tplarg> node
*
+ * @throws MWException
* @return array
*/
function splitTemplate() {
* @return mixed
*/
protected function unstripType( $type, $text ) {
- // Shortcut
+ // Shortcut
if ( !count( $this->data[$type] ) ) {
return $text;
}
. '</span>';
}
if ( $this->recursionLevel >= self::UNSTRIP_RECURSION_LIMIT ) {
- return '<span class="error">' .
+ return '<span class="error">' .
wfMessage( 'parser-unstrip-recursion-limit' )
->numParams( self::UNSTRIP_RECURSION_LIMIT )->inContentLanguage()->text() .
'</span>';
}
/**
- * Get a StripState object which is sufficient to unstrip the given text.
+ * Get a StripState object which is sufficient to unstrip the given text.
* It will contain the minimum subset of strip items necessary.
*
* @param $text string
*
* @param $id Mixed: source ID (string), or array( id1 => props1, id2 => props2, ... )
* @param $properties Array: source properties
+ * @throws MWException
*/
public function addSource( $id, $properties = null) {
// Allow multiple sources to be registered in one call
* associative array mapping message key to value, or a JSON-encoded message blob containing
* the same data, wrapped in an XmlJsCode object.
*
+ * @throws MWException
* @return string
*/
public static function makeLoaderImplementScript( $name, $scripts, $styles, $messages ) {
* to $wgScriptPath
*
* Below is a description for the $options array:
+ * @throws MWException
* @par Construction options:
* @code
- * array(
- * // Base path to prepend to all local paths in $options. Defaults to $IP
- * 'localBasePath' => [base path],
- * // Base path to prepend to all remote paths in $options. Defaults to $wgScriptPath
- * 'remoteBasePath' => [base path],
- * // Equivalent of remoteBasePath, but relative to $wgExtensionAssetsPath
- * 'remoteExtPath' => [base path],
- * // Scripts to always include
- * 'scripts' => [file path string or array of file path strings],
- * // Scripts to include in specific language contexts
- * 'languageScripts' => array(
- * [language code] => [file path string or array of file path strings],
- * ),
- * // Scripts to include in specific skin contexts
- * 'skinScripts' => array(
- * [skin name] => [file path string or array of file path strings],
- * ),
- * // Scripts to include in debug contexts
- * 'debugScripts' => [file path string or array of file path strings],
- * // Scripts to include in the startup module
- * 'loaderScripts' => [file path string or array of file path strings],
- * // Modules which must be loaded before this module
- * 'dependencies' => [modile name string or array of module name strings],
- * // Styles to always load
- * 'styles' => [file path string or array of file path strings],
- * // Styles to include in specific skin contexts
- * 'skinStyles' => array(
- * [skin name] => [file path string or array of file path strings],
- * ),
- * // Messages to always load
- * 'messages' => [array of message key strings],
- * // Group which this module should be loaded together with
- * 'group' => [group name string],
- * // Position on the page to load this module at
- * 'position' => ['bottom' (default) or 'top']
- * )
+ * array(
+ * // Base path to prepend to all local paths in $options. Defaults to $IP
+ * 'localBasePath' => [base path],
+ * // Base path to prepend to all remote paths in $options. Defaults to $wgScriptPath
+ * 'remoteBasePath' => [base path],
+ * // Equivalent of remoteBasePath, but relative to $wgExtensionAssetsPath
+ * 'remoteExtPath' => [base path],
+ * // Scripts to always include
+ * 'scripts' => [file path string or array of file path strings],
+ * // Scripts to include in specific language contexts
+ * 'languageScripts' => array(
+ * [language code] => [file path string or array of file path strings],
+ * ),
+ * // Scripts to include in specific skin contexts
+ * 'skinScripts' => array(
+ * [skin name] => [file path string or array of file path strings],
+ * ),
+ * // Scripts to include in debug contexts
+ * 'debugScripts' => [file path string or array of file path strings],
+ * // Scripts to include in the startup module
+ * 'loaderScripts' => [file path string or array of file path strings],
+ * // Modules which must be loaded before this module
+ * 'dependencies' => [modile name string or array of module name strings],
+ * // Styles to always load
+ * 'styles' => [file path string or array of file path strings],
+ * // Styles to include in specific skin contexts
+ * 'skinStyles' => array(
+ * [skin name] => [file path string or array of file path strings],
+ * ),
+ * // Messages to always load
+ * 'messages' => [array of message key strings],
+ * // Group which this module should be loaded together with
+ * 'group' => [group name string],
+ * // Position on the page to load this module at
+ * 'position' => ['bottom' (default) or 'top']
+ * )
* @endcode
*/
public function __construct( $options = array(), $localBasePath = null,
* Gets the contents of a list of JavaScript files.
*
* @param $scripts Array: List of file paths to scripts to read, remap and concetenate
+ * @throws MWException
* @return String: Concatenated and remapped JavaScript data from $scripts
*/
protected function readScriptFiles( array $scripts ) {
protected function readStyleFile( $path, $flip ) {
$localPath = $this->getLocalPath( $path );
if ( !file_exists( $localPath ) ) {
- throw new MWException( __METHOD__.": style file not found: \"$localPath\"" );
+ $msg = __METHOD__.": style file not found: \"$localPath\"";
+ wfDebugLog( 'resourceloader', $msg );
+ throw new MWException( $msg );
}
$style = file_get_contents( $localPath );
if ( $flip ) {
if ( !$revision ) {
return null;
}
- return $revision->getRawText();
+
+ $content = $revision->getContent( Revision::RAW );
+ $model = $content->getModel();
+
+ if ( $model !== CONTENT_MODEL_CSS && $model !== CONTENT_MODEL_JAVASCRIPT ) {
+ wfDebug( __METHOD__ . "bad content model #$model for JS/CSS page!\n" );
+ return null;
+ }
+
+ return $content->getNativeData(); //NOTE: this is safe, we know it's JS or CSS
}
/* Methods */
* comment: The log comment
* authorsIds: The array of the user IDs of the offenders
* authorsIPs: The array of the IP/anon user offenders
+ * @throws MWException
*/
protected function updateLog( $params ) {
// Get the URL param's corresponding DB field
*/
protected function initText() {
if ( !isset( $this->mText ) ) {
- if ( $this->mRevision != null )
- $this->mText = $this->mRevision->getText();
- else // TODO: can we fetch raw wikitext for commons images?
+ if ( $this->mRevision != null ) {
+ //TODO: if we could plug in some code that knows about special content models *and* about
+ // special features of the search engine, the search could benefit.
+ $content = $this->mRevision->getContent();
+ $this->mText = $content->getTextForSearchIndex();
+ } else { // TODO: can we fetch raw wikitext for commons images?
$this->mText = '';
-
+ }
}
}
function getTextSnippet( $terms ) {
global $wgUser, $wgAdvancedSearchHighlighting;
$this->initText();
+
+ // TODO: make highliter take a content object. Make ContentHandler a factory for SearchHighliter.
list( $contextlines, $contextchars ) = SearchEngine::userHighlightPrefs( $wgUser );
$h = new SearchHighlighter();
if ( $wgAdvancedSearchHighlighting )
--- /dev/null
+<?php
+
+/**
+ * Class representing a MediaWiki site.
+ *
+ * @since 1.21
+ *
+ * @file
+ * @ingroup Site
+ *
+ * @licence GNU GPL v2+
+ * @author John Erling Blad < jeblad@gmail.com >
+ * @author Daniel Kinzler
+ * @author Jeroen De Dauw < jeroendedauw@gmail.com >
+ */
+class MediaWikiSite extends SiteObject {
+
+ const PATH_FILE = 'file_path';
+ const PATH_PAGE = 'page_path';
+
+ /**
+ * @since 1.21
+ *
+ * @param integer $globalId
+ *
+ * @return MediaWikiSite
+ */
+ public static function newFromGlobalId( $globalId ) {
+ return SitesTable::singleton()->newRow( array(
+ 'type' => Site::TYPE_MEDIAWIKI,
+ 'global_key' => $globalId,
+ ), true );
+ }
+
+ /**
+ * Returns the database form of the given title.
+ *
+ * @since 1.21
+ *
+ * @param String $title the target page's title, in normalized form.
+ *
+ * @return String
+ */
+ public function toDBKey( $title ) {
+ return str_replace( ' ', '_', $title );
+ }
+
+ /**
+ * Returns the normalized form of the given page title, using the normalization rules of the given site.
+ * If the given title is a redirect, the redirect weill be resolved and the redirect target is returned.
+ *
+ * @note : This actually makes an API request to the remote site, so beware that this function is slow and depends
+ * on an external service.
+ *
+ * @note : If MW_PHPUNIT_TEST is defined or $egWBRemoteTitleNormalization is set to false, the call to the
+ * external site is skipped, and the title is normalized using the local normalization rules as
+ * implemented by the Title class.
+ *
+ * @see Site::normalizePageName
+ *
+ * @since 1.21
+ *
+ * @param string $pageName
+ *
+ * @return string
+ * @throws MWException
+ */
+ public function normalizePageName( $pageName ) {
+ global $egWBRemoteTitleNormalization;
+
+ // Check if we have strings as arguments.
+ if ( !is_string( $pageName ) ) {
+ throw new MWException( '$pageName must be a string' );
+ }
+
+ // Go on call the external site
+ if ( defined( 'MW_PHPUNIT_TEST' ) ) {
+ // If the code is under test, don't call out to other sites, just normalize locally.
+ // Note: this may cause results to be inconsistent with the actual normalization used by the respective remote site!
+
+ $t = Title::newFromText( $pageName );
+ return $t->getPrefixedText();
+ } else {
+
+ // Make sure the string is normalized into NFC (due to the bug 40017)
+ // but do nothing to the whitespaces, that should work appropriately.
+ // @see https://bugzilla.wikimedia.org/show_bug.cgi?id=40017
+ $pageName = UtfNormal::cleanUp( $pageName );
+
+ // Build the args for the specific call
+ $args = array(
+ 'action' => 'query',
+ 'prop' => 'info',
+ 'redirects' => true,
+ 'converttitles' => true,
+ 'format' => 'json',
+ 'titles' => $pageName,
+ //@todo: options for maxlag and maxage
+ // Note that maxlag will lead to a long delay before a reply is made,
+ // but that maxage can avoid the extreme delay. On the other hand
+ // maxage could be nice to use anyhow as it stops unnecessary requests.
+ // Also consider smaxage if maxage is used.
+ );
+
+ $url = $this->getFileUrl( 'api.php' ) . '?' . wfArrayToCgi( $args );
+
+ // Go on call the external site
+ //@todo: we need a good way to specify a timeout here.
+ $ret = Http::get( $url );
+ }
+
+ if ( $ret === false ) {
+ wfDebugLog( "MediaWikiSite", "call to external site failed: $url" );
+ return false;
+ }
+
+ $data = FormatJson::decode( $ret, true );
+
+ if ( !is_array( $data ) ) {
+ wfDebugLog( "MediaWikiSite", "call to <$url> returned bad json: " . $ret );
+ return false;
+ }
+
+ $page = static::extractPageRecord( $data, $pageName );
+
+ if ( isset( $page['missing'] ) ) {
+ wfDebugLog( "MediaWikiSite", "call to <$url> returned a marker for a missing page title! " . $ret );
+ return false;
+ }
+
+ if ( isset( $page['invalid'] ) ) {
+ wfDebugLog( "MediaWikiSite", "call to <$url> returned a marker for an invalid page title! " . $ret );
+ return false;
+ }
+
+ if ( !isset( $page['title'] ) ) {
+ wfDebugLog( "MediaWikiSite", "call to <$url> did not return a page title! " . $ret );
+ return false;
+ }
+
+ return $page['title'];
+ }
+
+
+ /**
+ * Get normalization record for a given page title from an API response.
+ *
+ * @since 1.21
+ *
+ * @param array $externalData A reply from the API on a external server.
+ * @param string $pageTitle Identifies the page at the external site, needing normalization.
+ *
+ * @return array|false a 'page' structure representing the page identified by $pageTitle.
+ */
+ private static function extractPageRecord( $externalData, $pageTitle ) {
+ // If there is a special case with only one returned page
+ // we can cheat, and only return
+ // the single page in the "pages" substructure.
+ if ( isset( $externalData['query']['pages'] ) ) {
+ $pages = array_values( $externalData['query']['pages'] );
+ if ( count( $pages) === 1 ) {
+ return $pages[0];
+ }
+ }
+ // This is only used during internal testing, as it is assumed
+ // a more optimal (and lossfree) storage.
+ // Make initial checks and return if prerequisites are not meet.
+ if ( !is_array( $externalData ) || !isset( $externalData['query'] ) ) {
+ return false;
+ }
+ // Loop over the tree different named structures, that otherwise are similar
+ $structs = array(
+ 'normalized' => 'from',
+ 'converted' => 'from',
+ 'redirects' => 'from',
+ 'pages' => 'title'
+ );
+ foreach ( $structs as $listId => $fieldId ) {
+ // Check if the substructure exist at all.
+ if ( !isset( $externalData['query'][$listId] ) ) {
+ continue;
+ }
+ // Filter the substructure down to what we actually are using.
+ $collectedHits = array_filter(
+ array_values( $externalData['query'][$listId] ),
+ function( $a ) use ( $fieldId, $pageTitle ) {
+ return $a[$fieldId] === $pageTitle;
+ }
+ );
+ // If still looping over normalization, conversion or redirects,
+ // then we need to keep the new page title for later rounds.
+ if ( $fieldId === 'from' && is_array( $collectedHits ) ) {
+ switch ( count( $collectedHits ) ) {
+ case 0:
+ break;
+ case 1:
+ $pageTitle = $collectedHits[0]['to'];
+ break;
+ default:
+ return false;
+ }
+ }
+ // If on the pages structure we should prepare for returning.
+ elseif ( $fieldId === 'title' && is_array( $collectedHits ) ) {
+ switch ( count( $collectedHits ) ) {
+ case 0:
+ return false;
+ case 1:
+ return array_shift( $collectedHits );
+ default:
+ return false;
+ }
+ }
+ }
+ // should never be here
+ return false;
+ }
+
+ /**
+ * @see Site::getLinkPathType
+ * Returns Site::PATH_PAGE
+ *
+ * @since 1.21
+ *
+ * @return string
+ */
+ public function getLinkPathType() {
+ return self::PATH_PAGE;
+ }
+
+ /**
+ * Returns the relative page path.
+ *
+ * @since 1.21
+ *
+ * @return string
+ */
+ public function getRelativePagePath() {
+ return parse_url( $this->getPath( self::PATH_PAGE ), PHP_URL_PATH );
+ }
+
+ /**
+ * Returns the relative file path.
+ *
+ * @since 1.21
+ *
+ * @return string
+ */
+ public function getRelativeFilePath() {
+ return parse_url( $this->getPath( self::PATH_FILE ), PHP_URL_PATH );
+ }
+
+ /**
+ * Sets the relative page path.
+ *
+ * @since 1.21
+ *
+ * @param string $path
+ */
+ public function setPagePath( $path ) {
+ $this->setPath( self::PATH_PAGE, $path );
+ }
+
+ /**
+ * Sets the relative file path.
+ *
+ * @since 1.21
+ *
+ * @param string $path
+ */
+ public function setFilePath( $path ) {
+ $this->setPath( self::PATH_FILE, $path );
+ }
+
+ /**
+ * @see Site::getPagePath
+ *
+ * This implementation returns a URL constructed using the path returned by getLinkPath().
+ * In addition to the default behaviour implemented by SiteObject::getPageUrl(), this
+ * method converts the $pageName to DBKey-format by replacing spaces with underscores
+ * before using it in the URL.
+ *
+ * @since 1.21
+ *
+ * @param string|false
+ *
+ * @return string
+ */
+ public function getPageUrl( $pageName = false ) {
+ $url = $this->getLinkPath();
+
+ if ( $url === false ) {
+ return false;
+ }
+
+ if ( $pageName !== false ) {
+ $pageName = $this->toDBKey( trim( $pageName ) );
+ $url = str_replace( '$1', wfUrlencode( $pageName ), $url ) ;
+ }
+
+ return $url;
+ }
+
+ /**
+ * Returns the full file path (ie site url + relative file path).
+ * The path should go at the $1 marker. If the $path
+ * argument is provided, the marker will be replaced by it's value.
+ *
+ * @since 1.21
+ *
+ * @param string|false $path
+ *
+ * @return string
+ */
+ public function getFileUrl( $path = false ) {
+ $filePath = $this->getPath( self::PATH_FILE );
+
+ if ( $filePath !== false ) {
+ $filePath = str_replace( '$1', $path, $filePath );
+ }
+
+ return $filePath;
+ }
+
+}
--- /dev/null
+<?php
+
+/**
+ * Interface for site 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.21
+ *
+ * @file
+ * @ingroup Site
+ *
+ * @licence GNU GPL v2+
+ * @author Jeroen De Dauw < jeroendedauw@gmail.com >
+ */
+interface Site {
+
+ const TYPE_UNKNOWN = 'unknown';
+ const TYPE_MEDIAWIKI = 'mediawiki';
+
+ const GROUP_NONE = 'none';
+
+ const ID_INTERWIKI = 'interwiki';
+ const ID_EQUIVALENT = 'equivalent';
+
+ const SOURCE_LOCAL = 'local';
+
+ /**
+ * Returns the global site identifier (ie enwiktionary).
+ *
+ * @since 1.21
+ *
+ * @return string
+ */
+ public function getGlobalId();
+
+ /**
+ * Sets the global site identifier (ie enwiktionary).
+ *
+ * @since 1.21
+ *
+ * @param string $globalId
+ */
+ public function setGlobalId( $globalId );
+
+ /**
+ * Returns the type of the site (ie mediawiki).
+ *
+ * @since 1.21
+ *
+ * @return string
+ */
+ public function getType();
+
+ /**
+ * Sets the type of the site (ie mediawiki).
+ * TODO: remove, we cannot change this after instantiation
+ *
+ * @since 1.21
+ *
+ * @param string $type
+ */
+ public function setType( $type );
+
+ /**
+ * Gets the type of the site (ie wikipedia).
+ *
+ * @since 1.21
+ *
+ * @return string
+ */
+ public function getGroup();
+
+ /**
+ * Sets the type of the site (ie wikipedia).
+ *
+ * @since 1.21
+ *
+ * @param string $group
+ */
+ public function setGroup( $group );
+
+ /**
+ * Returns the source of the site data (ie 'local', 'wikidata', 'my-magical-repo').
+ *
+ * @since 1.21
+ *
+ * @return string
+ */
+ public function getSource();
+
+ /**
+ * Sets the source of the site data (ie 'local', 'wikidata', 'my-magical-repo').
+ *
+ * @since 1.21
+ *
+ * @param string $source
+ */
+ public function setSource( $source );
+
+ /**
+ * Returns the protocol of the site, ie 'http://', 'irc://', '//'
+ * Or false if it's not known.
+ *
+ * @since 1.21
+ *
+ * @return string|false
+ */
+ public function getProtocol();
+
+ /**
+ * Returns the domain of the site, ie en.wikipedia.org
+ * Or false if it's not known.
+ *
+ * @since 1.21
+ *
+ * @return string|false
+ */
+ public function getDomain();
+
+ /**
+ * Returns the full URL for the given page on the site.
+ * Or false if the needed information is not known.
+ *
+ * This generated URL is usually based upon the path returned by getLinkPath(),
+ * but this is not a requirement.
+ *
+ * @since 1.21
+ * @see Site::getLinkPath()
+ *
+ * @param bool|String $page
+ *
+ * @return string|false
+ */
+ public function getPageUrl( $page = false );
+
+ /**
+ * Returns language code of the sites primary language.
+ * Or false if it's not known.
+ *
+ * @since 1.21
+ *
+ * @return string|false
+ */
+ public function getLanguageCode();
+
+ /**
+ * Sets language code of the sites primary language.
+ *
+ * @since 1.21
+ *
+ * @param string $languageCode
+ */
+ public function setLanguageCode( $languageCode );
+
+ /**
+ * Returns the normalized, canonical form of the given page name.
+ * How normalization is performed or what the properties of a normalized name are depends on the site.
+ * The general contract of this method is that the normalized form shall refer to the same content
+ * as the original form, and any other page name referring to the same content will have the same normalized form.
+ *
+ * Note that this method may call out to the target site to perform the normalization, so it may be slow
+ * and fail due to IO errors.
+ *
+ * @since 1.21
+ *
+ * @param string $pageName
+ *
+ * @return string the normalized page name
+ */
+ public function normalizePageName( $pageName );
+
+ /**
+ * Returns the interwiki link identifiers that can be used for this site.
+ *
+ * @since 1.21
+ *
+ * @return array of string
+ */
+ public function getInterwikiIds();
+
+ /**
+ * Returns the equivalent link identifiers that can be used to make
+ * the site show up in interfaces such as the "language links" section.
+ *
+ * @since 1.21
+ *
+ * @return array of string
+ */
+ public function getNavigationIds();
+
+ /**
+ * Adds an local identifier to the site.
+ *
+ * @since 1.21
+ *
+ * @param string $type The type of the identifier, element of the Site::ID_ enum
+ * @param string $identifier
+ */
+ public function addLocalId( $type, $identifier );
+
+ /**
+ * Adds an interwiki id to the site.
+ *
+ * @since 1.21
+ *
+ * @param string $identifier
+ */
+ public function addInterwikiId( $identifier );
+
+ /**
+ * Adds a navigation id to the site.
+ *
+ * @since 1.21
+ *
+ * @param string $identifier
+ */
+ public function addNavigationId( $identifier );
+
+ /**
+ * Saves the site.
+ *
+ * @since 1.21
+ *
+ * @param string|null $functionName
+ */
+ public function save( $functionName = null );
+
+ /**
+ * Returns the internal ID of the site.
+ *
+ * @since 1.21
+ *
+ * @return integer
+ */
+ public function getInternalId();
+
+ /**
+ * Sets the provided url as path of the specified type.
+ *
+ * @since 1.21
+ *
+ * @param string $pathType
+ * @param string $fullUrl
+ */
+ public function setPath( $pathType, $fullUrl );
+
+ /**
+ * Returns the path of the provided type or false if there is no such path.
+ *
+ * @since 1.21
+ *
+ * @param string $pathType
+ *
+ * @return string|false
+ */
+ public function getPath( $pathType );
+
+ /**
+ * Sets the path used to construct links with.
+ * Shall be equivalent to setPath( getLinkPathType(), $fullUrl ).
+ *
+ * @param string $fullUrl
+ *
+ * @since 1.21
+ */
+ public function setLinkPath( $fullUrl );
+
+ /**
+ * Returns the path used to construct links with or false if there is no such path.
+ * Shall be equivalent to getPath( getLinkPathType() ).
+ *
+ * @return string|false
+ */
+ public function getLinkPath();
+
+ /**
+ * Returns the path type used to construct links with.
+ *
+ * @return string|false
+ */
+ public function getLinkPathType();
+
+ /**
+ * Returns the paths as associative array.
+ * The keys are path types, the values are the path urls.
+ *
+ * @since 1.21
+ *
+ * @return array of string
+ */
+ public function getAllPaths();
+
+ /**
+ * Removes the path of the provided type if it's set.
+ *
+ * @since 1.21
+ *
+ * @param string $pathType
+ */
+ public function removePath( $pathType );
+
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+/**
+ * Implementation of SiteList using GenericArrayObject.
+ *
+ * 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.21
+ *
+ * @file
+ * @ingroup Site
+ *
+ * @licence GNU GPL v2+
+ * @author Jeroen De Dauw < jeroendedauw@gmail.com >
+ */
+class SiteArray extends GenericArrayObject implements SiteList {
+
+ /**
+ * Internal site identifiers pointing to their sites offset value.
+ *
+ * @since 1.21
+ *
+ * @var array of integer
+ */
+ protected $byInternalId = array();
+
+ /**
+ * Global site identifiers pointing to their sites offset value.
+ *
+ * @since 1.21
+ *
+ * @var array of string
+ */
+ protected $byGlobalId = array();
+
+ /**
+ * @see GenericArrayObject::getObjectType
+ *
+ * @since 1.21
+ *
+ * @return string
+ */
+ public function getObjectType() {
+ return 'Site';
+ }
+
+ /**
+ * @see GenericArrayObject::preSetElement
+ *
+ * @since 1.21
+ *
+ * @param int|string $index
+ * @param Site $site
+ *
+ * @return boolean
+ */
+ protected function preSetElement( $index, $site ) {
+ if ( $this->hasSite( $site->getGlobalId() ) ) {
+ $this->removeSite( $site->getGlobalId() );
+ }
+
+ $this->byGlobalId[$site->getGlobalId()] = $index;
+ $this->byInternalId[$site->getInternalId()] = $index;
+
+ return true;
+ }
+
+ /**
+ * @see ArrayObject::offsetUnset()
+ *
+ * @since 1.21
+ *
+ * @param mixed $index
+ */
+ public function offsetUnset( $index ) {
+ /**
+ * @var Site $site
+ */
+ $site = $this->offsetGet( $index );
+
+ if ( $site !== false ) {
+ unset( $this->byGlobalId[$site->getGlobalId()] );
+ unset( $this->byInternalId[$site->getInternalId()] );
+ }
+
+ parent::offsetUnset( $index );
+ }
+
+ /**
+ * @see SiteList::getGlobalIdentifiers
+ *
+ * @since 1.21
+ *
+ * @return array
+ */
+ public function getGlobalIdentifiers() {
+ return array_keys( $this->byGlobalId );
+ }
+
+ /**
+ * @see SiteList::hasSite
+ *
+ * @param string $globalSiteId
+ *
+ * @return boolean
+ */
+ public function hasSite( $globalSiteId ) {
+ return array_key_exists( $globalSiteId, $this->byGlobalId );
+ }
+
+ /**
+ * @see SiteList::getSite
+ *
+ * @since 1.21
+ *
+ * @param string $globalSiteId
+ *
+ * @return Site
+ */
+ public function getSite( $globalSiteId ) {
+ return $this->offsetGet( $this->byGlobalId[$globalSiteId] );
+ }
+
+ /**
+ * @see SiteList::removeSite
+ *
+ * @since 1.21
+ *
+ * @param string $globalSiteId
+ */
+ public function removeSite( $globalSiteId ) {
+ $this->offsetUnset( $this->byGlobalId[$globalSiteId] );
+ }
+
+ /**
+ * @see SiteList::isEmpty
+ *
+ * @since 1.21
+ *
+ * @return boolean
+ */
+ public function isEmpty() {
+ return $this->byGlobalId === array();
+ }
+
+ /**
+ * @see SiteList::hasInternalId
+ *
+ * @param integer $id
+ *
+ * @return boolean
+ */
+ public function hasInternalId( $id ) {
+ return array_key_exists( $id, $this->byInternalId );
+ }
+
+ /**
+ * @see SiteList::getSiteByInternalId
+ *
+ * @since 1.21
+ *
+ * @param integer $id
+ *
+ * @return Site
+ */
+ public function getSiteByInternalId( $id ) {
+ return $this->offsetGet( $this->byInternalId[$id] );
+ }
+
+ /**
+ * @see SiteList::removeSiteByInternalId
+ *
+ * @since 1.21
+ *
+ * @param integer $id
+ */
+ public function removeSiteByInternalId( $id ) {
+ $this->offsetUnset( $this->byInternalId[$id] );
+ }
+
+ /**
+ * @see SiteList::setSite
+ *
+ * @since 1.21
+ *
+ * @param Site $site
+ */
+ public function setSite( Site $site ) {
+ $this[] = $site;
+ }
+
+}
--- /dev/null
+<?php
+
+/**
+ * Interface for lists of Site 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.21
+ *
+ * @file
+ * @ingroup Site
+ *
+ * @licence GNU GPL v2+
+ * @author Jeroen De Dauw < jeroendedauw@gmail.com >
+ */
+interface SiteList extends Countable, Traversable, Serializable, ArrayAccess {
+
+ /**
+ * Returns all the global site identifiers.
+ * Optionally only those belonging to the specified group.
+ *
+ * @since 1.21
+ *
+ * @return array
+ */
+ public function getGlobalIdentifiers();
+
+ /**
+ * Returns if the list contains the site with the provided global site identifier.
+ *
+ * @param string $globalSiteId
+ *
+ * @return boolean
+ */
+ public function hasSite( $globalSiteId );
+
+ /**
+ * Returns the Site with the provided global site identifier.
+ * The site needs to exist, so if not sure, call hasGlobalId first.
+ *
+ * @since 1.21
+ *
+ * @param string $globalSiteId
+ *
+ * @return Site
+ */
+ public function getSite( $globalSiteId );
+
+ /**
+ * Removes the site with the specified global site identifier.
+ * The site needs to exist, so if not sure, call hasGlobalId first.
+ *
+ * @since 1.21
+ *
+ * @param string $globalSiteId
+ */
+ public function removeSite( $globalSiteId );
+
+ /**
+ * Returns if the list contains the site with the provided site id.
+ *
+ * @param integer $id
+ *
+ * @return boolean
+ */
+ public function hasInternalId( $id );
+
+ /**
+ * Returns the Site with the provided site id.
+ * The site needs to exist, so if not sure, call has first.
+ *
+ * @since 1.21
+ *
+ * @param integer $id
+ *
+ * @return Site
+ */
+ public function getSiteByInternalId( $id );
+
+ /**
+ * Removes the site with the specified site id.
+ * The site needs to exist, so if not sure, call has first.
+ *
+ * @since 1.21
+ *
+ * @param integer $id
+ */
+ public function removeSiteByInternalId( $id );
+
+ /**
+ * Sets a site in the list. If the site was not there,
+ * it will be added. If it was, it will be updated.
+ *
+ * @since 1.21
+ *
+ * @param Site $site
+ */
+ public function setSite( Site $site );
+
+ /**
+ * Returns if the site list contains no sites.
+ *
+ * @since 1.21
+ *
+ * @return boolean
+ */
+ public function isEmpty();
+
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+/**
+ * Class representing a single site.
+ *
+ * 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.21
+ *
+ * @file
+ * @ingroup Site
+ *
+ * @licence GNU GPL v2+
+ * @author Jeroen De Dauw < jeroendedauw@gmail.com >
+ * @author Daniel Werner
+ */
+class SiteObject extends ORMRow implements Site {
+
+ const PATH_LINK = 'link';
+
+ /**
+ * Holds the local ids for this site.
+ * You can obtain them via @see getLocalIds
+ *
+ * @since 1.21
+ *
+ * @var array|false
+ */
+ protected $localIds = false;
+
+ /**
+ * @see Site::getGlobalId
+ *
+ * @since 1.21
+ *
+ * @return string
+ */
+ public function getGlobalId() {
+ return $this->getField( 'global_key' );
+ }
+
+ /**
+ * @see Site::setGlobalId
+ *
+ * @since 1.21
+ *
+ * @param string $globalId
+ */
+ public function setGlobalId( $globalId ) {
+ $this->setField( 'global_key', $globalId );
+ }
+
+ /**
+ * @see Site::getType
+ *
+ * @since 1.21
+ *
+ * @return string
+ */
+ public function getType() {
+ return $this->getField( 'type' );
+ }
+
+ /**
+ * @see Site::setType
+ *
+ * @since 1.21
+ *
+ * @param string $type
+ */
+ public function setType( $type ) {
+ $this->setField( 'type', $type );
+ }
+
+ /**
+ * @see Site::getGroup
+ *
+ * @since 1.21
+ *
+ * @return string
+ */
+ public function getGroup() {
+ return $this->getField( 'group' );
+ }
+
+ /**
+ * @see Site::setGroup
+ *
+ * @since 1.21
+ *
+ * @param string $group
+ */
+ public function setGroup( $group ) {
+ $this->setField( 'group', $group );
+ }
+
+ /**
+ * @see Site::getSource
+ *
+ * @since 1.21
+ *
+ * @return string
+ */
+ public function getSource() {
+ return $this->getField( 'source' );
+ }
+
+ /**
+ * @see Site::setSource
+ *
+ * @since 1.21
+ *
+ * @param string $source
+ */
+ public function setSource( $source ) {
+ $this->setField( 'source', $source );
+ }
+
+ /**
+ * @see Site::getDomain
+ *
+ * @since 1.21
+ *
+ * @return string|false
+ */
+ public function getDomain() {
+ $path = $this->getLinkPath();
+
+ if ( $path === false ) {
+ return false;
+ }
+
+ return parse_url( $path, PHP_URL_HOST );
+ }
+
+ /**
+ * @see Site::getProtocol
+ *
+ * @since 1.21
+ *
+ * @return string|false
+ */
+ public function getProtocol() {
+ $path = $this->getLinkPath();
+
+ if ( $path === false ) {
+ return false;
+ }
+
+ return parse_url( $path, PHP_URL_SCHEME );
+ }
+
+ /**
+ * Sets the path used to construct links with.
+ * @see Site::setLinkPath
+ *
+ * @param string $fullUrl
+ *
+ * @since 1.21
+ *
+ * @throws MWException
+ */
+ public function setLinkPath( $fullUrl ) {
+ $type = $this->getLinkPathType();
+
+ if ( $type === false ) {
+ throw new MWException( "This SiteObject does not support link paths." );
+ }
+
+ $this->setPath( $type, $fullUrl );
+ }
+
+ /**
+ * Returns the path path used to construct links with or false if there is no such path.
+ *
+ * @see Site::getLinkPath
+ *
+ * @return string|false
+ */
+ public function getLinkPath() {
+ $type = $this->getLinkPathType();
+ return $type === false ? false : $this->getPath( $type );
+ }
+
+ /**
+ * @see Site::getLinkPathType
+ *
+ * Returns the main path type, that is the type of the path that should generally be used to construct links
+ * to the target site.
+ *
+ * This default implementation returns SiteObject::PATH_LINK as the default path type. Subclasses can override this
+ * to define a different default path type, or return false to disable site links.
+ *
+ * @since 1.21
+ *
+ * @return string|false
+ */
+ public function getLinkPathType() {
+ return self::PATH_LINK;
+ }
+
+ /**
+ * @see Site::getPageUrl
+ *
+ * This implementation returns a URL constructed using the path returned by getLinkPath().
+ *
+ * @since 1.21
+ *
+ * @param bool|String $pageName
+ *
+ * @return string|false
+ */
+ public function getPageUrl( $pageName = false ) {
+ $url = $this->getLinkPath();
+
+ if ( $url === false ) {
+ return false;
+ }
+
+ if ( $pageName !== false ) {
+ $url = str_replace( '$1', rawurlencode( $pageName ), $url ) ;
+ }
+
+ return $url;
+ }
+
+ /**
+ * Returns $pageName without changes.
+ * Subclasses may override this to apply some kind of normalization.
+ *
+ * @see Site::normalizePageName
+ *
+ * @since 1.21
+ *
+ * @param string $pageName
+ *
+ * @return string
+ */
+ public function normalizePageName( $pageName ) {
+ return $pageName;
+ }
+
+ /**
+ * Returns the value of a type specific field, or the value
+ * of the $default parameter in case it's not set.
+ *
+ * @since 1.21
+ *
+ * @param string $fieldName
+ * @param mixed $default
+ *
+ * @return array
+ */
+ protected function getExtraData( $fieldName, $default = null ) {
+ $data = $this->getField( 'data', array() );
+ return array_key_exists( $fieldName,$data ) ? $data[$fieldName] : $default;
+ }
+
+ /**
+ * Sets the value of a type specific field.
+ * @since 1.21
+ *
+ * @param string $fieldName
+ * @param mixed $value
+ */
+ protected function setExtraData( $fieldName, $value = null ) {
+ $data = $this->getField( 'data', array() );
+ $data[$fieldName] = $value;
+ $this->setField( 'data', $data );
+ }
+
+ /**
+ * @see Site::getLanguageCode
+ *
+ * @since 1.21
+ *
+ * @return string|false
+ */
+ public function getLanguageCode() {
+ return $this->getField( 'language', false );
+ }
+
+ /**
+ * @see Site::setLanguageCode
+ *
+ * @since 1.21
+ *
+ * @param string $languageCode
+ */
+ public function setLanguageCode( $languageCode ) {
+ $this->setField( 'language', $languageCode );
+ }
+
+ /**
+ * Returns the local identifiers of this site.
+ *
+ * @since 1.21
+ *
+ * @param string $type
+ *
+ * @return array
+ */
+ protected function getLocalIds( $type ) {
+ if ( $this->localIds === false ) {
+ $this->loadLocalIds();
+ }
+
+ return array_key_exists( $type, $this->localIds ) ? $this->localIds[$type] : array();
+ }
+
+ /**
+ * Loads the local ids for the site.
+ *
+ * @since 1.21
+ */
+ protected function loadLocalIds() {
+ $dbr = wfGetDB( $this->getTable()->getReadDb() );
+
+ $ids = $dbr->select(
+ 'site_identifiers',
+ array(
+ 'si_type',
+ 'si_key',
+ ),
+ array(
+ 'si_site' => $this->getId(),
+ ),
+ __METHOD__
+ );
+
+ $this->localIds = array();
+
+ foreach ( $ids as $id ) {
+ $this->addLocalId( $id->si_type, $id->si_key );
+ }
+ }
+
+ /**
+ * Adds a local identifier.
+ *
+ * @since 1.21
+ *
+ * @param string $type
+ * @param string $identifier
+ */
+ public function addLocalId( $type, $identifier ) {
+ if ( $this->localIds === false ) {
+ $this->localIds = array();
+ }
+
+ if ( !array_key_exists( $type, $this->localIds ) ) {
+ $this->localIds[$type] = array();
+ }
+
+ if ( !in_array( $identifier, $this->localIds[$type] ) ) {
+ $this->localIds[$type][] = $identifier;
+ }
+ }
+
+ /**
+ * @see Site::addInterwikiId
+ *
+ * @since 1.21
+ *
+ * @param string $identifier
+ */
+ public function addInterwikiId( $identifier ) {
+ $this->addLocalId( 'interwiki', $identifier );
+ }
+
+ /**
+ * @see Site::addNavigationId
+ *
+ * @since 1.21
+ *
+ * @param string $identifier
+ */
+ public function addNavigationId( $identifier ) {
+ $this->addLocalId( 'equivalent', $identifier );
+ }
+
+ /**
+ * @see Site::getInterwikiIds
+ *
+ * @since 1.21
+ *
+ * @return array of string
+ */
+ public function getInterwikiIds() {
+ return $this->getLocalIds( 'interwiki' );
+ }
+
+ /**
+ * @see Site::getNavigationIds
+ *
+ * @since 1.21
+ *
+ * @return array of string
+ */
+ public function getNavigationIds() {
+ return $this->getLocalIds( 'equivalent' );
+ }
+
+ /**
+ * @see Site::getInternalId
+ *
+ * @since 1.21
+ *
+ * @return integer
+ */
+ public function getInternalId() {
+ return $this->getId();
+ }
+
+ /**
+ * @see ORMRow::save
+ * @see Site::save
+ *
+ * @since 1.21
+ *
+ * @param string|null $functionName
+ *
+ * @return boolean Success indicator
+ */
+ public function save( $functionName = null ) {
+ $dbw = wfGetDB( DB_MASTER );
+
+ $trx = $dbw->trxLevel();
+
+ if ( $trx == 0 ) {
+ $dbw->begin( __METHOD__ );
+ }
+
+ $this->setField( 'protocol', $this->getProtocol() );
+ $this->setField( 'domain', strrev( $this->getDomain() ) . '.' );
+
+ $existedAlready = $this->hasIdField();
+
+ $success = parent::save( $functionName );
+
+ if ( $success && $existedAlready ) {
+ $dbw->delete(
+ 'site_identifiers',
+ array( 'si_site' => $this->getId() ),
+ __METHOD__
+ );
+ }
+
+ if ( $success && $this->localIds !== false ) {
+ foreach ( $this->localIds as $type => $ids ) {
+ foreach ( $ids as $id ) {
+ $dbw->insert(
+ 'site_identifiers',
+ array(
+ 'si_site' => $this->getId(),
+ 'si_type' => $type,
+ 'si_key' => $id,
+ ),
+ __METHOD__
+ );
+ }
+ }
+ }
+
+ if ( $trx == 0 ) {
+ $dbw->commit( __METHOD__ );
+ }
+
+ return $success;
+ }
+
+ /**
+ * @see Site::setPath
+ *
+ * @since 1.21
+ *
+ * @param string $pathType
+ * @param string $fullUrl
+ */
+ public function setPath( $pathType, $fullUrl ) {
+ $paths = $this->getExtraData( 'paths', array() );
+ $paths[$pathType] = $fullUrl;
+ $this->setExtraData( 'paths', $paths );
+ }
+
+ /**
+ * @see Sitres::getPath
+ *
+ * @since 1.21
+ *
+ * @param string $pathType
+ *
+ * @return string|false
+ */
+ public function getPath( $pathType ) {
+ $paths = $this->getExtraData( 'paths', array() );
+ return array_key_exists( $pathType, $paths ) ? $paths[$pathType] : false;
+ }
+
+ /**
+ * @see Sitres::getAll
+ *
+ * @since 1.21
+ *
+ * @return array of string
+ */
+ public function getAllPaths() {
+ return $this->getExtraData( 'paths', array() );
+ }
+
+ /**
+ * @see Sitres::removePath
+ *
+ * @since 1.21
+ *
+ * @param string $pathType
+ */
+ public function removePath( $pathType ) {
+ $paths = $this->getExtraData( 'paths', array() );
+ unset( $paths[$pathType] );
+ $this->setExtraData( 'paths', $paths );
+ }
+
+}
--- /dev/null
+<?php
+
+/**
+ * Represents the site configuration of a wiki.
+ * Holds a list of sites (ie SiteList) and takes care
+ * of retrieving and caching site information when appropriate.
+ *
+ * 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.21
+ *
+ * @file
+ * @ingroup Site
+ *
+ * @licence GNU GPL v2+
+ * @author Jeroen De Dauw < jeroendedauw@gmail.com >
+ */
+class Sites {
+
+ /**
+ * @since 1.21
+ * @var SiteList|false
+ */
+ protected $sites = false;
+
+ /**
+ * Constructor.
+ *
+ * @since 1.21
+ */
+ protected function __construct() {}
+
+ /**
+ * Returns an instance of Sites.
+ *
+ * @since 1.21
+ *
+ * @return Sites
+ */
+ public static function singleton() {
+ static $instance = false;
+
+ if ( $instance === false ) {
+ $instance = new static();
+ }
+
+ return $instance;
+ }
+
+ /**
+ * Factory for creating new site objects.
+ *
+ * @since 1.21
+ *
+ * @param string|false $globalId
+ *
+ * @return Site
+ */
+ public static function newSite( $globalId = false ) {
+ /**
+ * @var Site $site
+ */
+ $site = SitesTable::singleton()->newRow( array(), true );
+
+ if ( $globalId !== false ) {
+ $site->setGlobalId( $globalId );
+ }
+
+ return $site;
+ }
+
+ /**
+ * Returns a list of all sites. By default this site is
+ * fetched from the cache, which can be changed to loading
+ * the list from the database using the $useCache parameter.
+ *
+ * @since 1.21
+ *
+ * @param string $source either 'cache' or 'recache'
+ *
+ * @return SiteList
+ */
+ public function getSites( $source = 'cache' ) {
+ if ( $source === 'cache' ) {
+ if ( $this->sites === false ) {
+ $cache = wfGetMainCache();
+ $sites = $cache->get( 'sites-cache' );
+
+ if ( is_object( $sites ) ) {
+ $this->sites = $sites;
+ }
+ else {
+ $this->loadSites();
+ }
+ }
+ }
+ else {
+ $this->loadSites();
+ }
+
+ return $this->sites;
+ }
+
+ /**
+ * Returns a list of sites in the given group. Calling getGroup() on any of
+ * the sites in the resulting SiteList shall return $group.
+ *
+ * @since 1.21
+ *
+ * @param string $group th group to get.
+ *
+ * @return SiteList
+ */
+ public function getSiteGroup( $group ) {
+ $sites = self::getSites();
+
+ $siteGroup = new SiteArray();
+
+ /* @var Site $site */
+ foreach ( $sites as $site ) {
+ if ( $site->getGroup() == $group ) {
+ $siteGroup->append( $site );
+ }
+ }
+
+ return $siteGroup;
+ }
+
+ /**
+ * Fetches the site from the database and loads them into the sites field.
+ *
+ * @since 1.21
+ */
+ protected function loadSites() {
+ $this->sites = new SiteArray( SitesTable::singleton()->select() );
+
+ // Batch load the local site identifiers.
+ $dbr = wfGetDB( SitesTable::singleton()->getReadDb() );
+
+ $ids = $dbr->select(
+ 'site_identifiers',
+ array(
+ 'si_site',
+ 'si_type',
+ 'si_key',
+ ),
+ array(),
+ __METHOD__
+ );
+
+ foreach ( $ids as $id ) {
+ if ( $this->sites->hasInternalId( $id->si_site ) ) {
+ $site = $this->sites->getSiteByInternalId( $id->si_site );
+ $site->addLocalId( $id->si_type, $id->si_key );
+ $this->sites->setSite( $site );
+ }
+ }
+
+ $cache = wfGetMainCache();
+ $cache->set( 'sites-cache', $this->sites );
+ }
+
+ /**
+ * Returns the site with provided global id, or false if there is no such site.
+ *
+ * @since 1.21
+ *
+ * @param string $globalId
+ * @param string $source
+ *
+ * @return Site|false
+ */
+ public function getSite( $globalId, $source = 'cache' ) {
+ if ( $source === 'cache' && $this->sites !== false ) {
+ return $this->sites->hasSite( $globalId ) ? $this->sites->getSite( $globalId ) : false;
+ }
+
+ return SitesTable::singleton()->selectRow( null, array( 'global_key' => $globalId ) );
+ }
+
+}
--- /dev/null
+<?php
+
+/**
+ * Represents the sites database table.
+ * All access to this table should be done through this class.
+ *
+ * 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.21
+ *
+ * @file
+ * @ingroup Site
+ *
+ * @licence GNU GPL v2+
+ * @author Jeroen De Dauw < jeroendedauw@gmail.com >
+ */
+class SitesTable extends ORMTable {
+
+ /**
+ * @see IORMTable::getName()
+ * @since 1.21
+ * @return string
+ */
+ public function getName() {
+ return 'sites';
+ }
+
+ /**
+ * @see IORMTable::getFieldPrefix()
+ * @since 1.21
+ * @return string
+ */
+ public function getFieldPrefix() {
+ return 'site_';
+ }
+
+ /**
+ * @see IORMTable::getRowClass()
+ * @since 1.21
+ * @return string
+ */
+ public function getRowClass() {
+ return 'SiteObject';
+ }
+
+ /**
+ * @see IORMTable::getFields()
+ * @since 1.21
+ * @return array
+ */
+ public function getFields() {
+ return array(
+ 'id' => 'id',
+
+ // Site data
+ 'global_key' => 'str',
+ 'type' => 'str',
+ 'group' => 'str',
+ 'source' => 'str',
+ 'language' => 'str',
+ 'protocol' => 'str',
+ 'domain' => 'str',
+ 'data' => 'array',
+
+ // Site config
+ 'forward' => 'bool',
+ 'config' => 'array',
+ );
+ }
+
+ /**
+ * @see IORMTable::getDefaults()
+ * @since 1.21
+ * @return array
+ */
+ public function getDefaults() {
+ return array(
+ 'type' => Site::TYPE_UNKNOWN,
+ 'group' => Site::GROUP_NONE,
+ 'source' => Site::SOURCE_LOCAL,
+ 'data' => array(),
+
+ 'forward' => false,
+ 'config' => array(),
+ );
+ }
+
+ /**
+ * Returns the class name for the provided site type.
+ *
+ * @since 1.21
+ *
+ * @param integer $siteType
+ *
+ * @return string
+ */
+ protected static function getClassForType( $siteType ) {
+ global $wgSiteTypes;
+ return array_key_exists( $siteType, $wgSiteTypes ) ? $wgSiteTypes[$siteType] : 'SiteObject';
+ }
+
+ /**
+ * Factory method to construct a new Site instance.
+ *
+ * @since 1.21
+ *
+ * @param array $data
+ * @param boolean $loadDefaults
+ *
+ * @return Site
+ */
+ public function newRow( array $data, $loadDefaults = false ) {
+ if ( !array_key_exists( 'type', $data ) ) {
+ $data['type'] = Site::TYPE_UNKNOWN;
+ }
+
+ $class = static::getClassForType( $data['type'] );
+
+ return new $class( $this, $data, $loadDefaults );
+ }
+
+}
\ No newline at end of file
$name = str_replace( ' ', '_', $user->getName() );
$lb->add( NS_USER, $name );
$lb->add( NS_USER_TALK, $name );
- }
+ }
$lb->execute();
wfProfileOut( __METHOD__ );
$title = Title::makeTitleSafe( NS_PROJECT, $page ); # Show list in content language
if( is_object( $title ) && $title->exists() ) {
$rev = Revision::newFromTitle( $title, false, Revision::READ_NORMAL );
- $this->getOutput()->addWikiText( str_replace( 'MAGICNUMBER', $this->isbn, $rev->getText() ) );
- return true;
+ $content = $rev->getContent();
+
+ if ( $content instanceof TextContent ) {
+ //XXX: in the future, this could be stored as structured data, defining a list of book sources
+
+ $text = $content->getNativeData();
+ $this->getOutput()->addWikiText( str_replace( 'MAGICNUMBER', $this->isbn, $text ) );
+ return true;
+ } else {
+ throw new MWException( "Unexpected content type for book sources: " . $content->getModel() );
+ }
}
# Fall back to the defaults given in the language file
$rev2 = self::revOrTitle( $data['Revision2'], $data['Page2'] );
if( $rev1 && $rev2 ) {
- $de = new DifferenceEngine( $form->getContext(),
- $rev1,
- $rev2,
- null, // rcid
- ( $data['Action'] == 'purge' ),
- ( $data['Unhide'] == '1' )
- );
- $de->showDiffPage( true );
+ $revision = Revision::newFromId( $rev1 );
+
+ if ( $revision ) { // NOTE: $rev1 was already checked, should exist.
+ $contentHandler = $revision->getContentHandler();
+ $de = $contentHandler->createDifferenceEngine( $form->getContext(),
+ $rev1,
+ $rev2,
+ null, // rcid
+ ( $data['Action'] == 'purge' ),
+ ( $data['Unhide'] == '1' )
+ );
+ $de->showDiffPage( true );
+ }
}
}
)
) ;
- $extraOptions = Xml::tags( 'td', array( 'colspan' => 2 ),
- Html::rawElement( 'span', array( 'style' => 'white-space: nowrap' ),
+ if ( $this->getUser()->isAllowed( 'deletedhistory' ) ) {
+ $deletedOnlyCheck = Html::rawElement( 'span', array( 'style' => 'white-space: nowrap' ),
Xml::checkLabel(
$this->msg( 'history-show-deleted' )->text(),
'deletedOnly',
$this->opts['deletedOnly'],
array( 'class' => 'mw-input' )
)
- ) .
+ );
+ } else {
+ $deletedOnlyCheck = '';
+ }
+
+ $extraOptions = Xml::tags( 'td', array( 'colspan' => 2 ),
+ $deletedOnlyCheck .
Html::rawElement( 'span', array( 'style' => 'white-space: nowrap' ),
Xml::checkLabel(
$this->msg( 'sp-contributions-toponly' )->text(),
} else {
$pageSet = array(); // Inverted index of all pages to look up
-
+
// Split up and normalize input
foreach( explode( "\n", $page ) as $pageName ) {
$pageName = trim( $pageName );
* be thrown.
* @param $html String: The raw HTML.
* @param $state String: State, one of 'noframework', 'unknownframework' or 'frameworkfound'
+ * @throws MWException
* @return string
*/
private function wrapSummaryHtml( $html, $state ) {
$destTitle->getPrefixedText()
)->inContentLanguage()->text();
}
- $mwRedir = MagicWord::get( 'redirect' );
- $redirectText = $mwRedir->getSynonym( 0 ) . ' [[' . $destTitle->getPrefixedText() . "]]\n";
- $redirectPage = WikiPage::factory( $targetTitle );
- $redirectRevision = new Revision( array(
- 'page' => $this->mTargetID,
- 'comment' => $comment,
- 'text' => $redirectText ) );
- $redirectRevision->insertOn( $dbw );
- $redirectPage->updateRevisionOn( $dbw, $redirectRevision );
-
- # Now, we record the link from the redirect to the new title.
- # It should have no other outgoing links...
- $dbw->delete( 'pagelinks', array( 'pl_from' => $this->mDestID ), __METHOD__ );
- $dbw->insert( 'pagelinks',
- array(
- 'pl_from' => $this->mDestID,
- 'pl_namespace' => $destTitle->getNamespace(),
- 'pl_title' => $destTitle->getDBkey() ),
- __METHOD__
- );
+
+ $contentHandler = ContentHandler::getForTitle( $targetTitle );
+ $redirectContent = $contentHandler->makeRedirectContent( $destTitle );
+
+ if ( $redirectContent ) {
+ $redirectPage = WikiPage::factory( $targetTitle );
+ $redirectRevision = new Revision( array(
+ 'page' => $this->mTargetID,
+ 'comment' => $comment,
+ 'content' => $redirectContent ) );
+ $redirectRevision->insertOn( $dbw );
+ $redirectPage->updateRevisionOn( $dbw, $redirectRevision );
+
+ # Now, we record the link from the redirect to the new title.
+ # It should have no other outgoing links...
+ $dbw->delete( 'pagelinks', array( 'pl_from' => $this->mDestID ), __METHOD__ );
+ $dbw->insert( 'pagelinks',
+ array(
+ 'pl_from' => $this->mDestID,
+ 'pl_namespace' => $destTitle->getNamespace(),
+ 'pl_title' => $destTitle->getDBkey() ),
+ __METHOD__
+ );
+ } else {
+ // would be nice to show a warning if we couldn't create a redirect
+ }
} else {
$targetTitle->invalidateCache(); // update histories
}
}
protected function filterLinks() {
- global $wgGroupPermissions;
-
// show/hide links
$showhide = array( $this->msg( 'show' )->escaped(), $this->msg( 'hide' )->escaped() );
}
// Disable some if needed
- # @todo FIXME: Throws E_NOTICEs if not set; and doesn't obey hooks etc.
- if ( $wgGroupPermissions['*']['createpage'] !== true ) {
+ if ( !User::groupHasPermission( '*', 'createpage' ) ) {
unset( $filters['hideliu'] );
}
if ( !$this->getUser()->useNPPatrol() ) {
protected function feedItemDesc( $row ) {
$revision = Revision::newFromId( $row->rev_id );
if( $revision ) {
+ //XXX: include content model/type in feed item?
return '<p>' . htmlspecialchars( $revision->getUserText() ) .
$this->msg( 'colon-separator' )->inContentLanguage()->escaped() .
htmlspecialchars( FeedItem::stripComment( $revision->getComment() ) ) .
"</p>\n<hr />\n<div>" .
- nl2br( htmlspecialchars( $revision->getText() ) ) . "</div>";
+ nl2br( htmlspecialchars( $revision->getContent()->serialize() ) ) . "</div>";
}
return '';
}
}
function getQueryInfo() {
- global $wgEnableNewpagesUserFilter, $wgGroupPermissions;
+ global $wgEnableNewpagesUserFilter;
$conds = array();
$conds['rc_new'] = 1;
$conds['rc_user_text'] = $user->getText();
$rcIndexes = 'rc_user_text';
# If anons cannot make new pages, don't "exclude logged in users"!
- } elseif( $wgGroupPermissions['*']['createpage'] && $this->opts->getValue( 'hideliu' ) ) {
+ } elseif( User::groupHasPermission( '*', 'createpage' ) && $this->opts->getValue( 'hideliu' ) ) {
$conds['rc_user'] = 0;
}
# If this user cannot see patrolled edits or they are off, don't do dumb queries!
* userCanExecute(), and if the data array contains 'Username', etc, then Username
* resets are allowed.
* @param $data array
+ * @throws MWException
+ * @throws ThrottledError|PermissionsError
* @return Bool|Array
*/
public function onSubmit( array $data ) {
/**
* UI entry point for form submission.
+ * @throws PermissionsError
* @return bool
*/
protected function submit() {
$out = $this->getOutput();
if( strval( $term ) !== '' ) {
$out->setPageTitle( $this->msg( 'searchresults' ) );
- $out->setHTMLTitle( $this->msg( 'pagetitle', $this->msg( 'searchresults-title', $term )->plain() ) );
+ $out->setHTMLTitle( $this->msg( 'pagetitle' )->rawParams(
+ $this->msg( 'searchresults-title' )->rawParams( $term )->text()
+ ) );
}
// add javascript specific to special:search
$out->addModules( 'mediawiki.special.search' );
*
* @param $data Array
* @param $context IContextSource
+ * @throws ErrorPageError
* @return Array( Array(message key, parameters) ) on failure, True on success
*/
public static function processUnblock( array $data, IContextSource $context ){
* @var Title
*/
protected $title;
- var $fileStatus;
+
+ /**
+ * @var Status
+ */
+ protected $fileStatus;
+
+ /**
+ * @var Status
+ */
+ protected $revisionStatus;
function __construct( $title ) {
if( is_null( $title ) ) {
* @return ResultWrapper
*/
function listRevisions() {
+ global $wgContentHandlerUseDB;
+
$dbr = wfGetDB( DB_SLAVE );
+
+ $fields = array(
+ 'ar_minor_edit', 'ar_timestamp', 'ar_user', 'ar_user_text',
+ 'ar_comment', 'ar_len', 'ar_deleted', 'ar_rev_id', 'ar_sha1',
+ );
+
+ if ( $wgContentHandlerUseDB ) {
+ $fields[] = 'ar_content_format';
+ $fields[] = 'ar_content_model';
+ }
+
$res = $dbr->select( 'archive',
- array(
- 'ar_minor_edit', 'ar_timestamp', 'ar_user', 'ar_user_text',
- 'ar_comment', 'ar_len', 'ar_deleted', 'ar_rev_id', 'ar_sha1'
- ),
+ $fields,
array( 'ar_namespace' => $this->title->getNamespace(),
'ar_title' => $this->title->getDBkey() ),
__METHOD__,
'fa_user',
'fa_user_text',
'fa_timestamp',
- 'fa_deleted' ),
+ 'fa_deleted',
+ 'fa_sha1' ),
array( 'fa_name' => $this->title->getDBkey() ),
__METHOD__,
array( 'ORDER BY' => 'fa_timestamp DESC' ) );
* @return Revision
*/
function getRevision( $timestamp ) {
+ global $wgContentHandlerUseDB;
+
$dbr = wfGetDB( DB_SLAVE );
+
+ $fields = array(
+ 'ar_rev_id',
+ 'ar_text',
+ 'ar_comment',
+ 'ar_user',
+ 'ar_user_text',
+ 'ar_timestamp',
+ 'ar_minor_edit',
+ 'ar_flags',
+ 'ar_text_id',
+ 'ar_deleted',
+ 'ar_len',
+ 'ar_sha1',
+ );
+
+ if ( $wgContentHandlerUseDB ) {
+ $fields[] = 'ar_content_format';
+ $fields[] = 'ar_content_model';
+ }
+
$row = $dbr->selectRow( 'archive',
- array(
- 'ar_rev_id',
- 'ar_text',
- 'ar_comment',
- 'ar_user',
- 'ar_user_text',
- 'ar_timestamp',
- 'ar_minor_edit',
- 'ar_flags',
- 'ar_text_id',
- 'ar_deleted',
- 'ar_len',
- 'ar_sha1',
- ),
+ $fields,
array( 'ar_namespace' => $this->title->getNamespace(),
'ar_title' => $this->title->getDBkey(),
'ar_timestamp' => $dbr->timestamp( $timestamp ) ),
__METHOD__ );
if( $row ) {
- return Revision::newFromArchiveRow( $row, array( 'page' => $this->title->getArticleID() ) );
+ return Revision::newFromArchiveRow( $row, array( 'title' => $this->title ) );
} else {
return null;
}
if( $restoreFiles && $this->title->getNamespace() == NS_FILE ) {
$img = wfLocalFile( $this->title );
$this->fileStatus = $img->restore( $fileVersions, $unsuppress );
- if ( !$this->fileStatus->isOk() ) {
+ if ( !$this->fileStatus->isOK() ) {
return false;
}
$filesRestored = $this->fileStatus->successCount;
}
if( $restoreText ) {
- $textRestored = $this->undeleteRevisions( $timestamps, $unsuppress, $comment );
- if( $textRestored === false ) { // It must be one of UNDELETE_*
+ $this->revisionStatus = $this->undeleteRevisions( $timestamps, $unsuppress, $comment );
+ if( !$this->revisionStatus->isOK() ) {
return false;
}
+
+ $textRestored = $this->revisionStatus->getValue();
} else {
$textRestored = 0;
}
* @param $comment String
* @param $unsuppress Boolean: remove all ar_deleted/fa_deleted restrictions of seletected revs
*
- * @return Mixed: number of revisions restored or false on failure
+ * @return Status, containing the number of revisions restored on success
*/
private function undeleteRevisions( $timestamps, $unsuppress = false, $comment = '' ) {
+ global $wgContentHandlerUseDB;
+
if ( wfReadOnly() ) {
- return false;
+ throw new ReadOnlyError();
}
$restoreAll = empty( $timestamps );
$previousTimestamp = $dbw->selectField( 'revision', 'rev_timestamp',
array( 'rev_id' => $previousRevId ),
__METHOD__ );
+
if( $previousTimestamp === false ) {
wfDebug( __METHOD__.": existing page refers to a page_latest that does not exist\n" );
- return 0;
+
+ $status = Status::newGood( 0 );
+ $status->warning( 'undeleterevision-missing' );
+
+ return $status;
}
} else {
# Have to create a new article...
$oldones = "ar_timestamp IN ( {$oldts} )";
}
+ $fields = array(
+ 'ar_rev_id',
+ 'ar_text',
+ 'ar_comment',
+ 'ar_user',
+ 'ar_user_text',
+ 'ar_timestamp',
+ 'ar_minor_edit',
+ 'ar_flags',
+ 'ar_text_id',
+ 'ar_deleted',
+ 'ar_page_id',
+ 'ar_len',
+ 'ar_sha1');
+
+ if ( $wgContentHandlerUseDB ) {
+ $fields[] = 'ar_content_format';
+ $fields[] = 'ar_content_model';
+ }
+
/**
* Select each archived revision...
*/
$result = $dbw->select( 'archive',
- /* fields */ array(
- 'ar_rev_id',
- 'ar_text',
- 'ar_comment',
- 'ar_user',
- 'ar_user_text',
- 'ar_timestamp',
- 'ar_minor_edit',
- 'ar_flags',
- 'ar_text_id',
- 'ar_deleted',
- 'ar_page_id',
- 'ar_len',
- 'ar_sha1' ),
+ $fields,
/* WHERE */ array(
'ar_namespace' => $this->title->getNamespace(),
'ar_title' => $this->title->getDBkey(),
$rev_count = $dbw->numRows( $result );
if( !$rev_count ) {
wfDebug( __METHOD__ . ": no revisions to restore\n" );
- return false; // ???
+
+ $status = Status::newGood( 0 );
+ $status->warning( "undelete-no-results" );
+ return $status;
}
$ret->seek( $rev_count - 1 ); // move to last
$row = $ret->fetchObject(); // get newest archived rev
$ret->seek( 0 ); // move back
+ // grab the content to check consistency with global state before restoring the page.
+ $revision = Revision::newFromArchiveRow( $row,
+ array(
+ 'title' => $article->getTitle(), // used to derive default content model
+ ) );
+
+ $m = $revision->getContentModel();
+
+ $user = User::newFromName( $revision->getRawUserText(), false );
+ $content = $revision->getContent( Revision::RAW );
+
+ //NOTE: article ID may not be known yet. prepareSave() should not modify the database.
+ $status = $content->prepareSave( $article, 0, -1, $user );
+
+ if ( !$status->isOK() ) {
+ return $status;
+ }
+
if( $makepage ) {
// Check the state of the newest to-be version...
if( !$unsuppress && ( $row->ar_deleted & Revision::DELETED_TEXT ) ) {
- return false; // we can't leave the current revision like this!
+ return Status::newFatal( "undeleterevdel" );
}
// Safe to insert now...
$newid = $article->insertOn( $dbw );
if( $row->ar_timestamp > $previousTimestamp ) {
// Check the state of the newest to-be version...
if( !$unsuppress && ( $row->ar_deleted & Revision::DELETED_TEXT ) ) {
- return false; // we can't leave the current revision like this!
+ return Status::newFatal( "undeleterevdel" );
}
}
}
// unless we are specifically removing all restrictions...
$revision = Revision::newFromArchiveRow( $row,
array(
- 'page' => $pageId,
+ 'title' => $this->title,
'deleted' => $unsuppress ? 0 : $row->ar_deleted
) );
// Was anything restored at all?
if ( $restored == 0 ) {
- return 0;
+ return Status::newGood( 0 );
}
$created = (bool)$newid;
$update->doUpdate();
}
- return $restored;
+ return Status::newGood( $restored );
}
/**
* @return Status
*/
function getFileStatus() { return $this->fileStatus; }
+
+ /**
+ * @return Status
+ */
+ function getRevisionStatus() { return $this->revisionStatus; }
}
/**
private function showRevision( $timestamp ) {
if( !preg_match( '/[0-9]{14}/', $timestamp ) ) {
- return 0;
+ return;
}
$archive = new PageArchive( $this->mTargetObj );
- wfRunHooks( 'UndeleteForm::showRevision', array( &$archive, $this->mTargetObj ) );
+ if ( !wfRunHooks( 'UndeleteForm::showRevision', array( &$archive, $this->mTargetObj ) ) ) {
+ return;
+ }
$rev = $archive->getRevision( $timestamp );
$out = $this->getOutput();
$t = $lang->userTime( $timestamp, $user );
$userLink = Linker::revUserTools( $rev );
- if( $this->mPreview ) {
+ $content = $rev->getContent( Revision::FOR_THIS_USER, $user );
+
+ $isText = ( $content instanceof TextContent );
+
+ if( $this->mPreview || $isText ) {
$openDiv = '<div id="mw-undelete-revision" class="mw-warning">';
} else {
$openDiv = '<div id="mw-undelete-revision">';
$out->addHTML( $this->msg( 'undelete-revision' )->rawParams( $link )->params(
$time )->rawParams( $userLink )->params( $d, $t )->parse() . '</div>' );
- wfRunHooks( 'UndeleteShowRevision', array( $this->mTargetObj, $rev ) );
- if( $this->mPreview ) {
+ if ( !wfRunHooks( 'UndeleteShowRevision', array( $this->mTargetObj, $rev ) ) ) {
+ return;
+ }
+
+ if( $this->mPreview || !$isText ) {
+ // NOTE: non-text content has no source view, so always use rendered preview
+
// Hide [edit]s
$popts = $out->parserOptions();
$popts->setEditSection( false );
- $out->parserOptions( $popts );
- $out->addWikiTextTitleTidy( $rev->getText( Revision::FOR_THIS_USER, $user ), $this->mTargetObj, true );
+
+ $pout = $content->getParserOutput( $this->mTargetObj, $rev->getId(), $popts, true );
+ $out->addParserOutput( $pout );
}
+ if ( $isText ) {
+ // source view for textual content
+ $sourceView = Xml::element( 'textarea', array(
+ 'readonly' => 'readonly',
+ 'cols' => intval( $user->getOption( 'cols' ) ),
+ 'rows' => intval( $user->getOption( 'rows' ) ) ),
+ $content->getNativeData() . "\n" );
+
+ $previewButton = Xml::element( 'input', array(
+ 'type' => 'submit',
+ 'name' => 'preview',
+ 'value' => $this->msg( 'showpreview' )->text() ) );
+ } else {
+ $sourceView = '';
+ $previewButton = '';
+ }
+
+ $diffButton = Xml::element( 'input', array(
+ 'name' => 'diff',
+ 'type' => 'submit',
+ 'value' => $this->msg( 'showdiff' )->text() ) );
+
$out->addHTML(
- Xml::element( 'textarea', array(
- 'readonly' => 'readonly',
- 'cols' => intval( $user->getOption( 'cols' ) ),
- 'rows' => intval( $user->getOption( 'rows' ) ) ),
- $rev->getText( Revision::FOR_THIS_USER, $user ) . "\n" ) .
- Xml::openElement( 'div' ) .
+ $sourceView .
+ Xml::openElement( 'div', array(
+ 'style' => 'clear: both' ) ) .
Xml::openElement( 'form', array(
'method' => 'post',
'action' => $this->getTitle()->getLocalURL( array( 'action' => 'submit' ) ) ) ) .
'type' => 'hidden',
'name' => 'wpEditToken',
'value' => $user->getEditToken() ) ) .
- Xml::element( 'input', array(
- 'type' => 'submit',
- 'name' => 'preview',
- 'value' => $this->msg( 'showpreview' )->text() ) ) .
- Xml::element( 'input', array(
- 'name' => 'diff',
- 'type' => 'submit',
- 'value' => $this->msg( 'showdiff' )->text() ) ) .
+ $previewButton .
+ $diffButton .
Xml::closeElement( 'form' ) .
Xml::closeElement( 'div' ) );
}
* @return String: HTML
*/
function showDiff( $previousRev, $currentRev ) {
- $diffEngine = new DifferenceEngine( $this->getContext() );
+ $diffContext = clone $this->getContext();
+ $diffContext->setTitle( $currentRev->getTitle() );
+ $diffContext->setWikiPage( WikiPage::factory( $currentRev->getTitle() ) );
+
+ $diffEngine = $currentRev->getContentHandler()->createDifferenceEngine( $diffContext );
$diffEngine->showDiffStyle();
$this->getOutput()->addHTML(
"<div>" .
$this->diffHeader( $currentRev, 'n' ) .
"</td>\n" .
"</tr>" .
- $diffEngine->generateDiffBody(
- $previousRev->getText( Revision::FOR_THIS_USER, $this->getUser() ),
- $currentRev->getText( Revision::FOR_THIS_USER, $this->getUser() ) ) .
+ $diffEngine->generateContentDiffBody(
+ $previousRev->getContent( Revision::FOR_THIS_USER, $this->getUser() ),
+ $currentRev->getContent( Revision::FOR_THIS_USER, $this->getUser() ) ) .
"</table>" .
"</div>\n"
);
private function formatRevisionRow( $row, $earliestLiveTime, $remaining ) {
$rev = Revision::newFromArchiveRow( $row,
- array( 'page' => $this->mTargetObj->getArticleID() ) );
+ array(
+ 'title' => $this->mTargetObj
+ ) );
+
$revTextSize = '';
$ts = wfTimestamp( TS_MW, $row->ar_timestamp );
// Build checkboxen...
$out->addHTML( $this->msg( 'undeletedpage' )->rawParams( $link )->parse() );
} else {
$out->setPageTitle( $this->msg( 'undelete-error' ) );
- $out->addWikiMsg( 'cannotundelete' );
- $out->addWikiMsg( 'undeleterevdel' );
}
- // Show file deletion warnings and errors
+ // Show revision undeletion warnings and errors
+ $status = $archive->getRevisionStatus();
+ if( $status && !$status->isGood() ) {
+ $out->addWikiText( '<div class="error">' . $status->getWikiText( 'cannotundelete', 'cannotundelete' ) . '</div>' );
+ }
+
+ // Show file undeletion warnings and errors
$status = $archive->getFileStatus();
if( $status && !$status->isGood() ) {
$out->addWikiText( '<div class="error">' . $status->getWikiText( 'undelete-error-short', 'undelete-error-long' ) . '</div>' );
* Provides output to the user for a result of UploadBase::verifyUpload
*
* @param $details Array: result of UploadBase::verifyUpload
+ * @throws MWException
*/
protected function processVerificationError( $details ) {
global $wgFileExtensions;
$exists['normalizedFile']->getTitle()->getPrefixedText() )->parse();
} elseif ( $exists['warning'] == 'thumb' ) {
// Swapped argument order compared with other messages for backwards compatibility
- $warning = wfMessage( 'fileexists-thumbnail-yes',
+ $warning = wfMessage( 'fileexists-thumbnail-yes',
$exists['thumbFile']->getTitle()->getPrefixedText(), $filename )->parse();
} elseif ( $exists['warning'] == 'thumb-name' ) {
// Image w/o '180px-' does not exists, but we do not like these filenames
* @return string
*/
function getLabelHtml( $cellAttributes = array() ) {
- $id = "wpSourceType{$this->mParams['upload-type']}";
+ $id = $this->mParams['id'];
$label = Html::rawElement( 'label', array( 'for' => $id ), $this->mLabel );
if ( !empty( $this->mParams['radio'] ) ) {
* n.b. Most sanity checking done in UploadStashLocalFile, so this is straightforward.
*
* @param $key String: the key of a particular requested file
+ * @throws HttpError
* @return bool
*/
public function showUpload( $key ) {
* application the transform parameters
*
* @param string $key
+ * @throws UploadStashBadPathException
* @return array
*/
private function parseKey( $key ) {
/**
* Scale a file (probably with a locally installed imagemagick, or similar) and output it to STDOUT.
- * @param $file: File object
- * @param $params: scaling parameters ( e.g. array( width => '50' ) );
- * @param $flags: scaling flags ( see File:: constants )
+ * @param $file File
+ * @param $params array Scaling parameters ( e.g. array( width => '50' ) );
+ * @param $flags int Scaling flags ( see File:: constants )
* @throws MWException
+ * @throws UploadStashFileNotFoundException
* @return boolean success
*/
private function outputLocallyScaledThumb( $file, $params, $flags ) {
// now we should construct a File, so we can get mime and other such info in a standard way
// n.b. mimetype may be different from original (ogx original -> jpeg thumb)
- $thumbFile = new UnregisteredLocalFile( false,
+ $thumbFile = new UnregisteredLocalFile( false,
$this->stash->repo, $thumbnailImage->getStoragePath(), false );
if ( !$thumbFile ) {
throw new UploadStashFileNotFoundException( "couldn't create local file object for thumbnail" );
* Side effect: writes HTTP response to STDOUT.
*
* @param $file File object with a local path (e.g. UnregisteredLocalFile, LocalFile. Oddly these don't share an ancestor!)
+ * @throws SpecialUploadStashTooLargeException
* @return bool
*/
private function outputLocalFile( File $file ) {
* Side effect: writes HTTP response to STDOUT.
* @param $content String content
* @param $contentType String mime type
+ * @throws SpecialUploadStashTooLargeException
* @return bool
*/
private function outputContents( $content, $contentType ) {
/**
* @private
+ * @throws PermissionsError|ReadOnlyError
* @return bool|User
*/
function addNewAccountInternal() {
}
self::clearCreateaccountToken();
- return $this->initUser( $u, false );
+
+ $status = $this->initUser( $u, false );
+ if ( !$status->isOK() ) {
+ $this->mainLoginForm( $status->getHTML() );
+ return false;
+ }
+ return $status->value;
}
/**
*
* @param $u User object.
* @param $autocreate boolean -- true if this is an autocreation via auth plugin
- * @return User object.
+ * @return Status object, with the User object in the value member on success
* @private
*/
function initUser( $u, $autocreate ) {
global $wgAuth;
- $u->addToDatabase();
+ $status = $u->addToDatabase();
+ if ( !$status->isOK() ) {
+ return $status;
+ }
if ( $wgAuth->allowPasswordChange() ) {
$u->setPassword( $this->mPassword );
# Update user count
DeferredUpdates::addUpdate( new SiteStatsUpdate( 0, 0, 0, 0, 1 ) );
- return $u;
+ return Status::newGood( $u );
}
/**
}
wfDebug( __METHOD__ . ": creating account\n" );
- $this->initUser( $user, true );
+ $status = $this->initUser( $user, true );
+
+ if ( !$status->isOK() ) {
+ $errors = $status->getErrorsByType( 'error' );
+ $this->mAbortLoginErrorMsg = $errors[0]['message'];
+ return self::ABORTED;
+ }
+
return self::SUCCESS;
}
if ( $wgSecureLogin && !$this->mStickHTTPS ) {
$options = array( 'http' );
$proto = PROTO_HTTP;
- } else {
+ } elseif( $wgSecureLogin ) {
$options = array( 'https' );
$proto = PROTO_HTTPS;
+ } else {
+ $options = array();
+ $proto = PROTO_RELATIVE;
}
if ( $type == 'successredirect' ) {
* Depending on the submit button used, call a form or a save function.
*
* @param $par Mixed: string if any subpage provided, else null
+ * @throws UserBlockedError|PermissionsError
*/
public function execute( $par ) {
// If the visitor doesn't have permissions to assign or remove
$catMessage = $this->msg( 'broken-file-category' )
->title( Title::newFromText( "Wanted Files", NS_MAIN ) )
->inContentLanguage();
-
+
if ( !$catMessage->isDisabled() ) {
$category = Title::makeTitleSafe( NS_CATEGORY, $catMessage->text() );
} else {
* @ingroup SpecialPage
*/
class WantedPagesPage extends WantedQueryPage {
-
+
function __construct( $name = 'Wantedpages' ) {
parent::__construct( $name );
}
$this->mFileKey = $this->mLocalFile->getFileKey();
// Output a copy of this first to chunk 0 location:
- $status = $this->outputChunk( $this->mLocalFile->getPath() );
+ $this->outputChunk( $this->mLocalFile->getPath() );
// Update db table to reflect initial "chunk" state
$this->updateChunkStatus();
* @return array
*/
public function verifyUpload() {
- # Check for a post_max_size or upload_max_size overflow, so that a
+ # Check for a post_max_size or upload_max_size overflow, so that a
# proper error can be shown to the user
if ( is_null( $this->mTempPath ) || $this->isEmptyFile() ) {
if ( $this->mUpload->isIniSizeOverflow() ) {
- return array(
+ return array(
'status' => UploadBase::FILE_TOO_LARGE,
- 'max' => min(
- self::getMaxUploadSize( $this->getSourceType() ),
- wfShorthandToInteger( ini_get( 'upload_max_filesize' ) ),
+ 'max' => min(
+ self::getMaxUploadSize( $this->getSourceType() ),
+ wfShorthandToInteger( ini_get( 'upload_max_filesize' ) ),
wfShorthandToInteger( ini_get( 'post_max_size' ) )
),
);
}
}
-
+
return parent::verifyUpload();
}
}
/**
* Remove a particular file from the stash. Also removes it from the repo.
*
- * @throws UploadStashNotLoggedInException
- * @throws UploadStashWrongOwnerException
+ * @param $key
+ * @throws UploadStashNoSuchKeyException|UploadStashNotLoggedInException|UploadStashWrongOwnerException
* @return boolean: success
*/
public function removeFile( $key ) {
* with an extension.
* XXX this is somewhat redundant with the checks that ApiUpload.php does with incoming
* uploads versus the desired filename. Maybe we can get that passed to us...
+ * @param $path
+ * @throws UploadStashFileException
* @return string
*/
public static function getExtensionForPath( $path ) {
function convert( $t ) { return $t; }
function convertTo( $text, $variant ) { return $text; }
function convertTitle( $t ) { return $t->getPrefixedText(); }
+ function convertNamespace( $ns ) { return $this->mLang->getFormattedNsText( $ns ); }
function getVariants() { return array( $this->mLang->getCode() ); }
function getPreferredVariant() { return $this->mLang->getCode(); }
function getDefaultVariant() { return $this->mLang->getCode(); }
*/
public function setNamespaces( array $namespaces ) {
$this->namespaceNames = $namespaces;
+ $this->mNamespaceIds = null;
+ }
+
+ /**
+ * Resets all of the namespace caches. Mainly used for testing
+ */
+ public function resetNamespaces( ) {
+ $this->namespaceNames = null;
+ $this->mNamespaceIds = null;
+ $this->namespaceAliases = null;
}
/**
return $this->mConverter->convertTitle( $title );
}
+ /**
+ * Convert a namespace index to a string in the preferred variant
+ *
+ * @param $ns int
+ * @return string
+ */
+ public function convertNamespace( $ns ) {
+ return $this->mConverter->convertNamespace( $ns );
+ }
+
/**
* Check if this is a language with variants
*
/**
* Get default variant.
- * This function would not be affected by user's settings or headers
+ * This function would not be affected by user's settings
* @return String: the default variant code
*/
public function getDefaultVariant() {
$req = $this->getURLVariant();
+ if ( !$req ) {
+ $req = $this->getHeaderVariant();
+ }
+
if ( $wgDefaultLanguageVariant && !$req ) {
$req = $this->validateVariant( $wgDefaultLanguageVariant );
}
$attr = $attrs[$attrName];
// Don't convert URLs
if ( !strpos( $attr, '://' ) ) {
- $attr = $this->translate( $attr, $toVariant );
+ $attr = $this->convertTo( $attr, $toVariant );
}
// Remove HTML tags to avoid disrupting the layout
public function convertTitle( $title ) {
$variant = $this->getPreferredVariant();
$index = $title->getNamespace();
- if ( $index === NS_MAIN ) {
+ if ( $index !== NS_MAIN ) {
+ $text = $this->convertNamespace( $index ) . ':';
+ } else {
$text = '';
+ }
+ $text .= $this->translate( $title->getText(), $variant );
+ return $text;
+ }
+
+ /**
+ * Get the namespace display name in the preferred variant.
+ *
+ * @param $index int namespace id
+ * @return String: namespace name for display
+ */
+ public function convertNamespace( $index ) {
+ $variant = $this->getPreferredVariant();
+ if ( $index === NS_MAIN ) {
+ return '';
} else {
- // first let's check if a message has given us a converted name
+ // First check if a message gives a converted name in the target variant.
+ $nsConvMsg = wfMessage( 'conversion-ns' . $index )->inLanguage( $variant );
+ if ( $nsConvMsg->exists() ) {
+ return $nsConvMsg->plain();
+ }
+ // Then check if a message gives a converted name in content language
+ // which needs extra translation to the target variant.
$nsConvMsg = wfMessage( 'conversion-ns' . $index )->inContentLanguage();
if ( $nsConvMsg->exists() ) {
- $text = $nsConvMsg->plain();
- } else {
- // the message does not exist, try retrieve it from the current
- // variant's namespace names.
- $langObj = $this->mLangObj->factory( $variant );
- $text = $langObj->getFormattedNsText( $index );
+ return $this->translate( $nsConvMsg->plain(), $variant );
}
- $text .= ':';
+ // No message exists, retrieve it from the target variant's namespace names.
+ $langObj = $this->mLangObj->factory( $variant );
+ return $langObj->getFormattedNsText( $index );
}
- $text .= $title->getText();
- $text = $this->translate( $text, $variant );
- return $text;
}
/**
if ( $title && $title->exists() ) {
$revision = Revision::newFromTitle( $title );
if ( $revision ) {
- $txt = $revision->getRawText();
+ if ( $revision->getContentModel() == CONTENT_MODEL_WIKITEXT ) {
+ $txt = $revision->getContent( Revision::RAW )->getNativeData();
+ }
+
+ //@todo: in the future, use a specialized content model, perhaps based on json!
}
}
}
* MediaWiki:Conversiontable* is updated.
* @private
*
- * @param $article Article object
+ * @param $page WikiPage object
* @param $user Object: User object for the current user
- * @param $text String: article text (?)
+ * @param $content Content: new page content
* @param $summary String: edit summary of the edit
* @param $isMinor Boolean: was the edit marked as minor?
* @param $isWatch Boolean: did the user watch this page or not?
* @param $revision Object: new Revision object or null
* @return Boolean: true
*/
- function OnArticleSaveComplete( $article, $user, $text, $summary, $isMinor,
+ function OnPageContentSaveComplete( $page, $user, $content, $summary, $isMinor,
$isWatch, $section, $flags, $revision ) {
- $titleobj = $article->getTitle();
+ $titleobj = $page->getTitle();
if ( $titleobj->getNamespace() == NS_MEDIAWIKI ) {
$title = $titleobj->getDBkey();
$t = explode( '/', $title, 3 );
'monday' => 'maanantai',
'tuesday' => 'tiistai',
'wednesday' => 'keskiviikko',
- 'thursay' => 'torstai',
+ 'thursday' => 'torstai',
'friday' => 'perjantai',
'saturday' => 'lauantai',
'sunday' => 'sunnuntai',
array(),
$ml );
- $wgHooks['ArticleSaveComplete'][] = $this->mConverter;
+ $wgHooks['PageContentSaveComplete'][] = $this->mConverter;
}
/**
$flags = array();
$this->mConverter = new IuConverter( $this, 'iu', $variants, $variantfallbacks, $flags );
- $wgHooks['ArticleSaveComplete'][] = $this->mConverter;
+ $wgHooks['PageContentSaveComplete'][] = $this->mConverter;
}
}
$this->mConverter = new KkConverter( $this, 'kk', $variants, $variantfallbacks );
- $wgHooks['ArticleSaveComplete'][] = $this->mConverter;
+ $wgHooks['PageContentSaveComplete'][] = $this->mConverter;
}
/**
);
$this->mConverter = new KuConverter( $this, 'ku', $variants, $variantfallbacks );
- $wgHooks['ArticleSaveComplete'][] = $this->mConverter;
+ $wgHooks['PageContentSaveComplete'][] = $this->mConverter;
}
}
* @return string
*/
function getMessage( $key ) {
- return "($key)";
+ return "($key$*)";
}
}
$flags = array();
$this->mConverter = new ShiConverter( $this, 'shi', $variants, $variantfallbacks, $flags );
- $wgHooks['ArticleSaveComplete'][] = $this->mConverter;
+ $wgHooks['PageContentSaveComplete'][] = $this->mConverter;
}
}
'W' => 'W', 'реч' => 'W', 'reč' => 'W', 'ријеч' => 'W', 'riječ' => 'W'
);
$this->mConverter = new SrConverter( $this, 'sr', $variants, $variantfallbacks, $flags );
- $wgHooks['ArticleSaveComplete'][] = $this->mConverter;
+ $wgHooks['PageContentSaveComplete'][] = $this->mConverter;
}
/**
);
$this->mConverter = new UzConverter( $this, 'uz', $variants, $variantfallbacks );
- $wgHooks['ArticleSaveComplete'][] = $this->mConverter;
+ $wgHooks['PageContentSaveComplete'][] = $this->mConverter;
}
}
array(),
$ml );
- $wgHooks['ArticleSaveComplete'][] = $this->mConverter;
+ $wgHooks['PageContentSaveComplete'][] = $this->mConverter;
}
/**
'qbbrowse' => 'Snuffel',
'qbedit' => 'Wysig',
'qbpageoptions' => 'Bladsyopsies',
-'qbpageinfo' => 'Bladsyinligting',
'qbmyoptions' => 'My bladsye',
'qbspecialpages' => 'Spesiale bladsye',
'faq' => 'Gewilde vrae',
'qbbrowse' => 'ቃኝ',
'qbedit' => 'አርም',
'qbpageoptions' => 'ይህ ገጽ',
-'qbpageinfo' => 'አግባብ',
'qbmyoptions' => 'የኔ ገጾች',
'qbspecialpages' => 'ልዩ ገጾች',
'faq' => 'ብጊየጥ (ብዙ ጊዜ የሚጠየቁ ጥያቀዎች)',
'qbbrowse' => 'Navegar',
'qbedit' => 'Editar',
'qbpageoptions' => 'Ista pachina',
-'qbpageinfo' => 'Contexto',
'qbmyoptions' => 'Pachinas propias',
'qbspecialpages' => 'Pachinas especials',
'faq' => 'Preguntas freqüents (FAQ)',
'qbbrowse' => 'تصفح',
'qbedit' => 'تعديل',
'qbpageoptions' => 'هذه الصفحة',
-'qbpageinfo' => 'سياق النص',
'qbmyoptions' => 'صفحاتي',
'qbspecialpages' => 'الصفحات الخاصة',
'faq' => 'الأسئلة المتكررة',
'category-subcat-count-limited' => 'ܣܕܪܐ ܗܢܐ ܐܝܬ ܒܗ {{PLURAL:$1|ܣܕܪܐ ܦܪܥܝܐ ܗܢܐ|$1 ܣܕܪ̈ܐ ܦܪ̈ܥܝܐ ܗܠܝܢ}}.',
'category-article-count' => '{{PLURAL:$2|ܣܕܪܐ ܗܢܐ ܐܝܬ ܒܗ ܦܐܬܐ ܗܕܐ ܒܠܚܘܕ.|ܐܝܬ {{PLURAL:$1|ܦܐܬܐ|$1 ܦܐܬܬ̈ܐ}} ܒܣܕܪܐ ܗܢܐ، ܡܢ ܣܘܝܟܐ ܕ$2.}}',
'category-article-count-limited' => '{{PLURAL:$1|ܦܐܬܐ ܗܕܐ|$1 ܦܐܬܬ̈ܐ ܗܠܝܢ}} ܒܣܕܪܐ ܗܢܐ.',
-'category-file-count' => '{{PLURAL:$2|ܣܕܪܐ ܗܢܐ ܐܝܬ ܒܗ ܠܦܦܐ ܗܢܐ ܒܠܚܘܕ.|{{PLURAL:$1|ܠܦܦܐ ܕܐܬܐ ܐܝܬܘܗܝ|$1 ܠܦܦ̈ܐ ܕܐܬܝܢ ܐܝܬܝܗܘܢ}} ܒܣܕܪܐ ܗܢܐ، ܡܢ ܣܘܝܟܐ ܕ$2.}}',
+'category-file-count' => '{{PLURAL:$2|ܣܕܪܐ ܗܢܐ ܐܝܬ ܒܗ ܠܦܦܐ ܗܢܐ ܒܠܚܘܕ.|{{PLURAL:$1|ܠܦܦܐ ܕܐܬܐ ܐܝܬܘܗܝ|$1 ܠܦܦ̈ܐ ܕܐܬܝܢ ܐܝܬܝܗܘܢ}} ܒܣܕܪܐ ܗܢܐ، ܡܢ ܣܘܝܟܐ ܕ$2 ܟܠܢܐܝܬ.}}',
'category-file-count-limited' => 'ܐܝܬ {{PLURAL:$1|ܠܦܦܐ ܕܐܬܐ|$1 ܠܦܦ̈ܐ ܕܐܬܝܢ}} ܒܣܕܪܐ ܗܫܝܐ.',
'listingcontinuesabbrev' => '(ܫܘܠܡܐ)',
'rows' => 'ܨ̈ܦܐ',
'columns' => 'ܥܡܘܕ̈ܐ:',
'searchresultshead' => 'ܒܨܝ',
-'resultsperpage' => 'Ü¡Ü¢Ü\9dÜ¢Ü\90 Ü\95Ü¦Ü Ü\9bÌ\88Ü\90 Ü\92Ü\95ܦܐ:',
+'resultsperpage' => 'Ü¡Ü¢Ü\9dÜ¢Ü\90 Ü\95Ü¦Ü Ü\9bÌ\88Ü\90 Ü\92ܦÜ\90ܬܐ:',
'recentchangesdays' => 'ܝܘܡܬ̈ܐ ܠܚܙܝܐ ܒܫܘܚܠܦ̈ܐ ܚܕ̈ܬܐ:',
'recentchangescount' => 'ܡܢܝܢܐ ܕܫܘܚܠܦ̈ܐ ܠܚܙܝܐ ܪܫܐܝܬ:',
'savedprefs' => 'ܨܒܝܢܝܘܬ̈ܟ ܐܬܠܒܟܘ.',
'action-createaccount' => 'ܒܪܝܬܐ ܕܚܘܫܒܢܐ ܕܗܢܐ ܡܦܠܚܢܐ',
'action-minoredit' => 'ܫܘܕܥܬܐ ܥܠ ܫܘܚܠܦܐ ܗܢܐ ܐܝܟ ܙܥܘܪܐ',
'action-move' => 'ܫܢܝܬܐ ܕܦܐܬܐ ܗܕܐ',
-'action-move-rootuserpages' => 'Ü«Ü¢Ü\9dܬÜ\90 Ü\95Ü\95Ì\88ܦÜ\90 ܫܪÌ\88Ü«Ü\9dܐ ܕܡܦܠܚܢܐ',
+'action-move-rootuserpages' => 'Ü«Ü¢Ü\9dܬÜ\90 Ü\95ܦÜ\90ܬܬÌ\88Ü\90 ܫܪÌ\88Ü«Ü\9dܬܐ ܕܡܦܠܚܢܐ',
'action-movefile' => 'ܫܢܝܬܐ ܕܗܢܐ ܠܦܦܐ',
'action-upload' => 'ܐܣܩܬܐ ܕܗܢܐ ܠܦܦܐ',
'action-delete' => 'ܫܝܦܬܐ ܕܦܐܬܐ ܗܕܐ',
'minlength1' => 'ܫܡܗ̈ܐ ܕܠܦܦܐ ܘܠܐ ܕܢܗܘܐ ܒܪܝܐ ܡܢ ܐܬܘܬܐ ܚܕܐ ܟܕ ܙܥܘܪ',
'uploadwarning' => 'ܐܣܩ ܙܘܗܪܐ',
'savefile' => 'ܠܒܘܟ ܠܦܦܐ',
-'uploadedimage' => '',
+'uploadedimage' => 'ܐܣܩ "[[$1]]"',
'uploadvirus' => 'ܠܦܦܐ ܐܝܬ ܒܗ ܒܝܪܘܣ!
ܐܪ̈ܝܟܬܐ: $1',
'upload-source' => 'ܡܒܘܥܐ ܕܠܦܦܐ',
ܗܫܐ ܐܝܬܝܗܝ ܨܘܝܒܐ ܠ [[$2]].',
'brokenredirects' => 'ܨܘܝܒ̈ܐ ܬܒܝܪ̈ܐ',
-'brokenredirectstext' => 'ܨÜ\98Ì\88Ü\9dÜ\92Ü\90 Ü\97Ü Ü\9dÜ¢ Ü¡Ü\9bÜ\9dÜ¢ Ü Ü\95Ì\88ܦÜ\90 Ü\95Ü Ü\9dÜ¬Ü Ü\97Ü\98ܢ ܐܝܬܘܬܐ:',
+'brokenredirectstext' => 'ܨÜ\98Ì\88Ü\9dÜ\92Ü\90 Ü\97Ü Ü\9dÜ¢ Ü¡Ü\9bÜ\9dÜ¢ Ü Ü¦Ü\90ܬܬÌ\88Ü\90 Ü\95Ü Ü\9dÜ¬Ü Ü\97Ü\9dܢ ܐܝܬܘܬܐ:',
'brokenredirects-edit' => 'ܫܚܠܦ',
'brokenredirects-delete' => 'ܫܘܦ',
'tooltip-pt-mytalk' => 'ܦܐܬܐ ܕܡܡܠܘܟ',
'tooltip-pt-preferences' => 'Your preferences',
'tooltip-pt-watchlist' => 'ܡܟܬܒܢܘܬܐ ܕܦܐܬܬ̈ܐ ܕܒܪܗܝܬ ܐܢܬ ܫܘܚܠܦ̈ܐ ܕܬܗܘܐ ܒܗܘܢ',
-'tooltip-pt-mycontris' => 'ܡܟܬܒܢܘܬܐ ܕܫܘܬܦܘܝܬܘ̈ܟ',
+'tooltip-pt-mycontris' => 'ܡܟܬܒܢܘܬܐ ܕܫܘܬܦܘܝܬ̈ܟ',
'tooltip-pt-login' => 'ܢܠܒܒ ܠܟ ܕܣܓܠ ܐܢܬ ܥܠܠܐ ܕܝܠܟ، ܐܠܐ ܗܢܐ ܠܐ ܐܝܬܝܗܝ ܐܠܨܝܐ',
'tooltip-pt-logout' => 'ܦܠܛܐ',
'tooltip-ca-talk' => 'ܡܡܠܠܐ ܥܠ ܚܒܝܫܬܐ ܕܦܐܬܐ',
'tooltip-search' => 'ܒܨܝ ܒܓܘ {{SITENAME}}',
'tooltip-search-fulltext' => 'ܒܨܝ ܒܓܘ ܦܐܬܬ̈ܐ ܥܠ ܗܢܐ ܟܬܝܒܬܐ',
'tooltip-p-logo' => 'ܦܐܬܐ ܪܝܫܝܬܐ',
-'tooltip-n-mainpage' => 'ܬܪÜ\98ܩܬÜ\90 Ü\95ܦܐܬܐ ܪܝܫܝܬܐ',
+'tooltip-n-mainpage' => 'ܣܥܪ ܦܐܬܐ ܪܝܫܝܬܐ',
'tooltip-n-mainpage-description' => 'ܬܪܘܩܬܐ ܕܦܐܬܐ ܪܝܫܝܬܐ',
'tooltip-n-portal' => 'ܚܕܪ ܬܪܡܝܬܐ، ܡܢܐ ܡܫܟܚ ܐܢܬ ܠܥܒܕܐ، ܐܝܟܐ ܬܚܙܝ ܟܠ ܡܐ ܕܣܢܝܩ ܐܢܬ ܠܗ',
'tooltip-n-recentchanges' => 'ܡܟܬܒܢܘܬܐ ܒܫܘܚܠܦ̈ܐ ܚܕ̈ܬܐ ܒܓܘ ܘܝܩܝ.',
'tooltip-ca-nstab-main' => 'ܚܘܝ ܦܐܬܬܐ ܕܚܒ̈ܝܫܬܐ',
'tooltip-ca-nstab-user' => 'ܚܘܝ ܦܐܬܐ ܕܡܦܠܚܢܐ',
'tooltip-ca-nstab-image' => 'ܚܘܝ ܦܐܬܐ ܕܠܦܦܐ',
-'tooltip-ca-nstab-category' => 'ܚܘܝ ܦܐܬܐ ܕܣܕܪ̈ܐ',
+'tooltip-ca-nstab-category' => 'ܚܘܝ ܦܐܬܐ ܕܣܕܪܐ',
'tooltip-save' => 'ܠܒܘܟ ܫܘܚܠܦܟ',
-'tooltip-watch' => 'Ü\90Ü\98ܣܦ Ü\97Ü¢Ü\90 ܦÜ\90ܬÜ\90 Ü Ü¡Ü\9fܬÜ\92Ü¢Ü\98ܬÜ\90 Ü\95ܪÜ\97Ü\9dܬÜ\98ܟ',
+'tooltip-watch' => 'Ü\90Ü\98ܣܦ ܦÜ\90ܬÜ\90 Ü\97Ü\95Ü\90 Ü Ü¡Ü\9fܬÜ\92Ü¢Ü\98ܬÜ\90 Ü\95ܪÌ\88Ü\97Ü\9dܬܟ',
# Attribution
'anonymous' => '{{PLURAL:$1|ܡܦܠܚܢܐ ܠܐ ܝܕܝܥܐ|ܡܦܠܚܢ̈ܐ ܠܐ ܝܕ̈ܝܥܐ}} ܕ {{SITENAME}}',
'qbbrowse' => 'تصفح',
'qbedit' => 'عدل',
'qbpageoptions' => ' الصفحه دى',
-'qbpageinfo' => 'السياق',
'qbmyoptions' => 'صفحاتى',
'qbspecialpages' => 'الصفحات الخاصة',
'faq' => 'اسئله بتتسئل كتير',
'qbbrowse' => 'ব্ৰাওজ',
'qbedit' => 'সম্পাদনা',
'qbpageoptions' => 'এই পৃষ্ঠা',
-'qbpageinfo' => 'প্ৰসংগ',
'qbmyoptions' => 'মোৰ পৃষ্ঠাসমূহ',
'qbspecialpages' => 'বিশেষ পৃষ্ঠাসমূহ',
'faq' => 'সততে উদিত প্ৰশ্নসমূহ (FAQ)',
'qbbrowse' => 'Restolar',
'qbedit' => 'Editar',
'qbpageoptions' => 'Esta páxina',
-'qbpageinfo' => 'Contestu',
'qbmyoptions' => 'Les mios páxines',
'qbspecialpages' => 'Páxines especiales',
'faq' => 'EMF (entrugues más frecuentes)',
'vector-action-protect' => 'Protexer',
'vector-action-undelete' => 'Restaurar',
'vector-action-unprotect' => 'Camudar la proteición',
-'vector-simplesearch-preference' => 'Activar suxerencies meyoraes de gueta (namái apariencia Vector)',
+'vector-simplesearch-preference' => 'Activar la barra de gueta simplificada (namái apariencia Vector)',
'vector-view-create' => 'Crear',
'vector-view-edit' => 'Editar',
'vector-view-history' => 'Ver historial',
'protectedpagetext' => 'Esta páxina ta candada pa torgar la so edición.',
'viewsourcetext' => "Pues ver y copiar la fonte d'esta páxina:",
'viewyourtext' => "Pues ver y copiar la fonte de '''les tos ediciones''' d'esta páxina:",
-'protectedinterface' => "Esta páxina proporciona testu d'interfaz de l'aplicación, y ta candada pa torgar abusos.",
-'editinginterface' => "'''Avisu:''' Tas editando una páxina que s'usa pa proporcionar el testu d'interfaz de l'aplicación.
-Los cambeos nesta páxina van afeutar l'apariencia de la interfaz pa otros usuarios.
-Si quies facer traducciones, por favor usa [//translatewiki.net/wiki/Main_Page?setlang=ast translatewiki.net], el proyeutu de traducción de MediaWiki.",
+'protectedinterface' => "Esta páxina proporciona'l testu de la interfaz del software d'esta wiki, y ta candada pa torgar abusos.
+P'amestar o cambiar les traducciones de toles wikis, por favor usa [//translatewiki.net/translatewiki.net], el proyeutu de llocalización de MediaWiki.",
+'editinginterface' => "'''Avisu:''' Tas editando una páxina que s'usa pa proporcionar el testu d'interfaz del programa.
+Los cambeos nesta páxina van afeutar l'apariencia de la interfaz pa otros usuarios d'esta wiki.
+P'amestar o camudar traducciones pa toles wikis, por favor, usa [//translatewiki.net/ translatewiki.net], el proyeutu de traducción de MediaWiki.",
'sqlhidden' => '(consulta SQL anubrida)',
'cascadeprotected' => "Esta páxina ta protexida d'ediciones porque ta enxerta {{PLURAL:$1|na siguiente páxina|nes siguientes páxines}}, que {{PLURAL:$1|ta protexida|tán protexíes}} cola opción «en cascada» activada:
$2",
<span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} guetar los rexistros rellacionaos],
o [{{fullurl:{{FULLPAGENAME}}|action=edit}} editar esta páxina equí]</span>.',
'noarticletext-nopermission' => 'Nestos momentos nun hai testu nesta páxina.
-Pues [[Special:Search/{{PAGENAME}}|guetar esti títulu de páxina]] n\'otres páxines,
-o <span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} guetar los rexistros rellacionaos]</span>.',
+Pues [[Special:Search/{{PAGENAME}}|guetar esti títulu de páxina]] n\'otres páxines o <span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} guetar los rexistros rellacionaos]</span>, pero nun tienes permisu pa crear esta páxina.',
'missing-revision' => 'La revisión #$1 de la páxina llamada "{{PAGENAME}}" nun esiste.
De vezu la causa d\'esto ye siguir un enllaz antiguu del historial a una páxina que se desanició.
'edit-already-exists' => 'Nun se pudo crear una páxina nueva.
Yá esiste.',
'defaultmessagetext' => 'Testu predetermináu',
+'content-failed-to-parse' => 'Fallu al analizar el conteníu $2 pal modelu $1: $3',
+'invalid-content-data' => 'Datos del conteníu inválidos',
+'content-not-allowed-here' => 'El conteníu «$1» nun se permite na páxina [[$2]]',
+
+# Content models
+'content-model-wikitext' => 'testu wiki',
+'content-model-text' => 'testu simple',
+'content-model-javascript' => 'JavaScript',
+'content-model-css' => 'CSS',
# Parser/template warnings
'expensive-parserfunction-warning' => "'''Avisu:''' Esta páxina contién demasiaes llamaes costoses a funciones d'análisis sintáuticu.
'timezoneregion-indian' => 'Océanu Índicu',
'timezoneregion-pacific' => 'Océanu Pacíficu',
'allowemail' => 'Dexar a los otros usuarios mandate correos',
-'prefs-searchoptions' => 'Opciones de busca',
+'prefs-searchoptions' => 'Guetar',
'prefs-namespaces' => 'Espacios de nome',
'defaultns' => "D'otra miente, guetar nestos espacios de nome:",
'default' => 'predetermináu',
'backend-fail-internal' => 'Hebo un fallu desconocíu nel motor d\'almacenamientu "$1".',
'backend-fail-contenttype' => 'Non se pudo determinar la triba de conteníu de ficheru a guardar en "$1".',
'backend-fail-batchsize' => "El motor d'almacenamientu dio un llote de $1 {{PLURAL:$1|operación|operaciones}} en ficheros; el llímite ye de $2 {{PLURAL:$2|operación|operaciones}}.",
-'backend-fail-usable' => 'Nun se pudo escribir el ficheru $1 porque nun hai permisos bastantes o falten los direutorios/contenedores.',
+'backend-fail-usable' => 'Nun se pudo llee o escribir el ficheru «$1» porque nun hai permisos bastantes o falten los direutorios/contenedores.',
# File journal errors
'filejournal-fail-dbconnect' => 'Nun se pudo coneutar cola base de datos del diariu pal sofitu d\'almacenamientu "$1".',
'undeletedrevisions' => '{{PLURAL:$1|1 revisión restaurada|$1 revisiones restauraes}}',
'undeletedrevisions-files' => '{{PLURAL:$1|1 revisión|$1 revisiones}} y {{PLURAL:$2|1 archivu|$2 archivos}} restauraos',
'undeletedfiles' => '{{PLURAL:$1|1 archivu restauráu|$1 archivos restauraos}}',
-'cannotundelete' => 'Falló la restauración; seique daquién yá restaurara la páxina enantes.',
+'cannotundelete' => 'Falló la restauración:
+$1',
'undeletedpage' => "'''Restauróse $1'''
Consulta'l [[Special:Log/delete|rexistru d'esborraos]] pa ver los esborraos y restauraciones de recién.",
'immobile-target-namespace-iw' => "Nun puedes mover una páxina a un enllaz d'Interwiki.",
'immobile-source-page' => 'Esta páxina nun ye treslladable.',
'immobile-target-page' => 'Nun se pue treslladar a esi títulu de destín.',
+'bad-target-model' => 'El destín deseáu utiliza un modelu de conteníu diferente. Nun se pue convertir de $1 a $2.',
'imagenocrossnamespace' => "Nun se pue treslladar una imaxe a nun espaciu de nomes que nun ye d'imáxenes",
'nonfile-cannot-move-to-file' => 'Nun se pue treslladar más que ficheros al espaciu de nomes de ficheros',
'imagetypemismatch' => 'La estensión nueva del archivu nun concueya cola so mena',
'pageinfo-magic-words' => '{{PLURAL:$1|Pallabra máxica|Pallabres máxiques}} ($1)',
'pageinfo-hidden-categories' => '{{PLURAL:$1|Categoría anubrida|Categoríes anubríes}} ($1)',
'pageinfo-templates' => '{{PLURAL:$1|Plantía incluída|Plantíes incluíes}} ($1)',
+'pageinfo-toolboxlink' => 'Información de la páxina',
# Skin names
'skinname-standard' => 'Clásicu',
# Scary transclusion
'scarytranscludedisabled' => '[La tresclusión interwiki ta desactivada]',
-'scarytranscludefailed' => '[La obtención de la plantía falló pa $1]',
+'scarytranscludefailed' => '[Falló la recuperación de la plantía pa $1]',
+'scarytranscludefailed-httpstatus' => '[Falló la recuperación de la plantía pa $1: HTTP $2]',
'scarytranscludetoolong' => '[La URL ye demasiao llarga]',
# Delete conflict
'qbbrowse' => 'Farura',
'qbedit' => 'Betara',
'qbpageoptions' => 'Ikatcuksbu',
-'qbpageinfo' => 'Givabu',
'qbmyoptions' => 'Jinaf ikatcukseem',
'qbspecialpages' => 'Aptaf bueem',
'faq' => 'NEB',
* @author Cekli829
* @author Don Alessandro
* @author Emperyan
+ * @author Erdemaslancan
* @author Gulmammad
* @author Kaganer
* @author PPerviz
'qbbrowse' => 'Gözdən keçir',
'qbedit' => 'Redaktə',
'qbpageoptions' => 'Bu səhifə',
-'qbpageinfo' => 'Məzmun',
'qbmyoptions' => 'Mənim səhifələrim',
'qbspecialpages' => 'Xüsusi səhifələr',
'faq' => 'TSS',
'edit-already-exists' => 'Yeni səhifəni yaratmaq mümkün deyil.
Belə ki, bu adda səhifə artıq mövcuddur.',
+# Content models
+'content-model-javascript' => 'JavaScript',
+
# Parser/template warnings
'expensive-parserfunction-category' => 'Kifayət qədər böyük sayda genişresurslu funksiyaların müraciət olunduğu səhifələr',
'post-expand-template-inclusion-warning' => "'''DİQQƏT!''' Daxil edilən şablonların həcmi həddindən artıq böyükdür.
'qbbrowse' => 'Байҡарға',
'qbedit' => 'Үҙгәртергә',
'qbpageoptions' => 'Был бит',
-'qbpageinfo' => 'Бит тураһында мәғлүмәттәр',
'qbmyoptions' => 'Биттәрем',
'qbspecialpages' => 'Махсус биттәр',
'faq' => 'ЙБҺ',
'currentrev' => 'Ағымдағы версия',
'currentrev-asof' => '$1, ағымдағы версия',
'revisionasof' => '$1 версияһы',
-'revision-info' => 'Версия: $1; $2',
+'revision-info' => '<div id="viewingold-warning" style="background: #FFBDBD; border: 1px solid #BB7979; color: #000000; margin: 1em 0 .5em; padding: .5em 1em; vertical-align: middle; font-weight: bold; font-family: Palatino Linotype, Microsoft Sans Serif, Arial Unicode MS, Droid Sans; clear: both;">Хәҙер һеҙ был биттең иҫке, <span id="mw-revision-name">$2</span> тарафынан <span id="mw-revision-date">$1</span> һаҡланған версияһын ҡарайһығыҙ. Уның <span class="plainlinks">[{{fullurl:{{FULLPAGENAME}}}} ағымдағы версиянан] айырмаһы булыуы мөмкин</span>.</div>',
'previousrevision' => '← Алдағы',
'nextrevision' => 'Киләһе →',
'currentrevisionlink' => 'Ағымдағы версия',
'qbbrowse' => 'بروز',
'qbedit' => 'اصلاح',
'qbpageoptions' => 'صفحه',
-'qbpageinfo' => 'متن',
'qbmyoptions' => 'منی صفحات',
'qbspecialpages' => 'حاصین صفحات',
'faq' => 'ب.ج.س',
'qbbrowse' => 'Halungkáta',
'qbedit' => 'Liwata',
'qbpageoptions' => 'Ining pahina',
-'qbpageinfo' => 'Konteksto',
'qbmyoptions' => 'Sakong mga pahina',
'qbspecialpages' => 'Espesyal na mga pahina',
'faq' => 'PPK (Pirmihang Pighahapot na mga kahaputan)',
'qbbrowse' => 'Выбраць',
'qbedit' => 'Правіць',
'qbpageoptions' => 'Гэтая старонка',
-'qbpageinfo' => 'Кантэкст',
'qbmyoptions' => 'Свае старонкі',
'qbspecialpages' => 'Адмысловыя старонкі',
'faq' => 'ЧАПЫ',
'qbbrowse' => 'Праглядзець',
'qbedit' => 'Рэдагаваць',
'qbpageoptions' => 'Гэтая старонка',
-'qbpageinfo' => 'Інфармацыя пра старонку',
'qbmyoptions' => 'Мае старонкі',
'qbspecialpages' => 'Спэцыяльныя старонкі',
'faq' => 'Частыя пытаньні',
'qbbrowse' => 'Избор',
'qbedit' => 'Редактиране',
'qbpageoptions' => 'Тази страница',
-'qbpageinfo' => 'Информация за страницата',
'qbmyoptions' => 'Моите страници',
'qbspecialpages' => 'Специални страници',
'faq' => 'ЧЗВ',
'qbbrowse' => 'Tangadahi',
'qbedit' => 'Babak',
'qbpageoptions' => 'Tungkaran ini',
-'qbpageinfo' => 'Naskah aluran',
'qbmyoptions' => 'Tungkaran ulun',
'qbspecialpages' => 'Tungkaran istimiwa',
'faq' => 'FAQ',
'qbbrowse' => 'ব্রাউজ',
'qbedit' => 'সম্পাদনা',
'qbpageoptions' => 'এ পাতার বিকল্পসমূহ',
-'qbpageinfo' => 'পাতা-সংক্রান্ত তথ্য',
'qbmyoptions' => 'আমার পছন্দ',
'qbspecialpages' => 'বিশেষ পাতাসমূহ',
'faq' => 'সম্ভাব্য প্রশ্নসমূহ',
'vector-action-protect' => 'সুরক্ষা',
'vector-action-undelete' => 'পুনরুদ্ধার',
'vector-action-unprotect' => 'সুরক্ষা পরিবর্তন',
-'vector-simplesearch-preference' => 'à¦\86রà¦\93 সমà§\83দà§\8dধ à¦\85নà§\81সনà§\8dধান পরামরà§\8dশ সক্রিয় করুন (শুধুমাত্র ভেক্টর স্কিনের জন্য)',
+'vector-simplesearch-preference' => 'সরল à¦\85নà§\81সনà§\8dধান সক্রিয় করুন (শুধুমাত্র ভেক্টর স্কিনের জন্য)',
'vector-view-create' => 'তৈরি করুন',
'vector-view-edit' => 'সম্পাদনা',
'vector-view-history' => 'ইতিহাস',
'edit-already-exists' => 'নতুন পাতা সৃষ্টি করা যায়নি।
পাতাটি ইতিমধ্যেই বিদ্যমান।',
'defaultmessagetext' => 'আদি টেক্সট',
+'content-failed-to-parse' => '$1 মডেলের জন্য $2 কন্টেন্ট পার্স করা যাচ্ছে না: $3',
+'invalid-content-data' => 'ভুল কন্টেন্ট ডাটা',
+'content-not-allowed-here' => '"$1" কন্টেন্টটি [[$2]] পাতায় অনুমোদিত নয়',
+
+# Content models
+'content-model-wikitext' => 'উইকিটেক্সট',
+'content-model-text' => 'সাধারণ লেখা',
+'content-model-javascript' => 'জাভাস্ক্রিপ্ট',
+'content-model-css' => 'সিএসএস',
# Parser/template warnings
'expensive-parserfunction-warning' => "'''সতর্ক হোন:''' এই পাতাটি অনেক বেশি পরিমাণে এক্সপেনসিভ পার্সার ফাংশন কল রয়েছে।
'uploadnewversion-linktext' => 'এই ফাইলটির একটি নতুন সংস্করণ আপলোড করুন',
'shared-repo-from' => '$1 থেকে',
'shared-repo' => 'শেয়ার্ড রিপোজিটরী',
-'upload-disallowed-here' => 'দà§\81à¦\83à¦\96িত à¦\86পনি à¦\8fà¦\87 à¦\9bবিটি প্রতিস্থাপন করতে পারবেন না।',
+'upload-disallowed-here' => 'à¦\86পনি à¦\8fà¦\87 ফাà¦\87লটি প্রতিস্থাপন করতে পারবেন না।',
# File reversion
'filerevert' => '$1 পূর্বাবস্থায় ফেরত নিন',
'qbbrowse' => 'Furchal',
'qbedit' => 'Kemmañ',
'qbpageoptions' => 'Pajenn an dibaboù',
-'qbpageinfo' => 'Pajenn gelaouiñ',
'qbmyoptions' => 'Ma dibaboù',
'qbspecialpages' => 'Pajennoù dibar',
'faq' => 'FAG',
'tog-nocache' => 'Onemogući keširanje stranica u pregledniku',
'tog-enotifwatchlistpages' => 'Pošalji mi e-poštu kad se promijene stranice',
'tog-enotifusertalkpages' => 'Pošalji mi e-poštu kad se promijeni moja korisnička stranica za razgovor',
-'tog-enotifminoredits' => 'Pošalji mi e-poštu takođe za male izmjene stranica',
+'tog-enotifminoredits' => 'Pošalji mi e-poštu također za male izmjene u stranicama i datotekama',
'tog-enotifrevealaddr' => 'Otkrij adresu moje e-pošte u porukama obaviještenja',
'tog-shownumberswatching' => 'Prikaži broj korisnika koji prate',
'tog-oldsig' => 'Postojeći potpis:',
'qbbrowse' => 'Prelistajte',
'qbedit' => 'Uredi',
'qbpageoptions' => 'Opcije stranice',
-'qbpageinfo' => 'Informacije o stranici',
'qbmyoptions' => 'Moje opcije',
'qbspecialpages' => 'Posebne stranice',
'faq' => 'ČPP',
'youhavenewmessages' => 'Imate $1 ($2).',
'newmessageslink' => 'novih poruka',
'newmessagesdifflink' => 'posljednja promjena',
+'youhavenewmessagesfromusers' => 'Imate $1 od {{PLURAL:$3|drugog korisnika|$3 korisnika}} ($2).',
+'newmessageslinkplural' => '{{PLURAL:$1|novu poruku|nove poruke}}',
'youhavenewmessagesmulti' => 'Imate nove poruke na $1',
'editsection' => 'uredi',
'editsection-brackets' => '[$1]',
'protectedpagetext' => 'Ova stranica je zaključana da bi se spriječile izmjene.',
'viewsourcetext' => 'Možete vidjeti i kopirati izvorni tekst ove stranice:',
'viewyourtext' => "Možete da pogledate i kopirate izvor '''vaših izmjena''' na ovoj stranici:",
-'protectedinterface' => 'Ova stranica je zaštićena jer sadrži tekst MediaWiki programa.',
+'protectedinterface' => 'Ova stranica sadrži tekst korisničkog okruženja za softver na ovom wikiju i zaštićena je radi sprečavanja zloupotrebe.
+Da biste dodali ili izmjenili prijevode svih wikija, posjetite [//translatewiki.net/ translatewiki.net], projekat za lokalizaciju Mediawikija.',
'editinginterface' => "'''Upozorenje:''' Mijenjate stranicu koja sadrži aktivan tekst programa.
Promjene na ovoj stranici dovode i do promjena za druge korisnike.
Za prijevode, molimo Vas koristite [//translatewiki.net/wiki/Main_Page?setlang=bs translatewiki.net], projekt prijevoda za MediaWiki.",
'titleprotected' => 'Naslov stranice je zaštićen od postavljanja od strane korisnika [[User:$1|$1]].
Iz razloga "\'\'$2\'\'".',
'exception-nologin' => 'Niste prijavljeni',
+'exception-nologin-text' => 'Ova stranica ili aktivnost zahtijeva da budete prijavljeni na ovom wikiju.',
# Virus scanner
'virus-badscanner' => "Loša konfiguracija: nepoznati anti-virus program: ''$1''",
'remembermypassword' => 'Zapamti moju šifru na ovom računaru (najviše $1 {{PLURAL:$1|dan|dana|dana}})',
'securelogin-stick-https' => 'Ostani povezan na HTTPS nakon prijave',
'yourdomainname' => 'Vaš domen:',
+'password-change-forbidden' => 'Ne možete da promjenite lozinku na ovom wikiju.',
'externaldberror' => 'Došlo je do greške pri vanjskoj autorizaciji baze podataka ili vam nije dopušteno osvježavanje Vašeg vanjskog korisničkog računa.',
'login' => 'Prijavi se',
'nav-login-createaccount' => 'Prijavi se / Registruj se',
Možete [[Special:Search/{{PAGENAME}}|tražiti naslov ove stranice]] na drugim stranicama.
<span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} tražiti u povezanim zapisima] ili [{{fullurl:{{FULLPAGENAME}}|action=edit}} urediti ovu stranicu]</span>.',
'noarticletext-nopermission' => 'Trenutno nema teksta na ovoj stranici.
-Možete [[Special:Search/{{PAGENAME}}|tražiti ovaj naslov stranice]] na drugim stranicama ili <span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} pretražiti povezane zapisnike]</span>.',
+Možete [[Special:Search/{{PAGENAME}}|tražiti ovaj naslov stranice]] na drugim stranicama ili <span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} pretražiti povezane zapisnike]</span>, ali nemate dozvolu da napravite ovu stranicu.',
'userpage-userdoesnotexist' => 'Korisnički račun "<nowiki>$1</nowiki>" nije registrovan.
Molimo provjerite da li želite napraviti/izmijeniti ovu stranicu.',
'userpage-userdoesnotexist-view' => 'Korisnički račun "$1" nije registrovan.',
*'''Firefox / Safari:''' držite ''Shift'' tipku i kliknite na ''Reload'' dugme ili pritisnite ''Ctrl-F5'' ili ''Ctrl-R'' (''⌘-R'' na Macu)
*'''Google Chrome:''' pritisnite ''Ctrl-Shift-R'' (''⌘-Shift-R'' na Macu)
*'''Internet Explorer:''' držite tipku ''Ctrl'' i kliknite na ''Refresh'' ili pritisnite ''Ctrl-F5''
-*'''Konqueror:''' klikni na ''Reload'' ili pritisnite dugme ''F5''
*'''Opera:''' očistite \"keš\" preko izbornika ''Tools → Preferences''",
'usercssyoucanpreview' => "'''Pažnja:''' Koristite dugme \"{{int:showpreview}}\" da testirate svoj novi CSS prije nego što sačuvate.",
'userjsyoucanpreview' => "'''Pažnja:''' Koristite dugme \"{{int:showpreview}}\" da testirate svoj novi JavaScript prije nego što sačuvate.",
Izgleda da već postoji.',
'defaultmessagetext' => 'Uobičajeni tekst poruke',
+# Content models
+'content-model-javascript' => 'JavaScript',
+'content-model-css' => 'CSS',
+
# Parser/template warnings
'expensive-parserfunction-warning' => 'Upozorenje: Ova stranica sadrži previše poziva opterećujućih parserskih funkcija.
'timezoneregion-indian' => 'Indijski okean',
'timezoneregion-pacific' => 'Tihi okean',
'allowemail' => 'Dozvoli e-poštu od ostalih korisnika',
-'prefs-searchoptions' => 'Opcije pretrage',
+'prefs-searchoptions' => 'Traži',
'prefs-namespaces' => 'Imenski prostori',
'defaultns' => 'Inače tražite u ovim imenskim prostorima:',
'default' => 'standardno',
'allpages-bad-ns' => '{{SITENAME}} nema imenski prostor "$1".',
'allpages-hide-redirects' => 'Sakrij preusmjerenja',
+# SpecialCachedPage
+'cachedspecial-refresh-now' => 'Pogledaj najnoviju.',
+
# Special:Categories
'categories' => 'Kategorije',
'categoriespagetext' => '{{PLURAL:$1|Slijedeća kategorija sadrži|Slijedeće kategorije sadrže}} stranice ili multimedijalne datoteke.
i imati ispravnu adresu e-pošte u vašim [[Special:Preferences|podešavanjima]]
da biste slali e-poštu drugim korisnicima.',
'emailuser' => 'Pošalji e-poštu ovom korisniku',
+'emailuser-title-notarget' => 'Pošalji e-mail korisniku',
'emailpage' => 'Pošalji e-mail korisniku',
'emailpagetext' => 'Možete korisiti formu ispod za slanje e-mail poruka ovom korisniku.
E-mail adresa koju ste unijeli u [[Special:Preferences|Vašim korisničkim postavkama]] će biti prikazana kao adresa pošiljaoca, tako da će primaoc poruke moći da Vam odgovori.',
# Durations
'duration-seconds' => '$1 {{PLURAL:$1|sekunda|sekunde}}',
+'duration-minutes' => '$1 {{PLURAL:$1|minut|minuta|minuta}}',
+'duration-hours' => '$1 {{PLURAL:$1|sat|sata|sati}}',
'duration-days' => '$1 {{PLURAL:$1|dan|dana}}',
+'duration-weeks' => '$1 {{PLURAL:$1|sedmica|sedmice|sedmica}}',
+'duration-years' => '$1 {{PLURAL:$1|godina|godine|godina}}',
+'duration-decades' => '$1 {{PLURAL:$1|decenija|decenije|decenija}}',
+'duration-centuries' => '$1 {{PLURAL:$1|vijek|vijeka|vijekova}}',
);
'qbbrowse' => 'Navega',
'qbedit' => 'Modifica',
'qbpageoptions' => 'Opcions de pàgina',
-'qbpageinfo' => 'Informació de pàgina',
'qbmyoptions' => 'Pàgines pròpies',
'qbspecialpages' => 'Pàgines especials',
'faq' => 'PMF',
'vector-action-protect' => 'Protegeix',
'vector-action-undelete' => 'Restaura',
'vector-action-unprotect' => 'Desprotegeix',
-'vector-simplesearch-preference' => 'Habilitar suggeriments de recerca millorats (només aparença Vector)',
+'vector-simplesearch-preference' => 'Activar la barra de cerca simplificada (només aparença Vector)',
'vector-view-create' => 'Inicia',
'vector-view-edit' => 'Modifica',
'vector-view-history' => "Mostra l'historial",
# All link text and link target definitions of links into project namespace that get used by other message strings, with the exception of user group pages (see grouppage) and the disambiguation template definition (see disambiguations).
'aboutsite' => 'Quant al projecte {{SITENAME}}',
'aboutpage' => 'Project:Quant a',
-'copyright' => "El contingut és disponible sota els termes d'una llicència $1",
+'copyright' => 'El contingut està disponible sota els termes de la $1.',
'copyrightpage' => "{{ns:project}}:Drets d'autor",
'currentevents' => 'Actualitat',
'currentevents-url' => 'Project:Actualitat',
'protectedpagetext' => 'Aquesta pàgina està protegida per evitar modificacions.',
'viewsourcetext' => "Podeu visualitzar i copiar la font d'aquesta pàgina:",
'viewyourtext' => "Vostè pot veure i copiar la font de ' ' les modificacions ' ' d'aquesta pàgina:",
-'protectedinterface' => "Aquesta pàgina conté cadenes de text per a la interfície del programari, i és protegida per a previndre'n abusos.",
+'protectedinterface' => "Aquesta pàgina proporciona el text de la interfície del software d'aquest wiki i està protegida per evitar els abusos.
+Per agregar o canviar les traduccions per a tots els wikis, si us plau fes servir [//translatewiki.net/ translatewiki.net], el projecte de localització de MediaWiki.",
'editinginterface' => "'''Avís:''' Esteu editant una pàgina que conté cadenes de text per a la interfície d'aquest programari. Tingueu en compte que els canvis que es fan a aquesta pàgina afecten a l'aparença de la interfície d'altres usuaris. Pel que fa a les traduccions, plantegeu-vos utilitzar la [//translatewiki.net/wiki/Main_Page?setlang=ca translatewiki.net], el projecte de traducció de MediaWiki.",
'sqlhidden' => '(consulta SQL oculta)',
'cascadeprotected' => "Aquesta pàgina està protegida i no es pot modificar perquè està inclosa en {{PLURAL:$1|la següent pàgina, que té|les següents pàgines, que tenen}} activada l'opció de «protecció en cascada»:
'userpage-userdoesnotexist-view' => 'El compte d\'usuari "$1" no està registrat.',
'blocked-notice-logextract' => "En aquests moments aquest compte d'usuari es troba blocat.
Per més detalls, la darrera entrada del registre es mostra a continuació:",
-'clearyourcache' => "'''Nota:''' Després de desar, podeu haver d'ometre la memòria cau del vostre navegador per a veure'n els canvis.
-* '''Firefox / Safari:''' Premeu ''Maj'' mentre cliqueu a ''Actualitza'' (Reload), o premeu ''Ctrl+F5'' o ''Ctrl+R'' (''⌘+R'' en un Mac)
-* '''Google Chrome:''' Premeu ''Ctrl+Maj+R'' (''⌘+Maj+R'' en un Mac)
-* '''Internet Explorer:''' Premeu ''Ctrl'' mentre cliqueu a ''Actualitza'' (Refresh), o premeu ''Ctrl+F5''
-* '''Konqueror:''' Cliqueu al botó ''Recarrega'' (Reload), o premeu ''F5''
-* '''Opera:''' Netegeu la vostra memòria cau a ''Tools → Preferences''",
+'clearyourcache' => "'''Nota:''' després de guardar possiblement necessites refrescar el teu cache per poder veure els canvis.
+* '''Firefox / Safari:''' pressiona ''Shift'' mentre cliques el botó ''Actualitzar'', o pressiona ''Ctrl+F5'' o ''Ctrl+R'' (''⌘+R'' a Mac)
+* '''Google Chrome:''' pressiona ''Ctrl+Shift+R'' (''⌘+Shift+R'' a Mac)
+* '''Internet Explorer:''' pressiona la tecla ''Ctrl'' mentre cliques a ''Actualitzar'' o pressiona ''Ctrl+F5''
+* '''Opera:''' buida la memòria cau (cache) ''Eines → Preferències''",
'usercssyoucanpreview' => "'''Consell:''' Utilitzeu el botó \"{{int:showpreview}}\" per provar el vostre nou CSS abans de desar-lo.",
'userjsyoucanpreview' => "'''Consell:''' Utilitzeu el botó \"{{int:showpreview}}\" per provar el vostre nou JavaScript abans de desar-lo.",
'usercsspreview' => "'''Recordeu que esteu previsualitzant el vostre CSS d'usuari.'''
(prev) = diferència amb la versió anterior, m = modificació menor',
'history-fieldset-title' => "Cerca a l'historial",
'history-show-deleted' => 'Només esborrats',
-'histfirst' => 'El primer',
-'histlast' => 'El darrer',
+'histfirst' => 'Primeres',
+'histlast' => 'Últimes',
'historysize' => '({{PLURAL:$1|1 octet|$1 octets}})',
'historyempty' => '(buit)',
'revdelete-only-restricted' => 'Error amagant els ítems $2, $1: no pots suprimir elements a la vista dels administradors sense seleccionar alhora una de les altres opcions de supressió.',
'revdelete-reason-dropdown' => "*Raons d'esborrament comunes
** Violació del copyright
-** Informació personal inapropiada
+** Comentari o informació personal inapropiada
+** Nom d'usuari inapropiat
** Informació potencialment calumniosa",
'revdelete-otherreason' => 'Altre motiu / motiu suplementari:',
'revdelete-reasonotherlist' => 'Altres raons',
i tenir una direcció electrònica vàlida en les vostres [[Special:Preferences|preferències]]
per enviar un correu electrònic a altres usuaris.",
'emailuser' => 'Envia un missatge de correu electrònic a aquest usuari',
+'emailuser-title-target' => 'Enviar un correu electrònic a {{GENDER:$1|aquest usuari|aquesta usuària}}',
+'emailuser-title-notarget' => "Enviar un correu electrònic a l'usuari",
'emailpage' => 'Correu electrònic a usuari',
'emailpagetext' => "Podeu usar el següent formulari per a enviar un missatge de correu electrònic a aquest usuari.
L'adreça electrònica que heu entrat en [[Special:Preferences|les vostres preferències d'usuari]] apareixerà com a remitent del correu electrònic, de manera que el destinatari us podrà respondre directament.",
'undeletedrevisions' => '{{PLURAL:$1|Una revisió restaurada|$1 revisions restaurades}}',
'undeletedrevisions-files' => '{{PLURAL:$1|Una revisió|$1 revisions}} i {{PLURAL:$2|un fitxer|$2 fitxers}} restaurats',
'undeletedfiles' => '$1 {{PLURAL:$1|fitxer restaurat|fitxers restaurats}}',
-'cannotundelete' => "No s'ha pogut restaurar; algú altre pot estar restaurant la mateixa pàgina.",
+'cannotundelete' => 'Hi ha hagut un error en el procés de restauració:
+$1',
'undeletedpage' => "'''S'ha restaurat «$1»'''
Consulteu el [[Special:Log/delete|registre d'esborraments]] per a veure els esborraments i els restauraments més recents.",
'tooltip-ca-move' => 'Reanomena aquesta pàgina',
'tooltip-ca-watch' => 'Afegiu aquesta pàgina a la vostra llista de seguiment.',
'tooltip-ca-unwatch' => 'Suprimiu aquesta pàgina de la vostra llista de seguiment',
-'tooltip-search' => 'Cerca en el projecte {{SITENAME}}',
+'tooltip-search' => 'Cerca a {{SITENAME}}',
'tooltip-search-go' => 'Vés a una pàgina amb aquest nom exacte si existeix',
'tooltip-search-fulltext' => 'Cerca a les pàgines aquest text',
'tooltip-p-logo' => 'Pàgina principal',
'tooltip-watchlistedit-raw-submit' => 'Actualitza la llista de seguiment',
'tooltip-recreate' => 'Recrea la pàgina malgrat hagi estat suprimida',
'tooltip-upload' => 'Inicia la càrrega',
-'tooltip-rollback' => "«Rollback» reverteix les edicions del darrer contribuïdor d'aquesta pàgina en un clic.",
+'tooltip-rollback' => "«Revertir» reverteix totes les edicions de l'últim usuari en un clic.",
'tooltip-undo' => '«Desfés» reverteix aquesta modificació i obre un formulari de previsualització.
Permet afegir un motiu al resum.',
'tooltip-preferences-save' => 'Desa preferències',
# Info page
'pageinfo-title' => 'Informació de «$1»',
-'pageinfo-header-edits' => 'Modificacions',
+'pageinfo-header-basic' => 'Informació bàsica',
+'pageinfo-header-edits' => "Historial d'edicions",
+'pageinfo-header-restrictions' => 'Protecció de pàgina',
'pageinfo-views' => 'Número de visites',
-'pageinfo-watchers' => "Número d'usuaris que l'estan vigilant",
-'pageinfo-edits' => "Número d'edicions",
-'pageinfo-authors' => "Número d'autors diferents",
+'pageinfo-watchers' => "Número d'usuaris que vigilen la pàgina",
+'pageinfo-firstuser' => 'Creador de la pàgina',
+'pageinfo-firsttime' => 'Data de la creació de la pàgina',
+'pageinfo-lastuser' => 'Últim editor',
+'pageinfo-lasttime' => "Data de l'última edició",
+'pageinfo-edits' => "Número total d'edicions",
+'pageinfo-authors' => "Número total d'autors diferents",
# Skin names
'skinname-standard' => 'Clàssic',
'qbbrowse' => 'بگهڕێ',
'qbedit' => 'دەستکاری',
'qbpageoptions' => 'ئەم پەڕەیە',
-'qbpageinfo' => 'زانیاریی پهڕه',
'qbmyoptions' => 'پەڕەکانم',
'qbspecialpages' => 'پەڕە تایبەتەکان',
'faq' => 'پرسیار و وەڵام (FAQ)',
'import-logentry-interwiki-detail' => '$1 {{PLURAL:$1|پێداچوونەوە|پێداچوونەوە}} لە $2',
# Tooltip help for the actions
-'tooltip-pt-userpage' => 'پەڕە بەکارھێنەریی تۆ',
+'tooltip-pt-userpage' => 'پەڕەی بەکارھێنەرییەکەت',
'tooltip-pt-anonuserpage' => 'پەڕەی بەکارھێنەری بۆ ئایپی یەکە کە بەناویەوە خەریکی دەستکاری کردنی',
-'tooltip-pt-mytalk' => 'Ù¾Û\95Ú\95Û\95Û\8c Ù\88تÙ\88بÛ\8eÚ\98Û\8c تÛ\86',
+'tooltip-pt-mytalk' => 'Ù¾Û\95Ú\95Û\95Û\8c Ù\84Û\8eدÙ\88اÙ\86Û\95Ú©Û\95ت',
'tooltip-pt-anontalk' => 'لێدوان لەسەر دەستکارییەکان لەم ئایپی ئەدرەسەوە',
-'tooltip-pt-preferences' => 'بژاردەکانت',
+'tooltip-pt-preferences' => 'هەڵبژاردەکانت',
'tooltip-pt-watchlist' => 'پێرستی ئەو پەڕانە کە چاودێریی گۆڕانکارییەکانیانی دەکەی',
-'tooltip-pt-mycontris' => 'Ù\84Û\8cستی بەشدارییەکانت',
+'tooltip-pt-mycontris' => 'Ù¾Û\8eرستی بەشدارییەکانت',
'tooltip-pt-login' => 'پێشنیارت پێدەکرێ بچیتە ژوورەوە؛ ھەرچەندە زۆرت لێناکرێ',
'tooltip-pt-anonlogin' => 'پێشنیار دەکەین بڕۆیتەژوورەوە، ئەگەرچی ئەوە زۆرەملیی نیە',
'tooltip-pt-logout' => 'دەرچوون',
'qbbrowse' => 'Listování',
'qbedit' => 'Editování',
'qbpageoptions' => 'Tato stránka',
-'qbpageinfo' => 'Kontext',
'qbmyoptions' => 'Moje volby',
'qbspecialpages' => 'Speciální stránky',
'faq' => 'Často kladené otázky',
'edit-no-change' => 'Vaše editace byla ignorována, protože nedošlo k žádné změně textu.',
'edit-already-exists' => 'Nepodařilo se vytvořit novou stránku, protože již existuje.',
'defaultmessagetext' => 'Výchozí text hlášení',
+'content-failed-to-parse' => 'Nepodařilo se zpracovat data $2 do modelu $1: $3',
+'invalid-content-data' => 'Obsažená data jsou chybná',
+'content-not-allowed-here' => 'Obsah typu $1 není na stránce [[$2]] dovolen.',
+
+# Content models
+'content-model-wikitext' => 'wikitext',
+'content-model-text' => 'čistý text',
+'content-model-javascript' => 'JavaScript',
+'content-model-css' => 'CSS',
# Parser/template warnings
'expensive-parserfunction-warning' => 'Varování: Tato stránka obsahuje příliš mnoho volání výkonnostně náročných funkcí parseru.
'shared-repo-from' => 'z {{grammar:2sg|$1}}',
'shared-repo' => 'sdíleného úložiště',
'filepage.css' => '/* Zde uvedené CSS se vkládá na stránky s popisem souboru, včetně cizích klientských wiki */',
-'upload-disallowed-here' => 'Tento soubor bohužel nemůžete přepsat.',
+'upload-disallowed-here' => 'Tento soubor nemůžete přepsat.',
# File reversion
'filerevert' => 'Vrátit zpět $1',
'undeletedrevisions' => '{{PLURAL:$1|Obnovena $1 verze|Obnoveny $1 verze|Obnoveno $1 verzí}}',
'undeletedrevisions-files' => '{{PLURAL:$1|Obnovena jedna verze|Obnoveny $1 verze|Obnoveno $1 verzí}} a $2 {{PLURAL:$2|soubor|soubory|souborů}}.',
'undeletedfiles' => '{{PLURAL:$1|obnoven $1 soubor|obnoveny $1 soubory|obnoveno $1 souborů}}',
-'cannotundelete' => 'Obnovení se nepovedlo; stránku již pravděpodobně obnovil někdo jiný.',
+'cannotundelete' => 'Obnovení se nezdařilo:
+$1',
'undeletedpage' => "'''$1 byla obnovena'''
Záznam o posledních mazáních a obnoveních najdete v [[Special:Log/delete|knize smazaných stránek]].",
'immobile-target-namespace-iw' => 'Mezijazykový odkaz není validní cíl při přesouvání stránky.',
'immobile-source-page' => 'Tuto stránku nelze přesouvat.',
'immobile-target-page' => 'Stránku nelze přesunout na zadaný název.',
+'bad-target-model' => 'Požadovaný cíl používá jiný model obsahu. Nelze převést $1 na $2.',
'imagenocrossnamespace' => 'Nelze přesunout mimo jmenný prostor Soubor:',
'nonfile-cannot-move-to-file' => 'Do jmenného prostoru {{ns:file}} nelze přesouvat stránky nepřináležející k souboru',
'imagetypemismatch' => 'Nová přípona souboru neodpovídá jeho typu',
# Info page
'pageinfo-title' => 'Informace o stránce „$1“',
+'pageinfo-not-current' => 'Informace lze zobrazit jen pro aktuální verzi.',
'pageinfo-header-basic' => 'Základní údaje',
'pageinfo-header-edits' => 'Historie editací',
'pageinfo-header-restrictions' => 'Zámek stránky',
'qbbrowse' => 'Pori',
'qbedit' => 'Golygu',
'qbpageoptions' => 'Y dudalen hon',
-'qbpageinfo' => 'Cyd-destun',
'qbmyoptions' => 'Fy nhudalennau',
'qbspecialpages' => 'Tudalennau arbennig',
'faq' => 'Cwestiynau cyffredin',
'qbbrowse' => 'Gennemse',
'qbedit' => 'Redigér',
'qbpageoptions' => 'Indstillinger for side',
-'qbpageinfo' => 'Information om side',
'qbmyoptions' => 'Mine indstillinger',
'qbspecialpages' => 'Specielle sider',
'faq' => 'OSS',
'edit-no-change' => 'Din ændring ignoreredes, fordi der ikke var ændring af teksten.',
'edit-already-exists' => 'En ny side kunne ikke oprettes, fordi den allerede findes.',
'defaultmessagetext' => 'Standardtekst',
+'content-failed-to-parse' => 'Kunne ikke parse $2 indhold for $1 model: $3',
+'invalid-content-data' => 'Ugyldig indholdsdata',
+'content-not-allowed-here' => '"$1" indhold er ikke tilladt på siden [[$2]]',
+
+# Content models
+'content-model-wikitext' => 'wikitekst',
+'content-model-text' => 'almindelig tekst',
+'content-model-javascript' => 'JavaScript',
+'content-model-css' => 'CSS',
# Parser/template warnings
'expensive-parserfunction-warning' => 'Advarsel: Der er for mange beregningstunge oversætter-funktionskald på denne side.
'undeletedrevisions' => '$1 {{PLURAL:$1|version|versioner}} gendannet',
'undeletedrevisions-files' => '$1 {{plural:$1|version|versioner}} og $2 {{plural:$2|fil|filer}} gendannet',
'undeletedfiles' => '$1 {{plural:$1|fil|filer}} gendannet',
-'cannotundelete' => 'Gendannelse mislykkedes; en anden har allerede gendannet siden.',
+'cannotundelete' => 'Gendannelse mislykkedes:
+$1',
'undeletedpage' => "'''$1''' blev gendannet.
I [[Special:Log/delete|slette-loggen]] findes en oversigt over de nyligt slettede og gendannede sider.",
'immobile-target-namespace-iw' => 'En side kan ikke flyttes til en interwiki-henvisning.',
'immobile-source-page' => 'Denne side kan ikke flyttes.',
'immobile-target-page' => 'Kan ikke flytte til det navn.',
+'bad-target-model' => 'Den ønskede destination bruger en anden indholdsmodel. Kan ikke konvertere fra $1 til $2.',
'imagenocrossnamespace' => 'Filer kan ikke flyttes til et navnerum der ikke indeholder filer',
'nonfile-cannot-move-to-file' => 'Kan ikke flytte ikke-filer til fil-navnerummet',
'imagetypemismatch' => 'Den nye filendelse passer ikke til filtypen',
# Info page
'pageinfo-title' => 'Information om "$1"',
+'pageinfo-not-current' => 'Oplysninger vises kun for den aktuelle version.',
'pageinfo-header-basic' => 'Grundlæggende oplysninger',
'pageinfo-header-edits' => 'Redigeringshistorik',
'pageinfo-header-restrictions' => 'Sidebeskyttelse',
'qbbrowse' => 'Durchsuchen',
'qbedit' => 'Bearbeiten',
'qbpageoptions' => 'Seitenoptionen',
-'qbpageinfo' => 'Kontext',
'qbmyoptions' => 'Meine Seiten',
'qbspecialpages' => 'Spezialseiten',
'faq' => 'Häufig gestellte Fragen',
'mainpage' => 'Hauptseite',
'mainpage-description' => 'Hauptseite',
'policy-url' => 'Project:Richtlinien',
-'portal' => 'Gemeinschafts-Portal',
-'portal-url' => 'Project:Gemeinschafts-Portal',
+'portal' => 'Gemeinschaftsportal',
+'portal-url' => 'Project:Gemeinschaftsportal',
'privacy' => 'Datenschutz',
'privacypage' => 'Project:Datenschutz',
'edit-no-change' => 'Deine Bearbeitung wurde ignoriert, da keine Änderung an dem Text vorgenommen wurde.',
'edit-already-exists' => 'Die neue Seite konnte nicht erstellt werden, da sie bereits vorhanden ist.',
'defaultmessagetext' => 'Standardtext',
+'content-failed-to-parse' => 'Parsen des Inhalts $2 für Modell $1 fehlgeschlagen: $3',
+'invalid-content-data' => 'Ungültige Inhaltsdaten',
+'content-not-allowed-here' => 'Der Inhalt „$1“ ist auf der Seite [[$2]] nicht erlaubt',
+
+# Content models
+'content-model-wikitext' => 'Wikitext',
+'content-model-text' => 'Klartext',
+'content-model-javascript' => 'JavaScript',
+'content-model-css' => 'CSS',
# Parser/template warnings
'expensive-parserfunction-warning' => "'''Achtung''': Diese Seite enthält zu viele Aufrufe aufwändiger Parserfunktionen.
'searchhelp-url' => 'Help:Hilfe',
'searchmenu-prefix' => '[[Special:PrefixIndex/$1|Zeige alle Seiten, die mit dem Suchbegriff anfangen]]',
'searchprofile-articles' => 'Inhaltsseiten',
-'searchprofile-project' => 'Hilfe und Projektseiten',
+'searchprofile-project' => 'Hilfe- und Projektseiten',
'searchprofile-images' => 'Multimedia',
'searchprofile-everything' => 'Alles',
'searchprofile-advanced' => 'Erweitert',
'shared-repo-from' => 'aus $1',
'shared-repo' => 'einem gemeinsam genutzten Medienarchiv',
'filepage.css' => '/* Das folgende CSS wird auf Dateibeschreibungsseiten, auch auf fremden Client-Wikis, geladen. */',
-'upload-disallowed-here' => 'Leider kannst du dieses Bild nicht überschreiben.',
+'upload-disallowed-here' => 'Du kannst diese Datei nicht überschreiben.',
# File reversion
'filerevert' => 'Zurücksetzen von „$1“',
'undeletedrevisions' => '{{PLURAL:$1|1 Version wurde|$1 Versionen wurden}} wiederhergestellt',
'undeletedrevisions-files' => '{{PLURAL:$1|1 Version|$1 Versionen}} und {{PLURAL:$2|1 Datei|$2 Dateien}} wurden wiederhergestellt',
'undeletedfiles' => '{{PLURAL:$1|1 Datei wurde|$1 Dateien wurden}} wiederhergestellt',
-'cannotundelete' => 'Wiederherstellung fehlgeschlagen; jemand anderes hat die Seite bereits wiederhergestellt.',
+'cannotundelete' => 'Die Wiederherstellung ist fehlgeschlagen:
+$1',
'undeletedpage' => "'''„$1“''' wurde wiederhergestellt.
Im [[Special:Log/delete|Lösch-Logbuch]] findest du eine Übersicht der gelöschten und wiederhergestellten Seiten.",
'immobile-target-namespace-iw' => 'Interwiki-Link ist kein gültiges Ziel für Seitenverschiebungen.',
'immobile-source-page' => 'Diese Seite ist nicht verschiebbar.',
'immobile-target-page' => 'Es kann nicht auf diese Zielseite verschoben werden.',
+'bad-target-model' => 'Die gewünschte Zielseite verwendet ein abweichendes Inhaltsmodell. Das Inhaltsmodell $1 kann nicht in das Inhaltsmodell $2 umgewandelt werden.',
'imagenocrossnamespace' => 'Dateien können nicht aus dem {{ns:file}}-Namensraum heraus verschoben werden',
'nonfile-cannot-move-to-file' => 'Nichtdateien können nicht in den {{ns:file}}-Namensraum hinein verschoben werden',
'imagetypemismatch' => 'Die neue Dateierweiterung ist nicht mit der alten identisch',
'tooltip-pt-anontalk' => 'Diskussion über Änderungen von dieser IP-Adresse',
'tooltip-pt-preferences' => 'Eigene Einstellungen',
'tooltip-pt-watchlist' => 'Liste der beobachteten Seiten',
-'tooltip-pt-mycontris' => 'Liste deiner Beiträge',
+'tooltip-pt-mycontris' => 'Liste eigener Beiträge',
'tooltip-pt-login' => 'Sich anzumelden wird zwar gerne gesehen, ist aber keine Pflicht.',
'tooltip-pt-anonlogin' => 'Sich anzumelden wird zwar gerne gesehen, ist aber keine Pflicht.',
'tooltip-pt-logout' => 'Abmelden',
'tooltip-n-portal' => 'Über das Projekt, was du tun kannst, wo was zu finden ist',
'tooltip-n-currentevents' => 'Hintergrundinformationen zu aktuellen Ereignissen',
'tooltip-n-recentchanges' => 'Liste der letzten Änderungen in {{SITENAME}}',
-'tooltip-n-randompage' => 'Zufällige Seite',
+'tooltip-n-randompage' => 'Zufällige Seite aufrufen',
'tooltip-n-help' => 'Hilfeseite anzeigen',
'tooltip-t-whatlinkshere' => 'Liste aller Seiten, die hierher verlinken',
'tooltip-t-recentchangeslinked' => 'Letzte Änderungen an Seiten, die von hier verlinkt sind',
# Info page
'pageinfo-title' => 'Informationen zu „$1“',
+'pageinfo-not-current' => 'Diese Informationen können nur für die aktuelle Version angezeigt werden.',
'pageinfo-header-basic' => 'Basisinformationen',
'pageinfo-header-edits' => 'Bearbeitungsgeschichte',
'pageinfo-header-restrictions' => 'Seitenschutz',
# Font style option in Special:Preferences
'editfont-style' => 'Cayê vurnayışi de tipê nuştışi:',
-'editfont-default' => 'Hesıbyayiya rovıteri',
-'editfont-monospace' => 'Tipê nustey sabıtcagırewtoği',
-'editfont-sansserif' => 'Tipê nustey Sans-serifi',
-'editfont-serif' => 'Tipê nustey Serifi',
+'editfont-default' => 'Qerar cıgeyraoği dest dero',
+'editfont-monospace' => 'Tipê nusteyê sabıtcagırewtoği',
+'editfont-sansserif' => 'Babetê Sans-serifi',
+'editfont-serif' => 'Tipê nusteyê serifi',
# Dates
'sunday' => 'Bazar',
'thursday' => 'Pancşeme',
'friday' => 'Êne',
'saturday' => 'Bahdê êni',
-'sun' => 'Baz',
-'mon' => 'Ber',
+'sun' => 'Bzr',
+'mon' => 'Brr',
'tue' => 'Tlt',
'wed' => 'Çrş',
-'thu' => 'Pnş',
+'thu' => 'Pşm',
'fri' => 'Êne',
'sat' => 'Bah',
'january' => 'Çele',
'february' => 'Zemherı',
'march' => 'Mert',
-'april' => 'Lisane',
-'may_long' => 'Gulan',
+'april' => 'Lizan',
+'may_long' => 'Vılıkan',
'june' => 'Heziran',
'july' => 'Paliyan',
'august' => 'Tebaxe',
'january-gen' => 'Çele',
'february-gen' => 'Zemherı',
'march-gen' => 'Mert',
-'april-gen' => 'Lisan',
-'may-gen' => 'Gulan',
+'april-gen' => 'Lizan',
+'may-gen' => 'Vılıkan',
'june-gen' => 'Heziran',
'july-gen' => 'Paliyan',
'august-gen' => 'Tebaxe',
'about' => 'Heqa',
'article' => 'Wesiqe',
-'newwindow' => '(zerreyê teqeyê newey de beno a)',
+'newwindow' => '(teqa da newi de abena)',
'cancel' => 'Bıtexelne',
'moredotdotdot' => 'Vêşêri...',
'mypage' => 'Pela mı',
-'mytalk' => 'Verênayışê mı',
+'mytalk' => 'Behsê mı',
'anontalk' => 'Pela werênayışê nê IPy',
'navigation' => 'Geyrayış',
'and' => ' u',
'qbbrowse' => 'Rovete',
'qbedit' => 'Bıvurne',
'qbpageoptions' => 'Ena pele',
-'qbpageinfo' => 'Gıre',
'qbmyoptions' => 'Pelê mı',
'qbspecialpages' => 'Peley xısusi',
'faq' => 'PZP (Persê ke zehf persiyenê)',
'hr_tip' => 'Çıxiza dimdayi (hend akar mefiye)',
# Edit pages
-'summary' => 'Xulasa:',
+'summary' => 'Xelese:',
'subject' => 'Mewzu/serrêze:',
-'minoredit' => 'Eno yew vurnayışo qıckeko',
-'watchthis' => 'Ena perer temase ke',
+'minoredit' => 'Eno vurnayışo de qıckeko',
+'watchthis' => 'Ena perer teqib ke',
'savearticle' => 'Pele qeyd ke',
'preview' => 'Verqayt',
'showpreview' => 'Verqayti bımocne',
'edit-already-exists' => 'Pelo newe nêvıraziyeno.
Pel ca ra esto.',
'defaultmessagetext' => 'Hesıbyaye metne mesaci',
+'content-failed-to-parse' => 'Qandê madela $3 zereyê $1, $2 sero nêagozyayo',
+'invalid-content-data' => 'Zerrey malumati nêravêrdeyo',
+'content-not-allowed-here' => '"$1" sero per da [[$2]] rê mısade nêdeyêno',
+
+# Content models
+'content-model-wikitext' => 'wikimetin',
+'content-model-text' => 'duz metin',
+'content-model-javascript' => 'JavaScript',
+'content-model-css' => 'CSS',
# Parser/template warnings
'expensive-parserfunction-warning' => 'Hişyari: No pel de fonksiyoni zaf esti.
'shared-repo' => 'yew embarê repositoryî',
'shared-repo-name-wikimediacommons' => 'Wikimedia Commons',
'filepage.css' => '/* CSS placed here is included on the file description page, also included on foreign client wikis */',
-'upload-disallowed-here' => 'Nê asengi sero theba nênusneyêno.',
+'upload-disallowed-here' => 'Şıma na dosya de teba nênusnayo.',
# File reversion
'filerevert' => '$1 reyna biyere',
'usermessage-template' => 'MediaWiki:UserMessage',
# Watchlist
-'watchlist' => 'lista mına seyr-kerdışi',
-'mywatchlist' => 'Listey taqiban',
+'watchlist' => 'Listey teqiban',
+'mywatchlist' => 'Listey teqiban',
'watchlistfor2' => 'Qandê $1 ($2)',
'nowatchlist' => 'listeya temaşa kerdıişê şıma de yew madde zi çina.',
'watchlistanontext' => 'qey vurnayişê maddeya listeya temaşakerdişi $1.',
'undeletedrevisions' => 'pêro piya{{PLURAL:$1|1 qeyd|$1 qeyd}} tepiya anciya.',
'undeletedrevisions-files' => '{{PLURAL:$1|1 revizyon|$1 revizyon}} u {{PLURAL:$2|1 dosya|$2 dosya}} ameyê halê xo yê verıni',
'undeletedfiles' => '{{PLURAL:$1|1 dosya|$1 dosya}} tepiya anciyayi.',
-'cannotundelete' => 'şıma ya ver yewna ten pel u medya tepiya ard u ê ra tepiya ardışê şıma meqbul niyo.',
+'cannotundelete' => 'Besternayışo nêbeno:
+$1',
'undeletedpage' => "'''$1 pel tepiya anciya'''
qey karê tepiya ardışi u qey karê hewn a kerdışê verıni bıewnê [[Special:Log/delete|qeydê hewn a kerdışi]].",
# Contributions
'contributions' => 'İştiraqê karberi',
'contributions-title' => 'Dekerdenê karber de $1',
-'mycontris' => 'Dekerdeni',
+'mycontris' => 'Dekerdenê mı',
'contribsub2' => 'Qandê $1 ($2)',
'nocontribs' => 'Ena kriteriya de vurnayîş çini yo.',
'uctop' => '(top)',
'immobile-target-namespace-iw' => 'xetê benatê wikiyan, hedefê pelkırıştış niyo',
'immobile-source-page' => 'nameyê no peli nêvuriyeno',
'immobile-target-page' => 'sernameyê no hedefi re nêkırışiyeno',
+'bad-target-model' => 'Hedefo ke waştiyayo zerreke cı babetna model karneno. Ke nêşeno $1 ra açarno $2.',
'imagenocrossnamespace' => 'Dosya, ca yo ke qey nameyê dosyayan nêbıbo nêkırışiyeno',
'nonfile-cannot-move-to-file' => 'Ekê dosya niyê, cade namande dosyaya nêahulneyênê',
'imagetypemismatch' => 'tipa dosyaya neweyi re pênêgıneno/nêgıneno pê',
'allmessages' => 'Mesacê sistemi',
'allmessagesname' => 'Name',
'allmessagesdefault' => 'Hesıbyaye metnê mesaci',
-'allmessagescurrent' => 'nuşte yo ke Karyayo',
+'allmessagescurrent' => 'Metno ke karyayo',
'allmessagestext' => 'na liste, listeya mesajê cayê nameyê wikimedya yo.
eke şıma qayili paşt bıdi mahalli kerdışê wikimedyayi, kerem kerê pelê [//www.mediawiki.org/wiki/Localisation mahalli kerdışê wikimedyayi] u [//translatewiki.net translatewiki.net] ziyaret bıkerê.',
'allmessagesnotsupportedDB' => "'''\$wgUseDatabaseMessages''' qefelnaye yo u ey ra '''{{ns:special}}:Allmessages''' karkerdışi re akerde niyo.",
# Info page
'pageinfo-title' => 'Heq tê "$1"\'i',
+'pageinfo-not-current' => 'Malumat tenya qande rocane rewizyoni mocneyêno.',
'pageinfo-header-basic' => 'Seron zanayış',
'pageinfo-header-edits' => 'Vurnayışi verêni',
'pageinfo-header-restrictions' => 'Xısusiyetê pela',
# Watchlist editing tools
'watchlisttools-view' => 'vurnayışanê eleqadari bıvin',
-'watchlisttools-edit' => 'listey taqiban bıvinê u bıvurnê',
+'watchlisttools-edit' => 'Lista seyrkerdışi bıvênên u bıvurnên',
'watchlisttools-raw' => 'Listeyê seyr-kerdışi bıvin',
# Iranian month names
'qbbrowse' => 'Pśeběraś',
'qbedit' => 'Pśeměniś',
'qbpageoptions' => 'Toś ten bok',
-'qbpageinfo' => 'Kontekst',
'qbmyoptions' => 'Móje boki',
'qbspecialpages' => 'Specialne boki',
'faq' => 'FAQ (pšašanja a wótegrona)',
'edit-already-exists' => 'Njejo móžno było nowy bok napóraś.
Eksistěrujo južo.',
'defaultmessagetext' => 'Standardny tekst powěźeńki',
+'content-failed-to-parse' => 'Parsowanje wopśimjeśa $2 za model $1 jo se njeraźiło: $3',
+'invalid-content-data' => 'Njepłaśiwe wopśimjeśowe daty',
+'content-not-allowed-here' => 'Wopśimjeśe "$1" njejo na boku [[$2]] dowólone',
+
+# Content models
+'content-model-wikitext' => 'wikitekst',
+'content-model-text' => 'lutny tekst',
+'content-model-javascript' => 'JavaScript',
+'content-model-css' => 'CSS',
# Parser/template warnings
'expensive-parserfunction-warning' => 'Warnowanje: Toś ten bok wopśimujo pśewjele wołanjow parserowych funkcijow wupominajucych wusoke wugbaśe.
'undeletedrevisions' => '{{PLURAL:$1|1 wersija jo se nawrośiła|$1 wersiji stej se nawrośiłej|$1 wersije su se nawrośili}}.',
'undeletedrevisions-files' => '{{PLURAL:$1|1 wersija|$1 wersiji|$1 wersije}} a {{PLURAL:$2|1 dataja|$2 dataji|$2 dataje}} {{PLURAL:$2|jo se nawrośiła|stej se nawrośiłej|su se nawrośili}}.',
'undeletedfiles' => '{{PLURAL:$1|1 dataja jo se nawrośiła|$1 dataji stej se nawrośiłej|$1 dataje su se nawrośili}}.',
-'cannotundelete' => 'Nawrośenje njejo se zglucyło; něchten drugi jo bok južo nawrośił.',
+'cannotundelete' => 'Wótnowjenje jo se njeraźiło:
+$1',
'undeletedpage' => "Bok '''$1''' jo se nawrośił.
W [[Special:Log/delete|log-lisćinje wulašowanjow]] namakajoš pśeglěd wulašowanych a nawrośonych bokow.",
'immobile-target-namespace-iw' => 'Interwiki-wótkaz njejo płaśiwy cel za pśesunjenja bokow.',
'immobile-source-page' => 'Toś ten bok njedajo se pśesunuś.',
'immobile-target-page' => 'Njejo móžno na toś ten celowy bok pśesunuś.',
+'bad-target-model' => 'Póžedany cel wužywa drugi wopśimjeśowy model. $1 njedajo se do $2 konwertěrowaś.',
'imagenocrossnamespace' => 'Dataja njedajo se pśesunuś do mjenjowego ruma, kótarež njejo za dataje.',
'nonfile-cannot-move-to-file' => 'Njedataje njedaje se do datajowego mjenjowego ruma pśesunuś',
'imagetypemismatch' => 'Nowy datajowy sufiks swójomu typoju njewótpowědujo',
'qbbrowse' => 'Περιήγηση',
'qbedit' => 'Επεξεργασία',
'qbpageoptions' => 'Αυτή η σελίδα',
-'qbpageinfo' => 'Συμφραζόμενα',
'qbmyoptions' => 'Οι σελίδες μου',
'qbspecialpages' => 'Σελίδες λειτουργιών',
'faq' => 'Συχνές ερωτήσεις (FAQ)',
'qbbrowse' => 'Browse',
'qbedit' => 'Edit',
'qbpageoptions' => 'This page',
-'qbpageinfo' => 'Context',
'qbmyoptions' => 'My pages',
'qbspecialpages' => 'Special pages',
'faq' => 'FAQ',
'addsection-preload' => '', # do not translate or duplicate this message to other languages
'addsection-editintro' => '', # do not translate or duplicate this message to other languages
'defaultmessagetext' => 'Default message text',
+'content-failed-to-parse' => 'Failed to parse $2 content for $1 model: $3',
+'invalid-content-data' => 'Invalid content data',
+'content-not-allowed-here' => '"$1" content is not allowed on page [[$2]]',
+
+# Content models
+'content-model-wikitext' => 'wikitext',
+'content-model-text' => 'plain text',
+'content-model-javascript' => 'JavaScript',
+'content-model-css' => 'CSS',
# Parser/template warnings
'expensive-parserfunction-warning' => "'''Warning:''' This page contains too many expensive parser function calls.
'shared-repo' => 'a shared repository',
'shared-repo-name-wikimediacommons' => 'Wikimedia Commons', # only translate this message to other languages if you have to change it
'filepage.css' => '/* CSS placed here is included on the file description page, also included on foreign client wikis */', # only translate this message to other languages if you have to change it
-'upload-disallowed-here' => 'Unfortunately you cannot overwrite this image.',
+'upload-disallowed-here' => 'You cannot overwrite this file.',
# File reversion
'filerevert' => 'Revert $1',
'undeletedrevisions' => '{{PLURAL:$1|1 revision|$1 revisions}} restored',
'undeletedrevisions-files' => '{{PLURAL:$1|1 revision|$1 revisions}} and {{PLURAL:$2|1 file|$2 files}} restored',
'undeletedfiles' => '{{PLURAL:$1|1 file|$1 files}} restored',
-'cannotundelete' => 'Undelete failed;
-someone else may have undeleted the page first.',
+'cannotundelete' => 'Undelete failed:
+$1',
'undeletedpage' => "'''$1 has been restored'''
Consult the [[Special:Log/delete|deletion log]] for a record of recent deletions and restorations.",
'immobile-target-namespace-iw' => 'Interwiki link is not a valid target for page move.',
'immobile-source-page' => 'This page is not movable.',
'immobile-target-page' => 'Cannot move to that destination title.',
+'bad-target-model' => 'The desired destination uses a different content model. Can not convert from $1 to $2.',
'imagenocrossnamespace' => 'Cannot move file to non-file namespace',
'nonfile-cannot-move-to-file' => 'Cannot move non-file to file namespace',
'imagetypemismatch' => 'The new file extension does not match its type',
# Info page
'pageinfo-header' => '-', # do not translate or duplicate this message to other languages
'pageinfo-title' => 'Information for "$1"',
+'pageinfo-not-current' => 'Information may only be displayed for the current revision.',
'pageinfo-header-basic' => 'Basic information',
'pageinfo-header-edits' => 'Edit history',
'pageinfo-header-restrictions' => 'Page protection',
'qbbrowse' => 'Foliumi',
'qbedit' => 'Redakti',
'qbpageoptions' => 'Paĝagado',
-'qbpageinfo' => 'Paĝinformoj',
'qbmyoptions' => 'Personaĵoj',
'qbspecialpages' => 'Specialaj paĝoj',
'faq' => 'Oftaj demandoj',
* @author Locos epraix
* @author Mahadeva
* @author Manuelt15
+ * @author Maor X
* @author McDutchie
* @author Muro de Aguas
* @author Omnipaedista
'qbbrowse' => 'Navegar',
'qbedit' => 'Editar',
'qbpageoptions' => 'Opciones de página',
-'qbpageinfo' => 'Información de página',
'qbmyoptions' => 'Mis páginas',
'qbspecialpages' => 'Páginas especiales',
'faq' => 'Preguntas más frecuentes',
'faqpage' => 'Project:FAQ',
# Vector skin
-'vector-action-addsection' => 'Añadir tema',
+'vector-action-addsection' => 'Nueva sección',
'vector-action-delete' => 'Borrar',
'vector-action-move' => 'Mover',
'vector-action-protect' => 'Proteger',
'edit-already-exists' => 'No se pudo crear una página nueva.
Ya existe.',
'defaultmessagetext' => 'Texto de mensaje predeterminado',
+'content-failed-to-parse' => 'No se pudo analizar el contenido $2 del modelo $1: $3',
+'invalid-content-data' => 'Datos de contenido inválidos',
+'content-not-allowed-here' => 'El contenido "$1" no está permitido en la página [[$2]]',
+
+# Content models
+'content-model-wikitext' => 'texto wiki',
+'content-model-text' => 'Texto sin formato',
+'content-model-javascript' => 'JavaScript',
+'content-model-css' => 'CSS',
# Parser/template warnings
'expensive-parserfunction-warning' => 'Aviso: Esta página contiene demasiadas llamadas a funciones sintácticas costosas (#ifexist: y similares)
Leyenda: (act) = diferencias con la versión actual,
(prev) = diferencias con la versión previa, M = edición menor",
'history-fieldset-title' => 'Buscar en el historial',
-'history-show-deleted' => 'Solamente borrado',
+'history-show-deleted' => 'Solo ediciones ocultadas',
'histfirst' => 'Primeras',
'histlast' => 'Últimas',
'historysize' => '({{PLURAL:$1|1 byte|$1 bytes}})',
'revdelete-otherreason' => 'Otra/adicional razón:',
'revdelete-reasonotherlist' => 'Otra razón',
'revdelete-edit-reasonlist' => 'Editar razones de borrado',
-'revdelete-offender' => 'Autor de revisión:',
+'revdelete-offender' => 'Autor de la revisión:',
# Suppression log
'suppressionlog' => 'Registro de supresiones',
'suppressionlogtext' => 'A continuación hay una lista con los borrados y bloqueos cuyo contenido se encuentra oculto para los administradores.
-Ver la [[Special:BlockList|lista de bloqueos]] que incluye las prohibiciones y bloqueos actualmente operativos.',
+Véase la [[Special:BlockList|lista de bloqueos]] que incluye las prohibiciones y bloqueos actualmente operativos.',
# History merging
'mergehistory' => 'Fusionar historiales de páginas',
'shared-repo-from' => 'de $1',
'shared-repo' => 'un repositorio compartido',
'filepage.css' => '/* Los estilos CSS colocados aquí se incluirán en las páginas de descripción de archivos, incluso en los wikis externos que incluyan estas páginas */',
-'upload-disallowed-here' => 'Lamentablemente no puedes sobrescribir esta imagen.',
+'upload-disallowed-here' => 'No puedes sobrescribir este archivo.',
# File reversion
'filerevert' => 'Revertir $1',
Puedes filtrar la vista seleccionando un tipo de registro, el nombre del usuario o la página afectada. Se distinguen mayúsculas de minúsculas.',
'logempty' => 'No hay elementos en el registro con esas condiciones.',
'log-title-wildcard' => 'Buscar títulos que empiecen con este texto',
-'showhideselectedlogentries' => 'Mostrar u ocultar las entradas del registro seleccionado',
+'showhideselectedlogentries' => 'Mostrar u ocultar las entradas seleccionadas del registro',
# Special:AllPages
'allpages' => 'Todas las páginas',
'undeletedrevisions' => '{{PLURAL:$1|Una edición restaurada|$1 ediciones restauradas}}',
'undeletedrevisions-files' => '$1 {{PLURAL:$1|ediciones restauradas y $2 archivo restaurado|ediciones y $2 archivos restaurados}}',
'undeletedfiles' => '$1 {{PLURAL:$1|archivo restaurado|archivos restaurados}}',
-'cannotundelete' => 'Ha fallado el deshacer el borrado;
-alguien más puede haber deshecho el borrado antes.',
+'cannotundelete' => 'Hubo un error durante la restauración:
+$1',
'undeletedpage' => "'''Se ha restaurado $1'''
Consulta el [[Special:Log/delete|registro de borrados]] para ver una lista de los últimos borrados y restauraciones.",
A continuación se muestra la última entrada del registro de bloqueos para mayor referencia.',
'sp-contributions-search' => 'Buscar contribuciones',
'sp-contributions-username' => 'Dirección IP o nombre de usuario:',
-'sp-contributions-toponly' => 'Mostrar solamente revisiones top',
+'sp-contributions-toponly' => 'Solo mostrar últimas ediciones de página',
'sp-contributions-submit' => 'Buscar',
# What links here
'immobile-target-namespace-iw' => 'Un enlace interwiki no es un destino válido para trasladar una página.',
'immobile-source-page' => 'Esta página no se puede renombrar.',
'immobile-target-page' => 'No se puede trasladar a tal título.',
+'bad-target-model' => 'El destino deseado utiliza un modelo diferente de contenido. No se puede realizar la conversión de $1 a $2.',
'imagenocrossnamespace' => 'No se puede trasladar el fichero a otro espacio de nombres',
'nonfile-cannot-move-to-file' => 'No es posible mover un no-archivo al espacio de nombres de archivo',
'imagetypemismatch' => 'La nueva extensión de archivo no corresponde con su tipo',
# Info page
'pageinfo-title' => 'Información para «$1»',
+'pageinfo-not-current' => 'Únicamente se puede mostrar la información para la revisión actual.',
'pageinfo-header-basic' => 'Información básica',
'pageinfo-header-edits' => 'Historial de ediciones',
'pageinfo-header-restrictions' => 'Protección de página',
'pageinfo-magic-words' => '{{PLURAL:$1|Palabra mágica|Palabras mágicas}} ($1)',
'pageinfo-hidden-categories' => '{{PLURAL:$1|Categoría oculta|Categorías ocultas}} ($1)',
'pageinfo-templates' => '{{PLURAL:$1|plantilla incluida|plantillas incluidas}} ($1)',
+'pageinfo-toolboxlink' => 'Información de la página',
# Skin names
'skinname-standard' => 'Estándar',
# Scary transclusion
'scarytranscludedisabled' => '[Transclusión interwiki está deshabilitada]',
'scarytranscludefailed' => '[Obtención de plantilla falló para $1]',
+'scarytranscludefailed-httpstatus' => '[Error de recuperación de plantilla para $1: HTTP $2]',
'scarytranscludetoolong' => '[El URL es demasiado largo]',
# Delete conflict
'sqlite-no-fts' => '$1 sin soporte para búsqueda de texto completo',
# New logging system
-'logentry-delete-delete' => '$1 borró la página $3',
-'logentry-delete-restore' => '$1 restauró la página $3',
+'logentry-delete-delete' => '$1 borró la página «$3»',
+'logentry-delete-restore' => '$1 restauró la página «$3»',
'logentry-delete-event' => '$1 modificó la visibilidad de {{PLURAL:$5|un evento del registro|$5 eventos del registro}} en $3: $4',
'logentry-delete-revision' => '$1 modificó la visibilidad de {{PLURAL:$5|una edición|$5 ediciones}} en la página $3: $4',
'logentry-delete-event-legacy' => '$1 modificó la visibilidad de los eventos del registro en $3',
# Search suggestions
'searchsuggest-search' => 'Buscar',
-'searchsuggest-containing' => 'conteniendo...',
+'searchsuggest-containing' => 'que contiene...',
# API errors
'api-error-badaccess-groups' => 'No puedes cargar archivos en este wiki.',
'qbbrowse' => 'Sirvi',
'qbedit' => 'Redigeeri',
'qbpageoptions' => 'Lehekülje suvandid',
-'qbpageinfo' => 'Lehekülje andmed',
'qbmyoptions' => 'Minu suvandid',
'qbspecialpages' => 'Erileheküljed',
'faq' => 'KKK',
'qbbrowse' => 'Arakatu',
'qbedit' => 'Aldatu',
'qbpageoptions' => 'Orrialde hau',
-'qbpageinfo' => 'Testuingurua',
'qbmyoptions' => 'Nire orrialdeak',
'qbspecialpages' => 'Aparteko orrialdeak',
'faq' => 'Maiz egindako galderak',
'qbbrowse' => 'Escrucal',
'qbedit' => 'Eital',
'qbpageoptions' => 'Esta páhina',
-'qbpageinfo' => 'Contestu',
'qbmyoptions' => 'Las mis páhinas',
'qbspecialpages' => 'Páhinas especialis',
'faq' => 'FAQ',
'qbbrowse' => 'مرور',
'qbedit' => 'ویرایش',
'qbpageoptions' => 'این صفحه',
-'qbpageinfo' => 'محتوا',
'qbmyoptions' => 'صفحههای من',
'qbspecialpages' => 'صفحههای ویژه',
'faq' => 'پرسشهای متداول',
'vector-action-protect' => 'محافظت',
'vector-action-undelete' => 'احیا',
'vector-action-unprotect' => 'تغییر سطح حفاظت',
-'vector-simplesearch-preference' => 'فعال کردن پیشنهادهای جستجوی پیشرفته (فقط در پوستهٔ برداری)',
+'vector-simplesearch-preference' => 'فعال کردن جستجوی ساده (فقط در پوستهٔ برداری)',
'vector-view-create' => 'ایجاد',
'vector-view-edit' => 'ویرایش',
'vector-view-history' => 'نمایش تاریخچه',
'disclaimerpage' => 'Project:تکذیبنامهٔ عمومی',
'edithelp' => 'راهنمای ویرایشکردن',
'edithelppage' => 'Help:ویرایش',
-'helppage' => 'Help:محتویات',
+'helppage' => 'Help:محتوا',
'mainpage' => 'صفحهٔ اصلی',
'mainpage-description' => 'صفحهٔ اصلی',
'policy-url' => 'Project:سیاستها',
'mergehistory-fail' => 'ادغام تاریخچه ممکن نیست، لطفاً گزینههای صفحه و زمان را بازبینی کنید.',
'mergehistory-no-source' => 'صفحهٔ مبدأ $1 وجود ندارد.',
'mergehistory-no-destination' => 'صفحهٔ مقصد $1 وجود ندارد.',
-'mergehistory-invalid-source' => 'صفحهٔ مبدأ باید عنوان قابل قبولی داشته باشد.',
-'mergehistory-invalid-destination' => 'صفحهٔ مقصد باید عنوان قابل قبولی داشته باشد.',
+'mergehistory-invalid-source' => 'صفحهٔ مبدأ باید عنوانی معتبر داشته باشد.',
+'mergehistory-invalid-destination' => 'صفحهٔ مقصد باید عنوانی معتبر داشته باشد.',
'mergehistory-autocomment' => '[[:$1]] را در [[:$2]] ادغام کرد',
'mergehistory-comment' => '[[:$1]] را در [[:$2]] ادغام کرد: $3',
'mergehistory-same-destination' => 'صفحهٔ مبدأ و مقصد نمیتواند یکی باشد',
'searchmenu-legend' => 'گزینههای جستجو',
'searchmenu-exists' => "'''صفحهای با عنوان \"[[:\$1]]\" در این ویکی وجود دارد.'''",
'searchmenu-new' => "'''صفحهٔ «[[:$1]]» را در این ویکی بسازید!'''",
-'searchhelp-url' => 'Help:راهنما',
+'searchhelp-url' => 'Help:محتوا',
'searchmenu-prefix' => '[[Special:PrefixIndex/$1|مرور صفحههای با این پیشوند]]',
'searchprofile-articles' => 'صفحههای محتوایی',
'searchprofile-project' => 'صفحههای راهنما و پروژه',
'shared-repo-from' => 'از $1',
'shared-repo' => 'یک مخزن مشترک',
'shared-repo-name-wikimediacommons' => 'ویکیانبار',
-'upload-disallowed-here' => 'Ù\85تاسÙ\81اÙ\86Ù\87 Ø´Ù\85ا Ù\86Ù\85Û\8c تÙ\88اÙ\86Û\8cد اÛ\8cÙ\86 Ù\86گاره را بازنویس کنید.',
+'upload-disallowed-here' => 'Ù\85تاسÙ\81اÙ\86Ù\87 Ø´Ù\85ا Ù\86Ù\85Û\8c تÙ\88اÙ\86Û\8cد اÛ\8cÙ\86 پرÙ\88Ù\86ده را بازنویس کنید.',
# File reversion
'filerevert' => 'واگردانی $1',
'undeletedrevisions-files' => '$1 نسخه و $2 پرونده احیا {{PLURAL:$1|شد|شدند}}.',
'undeletedfiles' => '$1 پرونده احیا {{PLURAL:$1|شد|شدند}}.',
'cannotundelete' => 'احیا ناموفق بود؛
-ممکن است کس دیگری پیشتر این صفحه را احیا کرده باشد.',
+$1',
'undeletedpage' => "'''$1 احیا شد'''
برای دیدن سیاههٔ حذفها و احیاهای اخیر به [[Special:Log/delete|سیاههٔ حذف]] رجوع کنید.",
'pageinfo-magic-words' => '{{PLURAL:$1|حرف|حروف}} جادویی ($1)',
'pageinfo-hidden-categories' => '{{PLURAL:$1| ردهٔ|ردهٔ}} پنهان ( $1 )',
'pageinfo-templates' => '{{PLURAL:$1|الگو|الگو}} استفادهشده ($1)',
+'pageinfo-toolboxlink' => 'اطلاعات صفحه',
# Skin names
'skinname-standard' => 'کلاسیک',
# Scary transclusion
'scarytranscludedisabled' => '[تراگنجانش بینویکیانه فعال نیست]',
'scarytranscludefailed' => '[فراخوانی الگو برای $1 میسر نشد]',
+'scarytranscludefailed-httpstatus' => '[فراخوانی الگو برای $1 میسر نشد: خطای اچتیتیپی $2]',
'scarytranscludetoolong' => '[نشانی اینترنتی مورد نظر (URL) بیش از اندازه بلند بود]',
# Delete conflict
'qbbrowse' => 'Selaa',
'qbedit' => 'Muokkaa',
'qbpageoptions' => 'Sivuasetukset',
-'qbpageinfo' => 'Sivun tiedot',
'qbmyoptions' => 'Asetukset',
'qbspecialpages' => 'Toimintosivut',
'faq' => 'Usein kysytyt kysymykset',
'delete-hook-aborted' => 'Laajennuskoohdi esti muokkauksen antamatta syytä.',
'badtitle' => 'Virheellinen otsikko',
'badtitletext' => 'Pyytämäsi sivuotsikko oli virheellinen, tyhjä tai väärin linkitetty kieltenvälinen tai wikienvälinen linkki.',
-'perfcached' => 'Tiedot ovat välimuistista eivätkä välttämättä ole ajan tasalla. Välimuistissa on enintään {{PLURAL:$1|yksi tulos|$1 tulosta}}.',
-'perfcachedts' => 'Tiedot ovat välimuistista ja se päivitettiin viimeksi $1. Välimuistissa on enintään {{PLURAL:$4|yksi tulos|$4 tulosta}}.',
+'perfcached' => 'Tiedot ovat välimuistista eivätkä välttämättä ole ajan tasalla. Välimuistissa on saatavilla enintään {{PLURAL:$1|yksi tulos|$1 tulosta}}.',
+'perfcachedts' => 'Tiedot ovat välimuistista ja se päivitettiin viimeksi $1. Välimuistissa on saatavilla enintään {{PLURAL:$4|yksi tulos|$4 tulosta}}.',
'querypage-no-updates' => 'Tämän sivun tietoja ei toistaiseksi päivitetä.',
'wrong_wfQuery_params' => 'Virheelliset parametrit wfQuery()<br />Funktio: $1<br />Tiedustelu: $2',
'viewsource' => 'Lähdekoodi',
'noemailprefs' => 'Sähköpostiosoitetta ei ole määritelty.',
'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ä.',
+'cannotchangeemail' => 'Tunnusten 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.',
'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' => 'Luodaan sivu $1',
+'creating' => 'Luodaan sivua $1',
'editingsection' => 'Muokataan osiota sivusta $1',
'editingcomment' => 'Muokataan uutta osiota sivulla $1',
'editconflict' => 'Päällekkäinen muokkaus: $1',
'edit-already-exists' => 'Uuden sivun luominen ei onnistunut.
Se on jo olemassa.',
'defaultmessagetext' => 'Viestin oletusteksti',
+'content-not-allowed-here' => '$1 ei ole sallittua sisältöä sivulla [[$2]]',
+
+# Content models
+'content-model-javascript' => 'JavaScript',
+'content-model-css' => 'CSS',
# Parser/template warnings
'expensive-parserfunction-warning' => 'Tällä sivulla on liian monta hitaiden laajennusfunktioiden kutsua.
'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; joku muu on voinut jo palauttaa sivun.',
+'cannotundelete' => 'Palauttaminen epäonnistui:
+$1',
'undeletedpage' => "'''$1 on palautettu.'''
[[Special:Log/delete|Poistolokista]] löydät listan viimeisimmistä poistoista ja palautuksista.",
'pageinfo-watchers' => 'Sivun tarkkailijoiden lukumäärä',
'pageinfo-redirects-name' => 'Sivulle johtavat ohjaukset',
'pageinfo-subpages-name' => 'Sivun alasivut',
-'pageinfo-firstuser' => 'Sivun tekijä',
+'pageinfo-firstuser' => 'Sivun luonut',
'pageinfo-lastuser' => 'Viimeisin muokkaaja',
'pageinfo-edits' => 'Muokkausten kokonaismäärä',
'pageinfo-authors' => 'Sivun eri muokkaajien kokonaismäärä',
+'pageinfo-magic-words' => '{{PLURAL:$1|Taikasana|Taikasanat}} ($1)',
'pageinfo-hidden-categories' => '{{PLURAL:$1|Piilotettu luokka|Piilotetut luokat}} ($1)',
'pageinfo-templates' => '{{PLURAL:$1|Sisällytetty malline|Sisällytetyt mallineet}} ($1)',
'pageinfo-toolboxlink' => 'Sivun tiedot',
# Scary transclusion
'scarytranscludedisabled' => '[Wikienvälinen sisällytys ei ole käytössä]',
'scarytranscludefailed' => '[Mallineen hakeminen epäonnistui: $1]',
+'scarytranscludefailed-httpstatus' => '[Mallineen hakeminen epäonnistui: $1 HTTP $2]',
'scarytranscludetoolong' => '[Verkko-osoite on liian pitkä]',
# Delete conflict
'tog-shownumberswatching' => 'Näytä kuinka moni käyttäjä valvoo sivua',
'tog-oldsig' => 'Nykynen allekirjotus',
'tog-fancysig' => 'Mookkaamaton allekirjotus ilman auttomaattista linkkiä',
-'tog-externaleditor' => 'Käytä ekterniä tekstiedituuria stantartina. Vain kokenheile käyttäjile, vaatii taattorin asetuksitten muuttamista. Käytä eksterniä tekstiedituuria oletuksena. Vain kokeneille käyttäjille, vaatii selaimen asetusten muuttamista. (<span class="plainlinks">[[//www.mediawiki.org/wiki/Manual:External_editors Ohje]</span>)',
+'tog-externaleditor' => 'Käytä ekterniä tekstiedituuria stantartina. Vain kokenheile käyttäjile, vaatii taattorin asetuksitten muuttamista. Käytä eksterniä tekstiedituuria oletuksena. Vain kokeneille käyttäjille, vaatii selaimen asetusten muuttamista. ([//www.mediawiki.org/wiki/Manual:External_editors Ohje])',
# Dates
'sunday' => 'pyhä',
# Cologne Blue skin
'qbedit' => 'Mookkaa',
'qbpageoptions' => 'Tämä sivu',
-'qbpageinfo' => 'Sisältö',
'qbmyoptions' => 'Minun inställninkit',
'qbspecialpages' => 'Spesiaali sivut',
'faq' => 'Useasti kysytyt kysymykset',
'qbbrowse' => 'Parcourir',
'qbedit' => 'Modifier',
'qbpageoptions' => 'Cette page',
-'qbpageinfo' => 'Contexte',
'qbmyoptions' => 'Mes pages',
'qbspecialpages' => 'Pages spéciales',
'faq' => 'FAQ',
'vector-simplesearch-preference' => "Activer la barre de recherche simplifiée (seulement pour l'habillage Vector)",
'vector-view-create' => 'Créer',
'vector-view-edit' => 'Modifier',
-'vector-view-history' => 'Afficher l’historique',
+'vector-view-history' => "Afficher l'historique",
'vector-view-view' => 'Lire',
'vector-view-viewsource' => 'Voir la source',
'actions' => 'Actions',
Votre compte a été créé.
N’oubliez pas de personnaliser vos [[Special:Preferences|préférences sur {{SITENAME}}]].',
-'yourname' => 'Nom d’utilisateur :',
+'yourname' => 'Nom d’utilisateur :',
'yourpassword' => 'Mot de passe :',
'yourpasswordagain' => 'Confirmez le mot de passe :',
'remembermypassword' => 'Me reconnecter automatiquement aux prochaines visites avec ce navigateur (au maximum $1 {{PLURAL:$1|jour|jours}})',
pouvez ignorer ce message et continuer à utiliser votre ancien mot de passe.',
'noemail' => "Aucune adresse de courriel n’a été enregistrée pour l'utilisateur « $1 ».",
'noemailcreate' => 'Vous devez fournir une adresse de courriel valide',
-'passwordsent' => 'Un nouveau mot de passe a été envoyé à l’adresse de courriel de l’utilisateur « $1 ». Veuillez vous reconnecter après l’avoir reçu.',
+'passwordsent' => "Un nouveau mot de passe a été envoyé à l'adresse de courriel de l'utilisateur « $1 ». Veuillez vous reconnecter après l'avoir reçu.",
'blocked-mailpassword' => 'Votre adresse IP est bloquée en écriture, la fonction de rappel du mot de passe est donc désactivée pour éviter les abus.',
'eauthentsent' => 'Un courriel de confirmation a été envoyé à l’adresse indiquée.
Avant qu’un autre courriel ne soit envoyé à ce compte, vous devrez suivre les instructions du courriel et confirmer que le compte est bien le vôtre.',
Si vous cliquez de nouveau sur « {{int:Savearticle}} », votre modification sera enregistrée sans titre.",
'summary-preview' => 'Aperçu du résumé :',
'subject-preview' => 'Prévisualisation du sujet/titre :',
-'blockedtitle' => 'L’utilisateur est bloqué.',
+'blockedtitle' => "L'utilisateur est bloqué.",
'blockedtext' => "'''Votre compte utilisateur ou votre adresse IP a été bloqué.'''
Le blocage a été effectué par $1.
'edit-already-exists' => 'La nouvelle page n’a pas pu être créée.
Elle existe déjà.',
'defaultmessagetext' => 'Message par défaut',
+'content-failed-to-parse' => "Échec de l'analyse du contenu de $2 pour le modèle $1: $3",
+'invalid-content-data' => 'Données du contenu non valides',
+'content-not-allowed-here' => 'Le contenu "$1" n\'est pas autorisé sur la page [[$2]]',
+
+# Content models
+'content-model-wikitext' => 'wikitexte',
+'content-model-text' => 'texte brut',
+'content-model-javascript' => 'JavaScript',
+'content-model-css' => 'CSS',
# Parser/template warnings
'expensive-parserfunction-warning' => 'Attention : cette page contient de trop nombreux appels à des fonctions coûteuses de l’analyseur syntaxique.
'page_first' => 'première',
'page_last' => 'dernière',
'histlegend' => 'Légende : ({{int:cur}}) = différence avec la version actuelle, ({{int:last}}) = différence avec la version précédente, <b>{{int:minoreditletter}}</b> = modification mineure',
-'history-fieldset-title' => 'Naviguer dans l’historique',
+'history-fieldset-title' => "Naviguer dans l'historique",
'history-show-deleted' => 'Masqués seulement',
'histfirst' => 'première page',
'histlast' => 'dernière page',
'userrights-user-editname' => 'Entrez un nom d’utilisateur :',
'editusergroup' => 'Modification des groupes d’utilisateurs',
'editinguser' => "Modification des droits de l’{{GENDER:$1|utilisateur|utilisatrice}} '''[[User:$1|$1]]''' $2",
-'userrights-editusergroup' => 'Modifier les groupes de l’utilisateur',
+'userrights-editusergroup' => "Modifier les groupes de l'utilisateur",
'saveusergroups' => 'Enregistrer les groupes de l’utilisateur',
'userrights-groupsmember' => 'Membre de :',
'userrights-groupsmember-auto' => 'Membre implicite de :',
# User rights log
'rightslog' => 'Journal des modifications de droits d’utilisateurs',
'rightslogtext' => 'Voici l’historique des modifications des droits des utilisateurs.',
-'rightslogentry' => 'a modifié les droits de l’utilisateur « $1 » de $2 à $3',
+'rightslogentry' => "a modifié les droits de l'utilisateur « $1 » de $2 à $3",
'rightslogentry-autopromote' => 'a été automatiquement promu de $2 à $3',
'rightsnone' => '(aucun)',
'shared-repo' => 'un dépôt partagé',
'shared-repo-name-wikimediacommons' => 'Wikimédia Commons',
'filepage.css' => '/* Les styles CSS placés ici sont inclus dans la page de description du fichier, également incluse sur les clients wikis étrangers */',
-'upload-disallowed-here' => 'Malheureusement, vous ne peut pas remplacer cette image.',
+'upload-disallowed-here' => 'Vous ne pouvez pas remplacer ce fichier.',
# File reversion
'filerevert' => 'Rétablir $1',
'allpagesprev' => 'Précédent',
'allpagesnext' => 'Suivant',
'allpagessubmit' => 'Lister',
-'allpagesprefix' => 'Afficher les pages commençant par le préfixe :',
+'allpagesprefix' => 'Afficher les pages commençant par :',
'allpagesbadtitle' => 'Le titre de page indiqué est incorrect : il contient un préfixe inter-langue ou inter-wiki réservé, ou contient un ou plusieurs caractères inutilisables dans les titres.',
'allpages-bad-ns' => '{{SITENAME}} n’a pas d’espace de noms « $1 ».',
'allpages-hide-redirects' => 'Masquer les redirections',
'mailnologintext' => 'Vous devez être [[Special:UserLogin|identifié]] et avoir indiqué une adresse électronique valide dans vos [[Special:Preferences|préférences]] pour pouvoir envoyer des courriels à d’autres utilisateurs.',
'emailuser' => 'Lui envoyer un courriel',
'emailuser-title-target' => 'Envoyer un courriel à {{GENDER:$1|cet utilisateur|cette utilisatrice}}',
-'emailuser-title-notarget' => 'Envoyer un courriel à l’utilisateur',
-'emailpage' => 'Envoyer un courriel à l’utilisateur',
+'emailuser-title-notarget' => "Envoyer un courriel à l'utilisateur",
+'emailpage' => "Envoyer un courriel à l'utilisateur",
'emailpagetext' => 'Vous pouvez utiliser le formulaire ci-dessous pour envoyer un courriel à cet utilisateur.
L’adresse électronique que vous avez indiquée dans [[Special:Preferences|vos préférences]] apparaîtra dans le champ « Expéditeur » de votre message ; ainsi, le destinataire pourra vous répondre directement.',
'usermailererror' => 'Erreur dans l’objet du courriel :',
-'defemailsubject' => '{{SITENAME}} Courriel de l’utilisateur « $1 »',
+'defemailsubject' => "{{SITENAME}} Courriel de l'utilisateur « $1 »",
'usermaildisabled' => 'L’envoi de courriels entre utilisateurs est désactivé',
'usermaildisabledtext' => 'Vous ne pouvez pas envoyer de courriels à d’autres utilisateurs sur ce wiki',
'noemailtitle' => 'Aucune adresse de courriel',
'nowikiemailtext' => 'Cet utilisateur a choisi de ne pas recevoir de courriel de la part d’autres utilisateurs.',
'emailnotarget' => "Nom d'utilisateur du destinataire inexistant ou invalide.",
'emailtarget' => "Entrez le nom d'utilisateur du destinataire",
-'emailusername' => "Nom de l'utilisateur :",
+'emailusername' => 'Nom d’utilisateur :',
'emailusernamesubmit' => 'Soumettre',
'email-legend' => 'Envoyer un courriel à un autre utilisateur de {{SITENAME}}',
'emailfrom' => 'De :',
'emailccsubject' => 'Copie de votre message à $1 : $2',
'emailsent' => 'Courriel envoyé',
'emailsenttext' => 'Votre message a été envoyé par courriel.',
-'emailuserfooter' => 'Ce courriel a été envoyé par « $1 » à « $2 » par la fonction « Envoyer un courriel à l’utilisateur » de {{SITENAME}}.',
+'emailuserfooter' => "Ce courriel a été envoyé par « $1 » à « $2 » par la fonction « Envoyer un courriel à l'utilisateur » de {{SITENAME}}.",
# User Messenger
'usermessage-summary' => 'A laissé un message système.',
'undeletedrevisions' => '$1 {{PLURAL:$1|version restaurée|versions restaurées}}',
'undeletedrevisions-files' => '$1 version{{PLURAL:$1||s}} et $2 fichier{{PLURAL:$2||s}} restauré{{PLURAL:$2||s}}',
'undeletedfiles' => '$1 {{PLURAL:$1|fichier restauré|fichiers restaurés}}',
-'cannotundelete' => 'La restauration a échoué ;
-un autre utilisateur a probablement déjà restauré la page.',
+'cannotundelete' => 'Échec de la restauration:
+$1',
'undeletedpage' => "'''La page $1 a été restaurée.'''
Consultez le [[Special:Log/delete|journal des suppressions]] pour obtenir la liste des récentes suppressions et restaurations.",
'blanknamespace' => '(Principal)',
# Contributions
-'contributions' => 'Contributions de l’utilisateur',
+'contributions' => "Contributions de l'utilisateur",
'contributions-title' => 'Liste des contributions de l’utilisateur $1',
'mycontris' => 'Contributions',
'contribsub2' => 'Pour $1 ($2)',
'nocontribs' => 'Aucune modification correspondant à ces critères n’a été trouvée.',
'uctop' => '(dernière)',
'month' => 'À partir du mois (et précédents) :',
-'year' => 'À partir de l’année (et précédentes) :',
+'year' => "À partir de l'année (et précédentes) :",
'sp-contributions-newbies' => 'Ne montrer que les contributions des nouveaux utilisateurs',
'sp-contributions-newbies-sub' => 'Parmi les nouveaux comptes',
La dernière entrée du journal des blocages est indiquée ci-dessous à titre d’information :',
'sp-contributions-search' => 'Rechercher les contributions',
'sp-contributions-username' => 'Adresse IP ou nom d’utilisateur :',
-'sp-contributions-toponly' => 'Ne montrer que les articles dont je suis le dernier contributeur',
+'sp-contributions-toponly' => 'Ne montrer que les contributions qui sont les dernières des articles',
'sp-contributions-submit' => 'Rechercher',
# What links here
# Block/unblock
'autoblockid' => 'Blocage automatique #$1',
-'block' => 'Bloquer l’utilisateur',
-'unblock' => 'Débloquer l’utilisateur',
-'blockip' => 'Bloquer l’utilisateur',
-'blockip-title' => 'Bloquer l’utilisateur',
-'blockip-legend' => 'Bloquer l’utilisateur',
+'block' => "Bloquer l'utilisateur",
+'unblock' => "Débloquer l'utilisateur",
+'blockip' => "Bloquer l'utilisateur",
+'blockip-title' => "Bloquer l'utilisateur",
+'blockip-legend' => "Bloquer l'utilisateur",
'blockiptext' => 'Utilisez le formulaire ci-dessous pour bloquer l’accès aux modifications faites à partir d’une adresse IP spécifique ou d’un nom d’utilisateur.
Une telle mesure ne devrait être prise que pour prévenir le vandalisme et en accord avec les [[{{MediaWiki:Policy-url}}|règles internes]].
Donnez ci-dessous un motif précis (par exemple en citant les pages qui ont été vandalisées).',
-'ipadressorusername' => 'Adresse IP ou nom d’utilisateur :',
+'ipadressorusername' => "Adresse IP ou nom d'utilisateur :",
'ipbexpiry' => 'Durée avant expiration :',
'ipbreason' => 'Motif :',
'ipbreasonotherlist' => 'Autre motif',
'emailblock' => 'courriel bloqué',
'blocklist-nousertalk' => 'ne peut modifier sa propre page de discussion',
'ipblocklist-empty' => 'La liste des adresses IP bloquées est actuellement vide.',
-'ipblocklist-no-results' => 'L’adresse IP ou l’utilisateur demandé n’est pas bloqué.',
+'ipblocklist-no-results' => "L'adresse IP ou l'utilisateur demandé n'est pas bloqué.",
'blocklink' => 'bloquer',
'unblocklink' => 'débloquer',
'change-blocklink' => 'modifier le blocage',
'immobile-target-namespace-iw' => 'Les destinations interwikis ne sont pas une cible valide pour les déplacements.',
'immobile-source-page' => 'Cette page n’est pas renommable.',
'immobile-target-page' => 'Il n’est pas possible de renommer la page vers ce titre.',
+'bad-target-model' => 'La destination souhaitée utilise un autre modèle de contenu. Impossible de convertir de $1 vers $2.',
'imagenocrossnamespace' => 'Impossible de renommer un fichier vers un espace de noms autre que fichier.',
'nonfile-cannot-move-to-file' => "Impossible de renommer quelque chose d’autre qu'un fichier vers l’espace de noms fichier.",
'imagetypemismatch' => 'La nouvelle extension de ce fichier ne correspond pas à son type.',
# Attribution
'anonymous' => '{{PLURAL:$1|Utilisateur non enregistré|Utilisateurs non enregistrés}} sur {{SITENAME}}',
-'siteuser' => '{{GENDER:$2|l’utilisateur|l’utilisatrice|l’utilisateur}} $1 de {{SITENAME}}',
-'anonuser' => 'l’utilisateur anonyme $1 de {{SITENAME}}',
+'siteuser' => "{{GENDER:$2|l'utilisateur|l'utilisatrice|l'utilisateur}} $1 de {{SITENAME}}",
+'anonuser' => "l'utilisateur anonyme $1 de {{SITENAME}}",
'lastmodifiedatby' => 'Cette page a été modifiée pour la dernière fois le $1 à $2 par $3.',
'othercontribs' => 'Basé sur le travail de $1.',
'others' => 'autres',
# Info page
'pageinfo-title' => 'Informations pour « $1 »',
+'pageinfo-not-current' => 'Les informations peuvent uniquement être affichées pour la révision en cours.',
'pageinfo-header-basic' => 'Informations de base',
'pageinfo-header-edits' => 'Historique des modifications',
'pageinfo-header-restrictions' => 'Protection de la page',
* <span class="mw-specialpagecached">Pages spéciales seulement en cache (pourraient être désuètes).</span>',
'specialpages-group-maintenance' => 'Rapports de maintenance',
'specialpages-group-other' => 'Autres pages spéciales',
-'specialpages-group-login' => 'S’identifier / s’inscrire',
+'specialpages-group-login' => "S'identifier / s'inscrire",
'specialpages-group-changes' => 'Modifications récentes et journaux',
'specialpages-group-media' => 'Rapports et import de fichiers médias',
'specialpages-group-users' => 'Utilisateurs et droits rattachés',
'api-error-unknown-error' => 'Erreur interne : Quelque chose a mal tourné lors du versement de votre fichier.',
'api-error-unknown-warning' => 'Avertissement inconnu : $1',
'api-error-unknownerror' => 'Erreur inconnue : « $1 ».',
-'api-error-uploaddisabled' => 'Le versement est désactivé sur ce wiki.',
+'api-error-uploaddisabled' => 'Le téléversement est désactivé sur ce wiki.',
'api-error-verification-error' => 'Ce fichier peut être corrompu, ou son extension est incorrecte.',
# Durations
-'duration-seconds' => '$1 {{PLURAL:$1|seconde|secondes}}',
-'duration-minutes' => '$1 {{PLURAL:$1|minute|minutes}}',
-'duration-hours' => '$1 {{PLURAL:$1|heure|heures}}',
-'duration-days' => '$1 {{PLURAL:$1|jour|jours}}',
-'duration-weeks' => '$1 {{PLURAL:$1|semaine|semaines}}',
-'duration-years' => '$1 {{PLURAL:$1|année|années}}',
-'duration-decades' => '$1 {{PLURAL:$1|décennie|décennies}}',
-'duration-centuries' => '$1 {{PLURAL:$1|siècle|siècles}}',
-'duration-millennia' => '$1 {{PLURAL:$1|millénaire|millénaires}}',
+'duration-seconds' => '$1 seconde{{PLURAL:$1||s}}',
+'duration-minutes' => '$1 minute{{PLURAL:$1||s}}',
+'duration-hours' => '$1 heure{{PLURAL:$1||s}}',
+'duration-days' => '$1 jour{{PLURAL:$1||s}}',
+'duration-weeks' => '$1 semaine{{PLURAL:$1||s}}',
+'duration-years' => '$1 année{{PLURAL:$1||s}}',
+'duration-decades' => '$1 décennie{{PLURAL:$1||s}}',
+'duration-centuries' => '$1 siècle{{PLURAL:$1||s}}',
+'duration-millennia' => '$1 millénaire{{PLURAL:$1||s}}',
);
'qbbrowse' => 'Fâre dèfelar',
'qbedit' => 'Changiér',
'qbpageoptions' => 'Ceta pâge',
-'qbpageinfo' => 'Contèxto',
'qbmyoptions' => 'Mes pâges',
'qbspecialpages' => 'Pâges spèciâles',
'faq' => 'Quèstions sovent posâyes',
'qbbrowse' => 'Bleese',
'qbedit' => 'Änre',
'qbpageoptions' => 'Jüdeer sid',
-'qbpageinfo' => 'Sidedoote',
'qbmyoptions' => 'Min side',
'qbspecialpages' => 'Spetsjåålside',
'faq' => 'FAQ',
'qbbrowse' => '查看',
'qbedit' => '编写',
'qbpageoptions' => '个页',
-'qbpageinfo' => '个页信息',
'qbmyoptions' => '偶𠮶选项',
'qbspecialpages' => '特殊页',
'faq' => 'FAQ',
'qbbrowse' => '查看',
'qbedit' => '編寫',
'qbpageoptions' => '箇頁',
-'qbpageinfo' => '箇頁信息',
'qbmyoptions' => '我嗰頁面',
'qbspecialpages' => '特殊頁',
'faq' => 'FAQ',
* @file
*
* @author Alma
+ * @author Dferg
* @author Elisardojm
* @author Gallaecio
* @author Gustronico
* @author Lameiro
* @author Prevert
* @author Toliño
+ * @author Vivaelcelta
* @author Xosé
* @author לערי ריינהארט
*/
'qbbrowse' => 'Navegar',
'qbedit' => 'Editar',
'qbpageoptions' => 'Esta páxina',
-'qbpageinfo' => 'Contexto',
'qbmyoptions' => 'As miñas páxinas',
'qbspecialpages' => 'Páxinas especiais',
'faq' => 'Preguntas máis frecuentes',
'faqpage' => 'Project:FAQ',
# Vector skin
-'vector-action-addsection' => 'Engadir un comentario',
+'vector-action-addsection' => 'Nova sección',
'vector-action-delete' => 'Borrar',
'vector-action-move' => 'Mover',
'vector-action-protect' => 'Protexer',
'newmessagesdifflink' => 'diferenzas coa revisión anterior',
'youhavenewmessagesfromusers' => 'Ten $1 {{PLURAL:$3|doutro usuario|de $3 usuarios}} ($2).',
'youhavenewmessagesmanyusers' => 'Ten $1 de moitos usuarios ($2).',
-'newmessageslinkplural' => '{{PLURAL:$1|unha mensaxe nova|$1 mensaxes novas}}',
+'newmessageslinkplural' => '{{PLURAL:$1|unha mensaxe nova|mensaxes novas}}',
'newmessagesdifflinkplural' => '{{PLURAL:$1|última modificación|últimas modificacións}}',
'youhavenewmessagesmulti' => 'Ten mensaxes novas en $1',
'editsection' => 'editar',
'delete-hook-aborted' => 'O borrado foi abortado polo asociador.
Este non deu ningunha explicación.',
'badtitle' => 'Título incorrecto',
-'badtitletext' => 'O título da páxina pedida non era válido, estaba baleiro ou proviña dunha ligazón interlingua ou interwiki incorrecta.
-Pode conter un ou máis caracteres dos que non se poden empregar nos títulos.',
+'badtitletext' => 'O título da páxina pedida non era válido, estaba baleiro ou proviña dunha ligazón interlingüística ou interwiki incorrecta.
+Poida que conteña un ou máis caracteres dos que non se poden empregar nos títulos.',
'perfcached' => 'Esta información é da memoria caché e pode ser que non estea completamente actualizada. Hai un máximo de {{PLURAL:$1|$1 resultado dispoñible|$1 resultados dispoñibles}} na caché.',
'perfcachedts' => 'Esta información é da memoria caché. Última actualización: $2 ás $3. Hai un máximo de {{PLURAL:$4|$4 resultado dispoñible|$4 resultados dispoñibles}} na caché.',
'querypage-no-updates' => 'Neste momento están desactivadas as actualizacións nesta páxina. O seu contido non se modificará.',
Pode contactar con $1 ou con calquera outro [[{{MediaWiki:Grouppage-sysop}}|administrador]] para discutir este bloqueo.
-Teña en conta que non pode empregar "enviarlle un correo electrónico a este usuario" a non ser que dispoña dun enderezo electrónico válido rexistrado nas súas [[Special:Preferences|preferencias de usuario]] e e que o seu uso non fose bloqueado.
+Teña en conta que non pode empregar a característica "Enviar un correo electrónico a este usuario" a non ser que dispoña dun enderezo electrónico válido rexistrado nas súas [[Special:Preferences|preferencias de usuario]] e e que o seu uso non fose bloqueado.
O seu enderezo IP actual é $3 e o ID do bloqueo é #$5.
Por favor, inclúa eses datos nas consultas que faga.',
'edit-already-exists' => 'Non se pode crear a nova páxina.
Esta xa existe.',
'defaultmessagetext' => 'Texto predeterminado',
+'content-failed-to-parse' => 'Erro ao analizar o contido de "$2" para o modelo de $1: $3',
+'invalid-content-data' => 'Datos de contido inválidos',
+'content-not-allowed-here' => 'O contido "$1" non está permitido na páxina "[[$2]]"',
+
+# Content models
+'content-model-wikitext' => 'texto wiki',
+'content-model-text' => 'texto simple',
+'content-model-javascript' => 'JavaScript',
+'content-model-css' => 'CSS',
# Parser/template warnings
'expensive-parserfunction-warning' => "'''Aviso:''' Esta páxina contén demasiados analizadores de funcións de chamadas.
'shared-repo-from' => 'de $1',
'shared-repo' => 'repositorio compartido',
'filepage.css' => '/** O CSS que se coloque aquí será incluído na páxina de descrición do ficheiro, así como nos wikis de clientes estranxeiros */',
-'upload-disallowed-here' => 'Por desgraza, non pode sobrescribir esta imaxe.',
+'upload-disallowed-here' => 'Non pode sobrescribir este ficheiro.',
# File reversion
'filerevert' => 'Reverter $1',
'allpagesnext' => 'Seguinte',
'allpagessubmit' => 'Mostrar',
'allpagesprefix' => 'Mostrar as páxinas que comezan co prefixo:',
-'allpagesbadtitle' => 'O título dado á páxina non era válido ou contiña un prefixo inter-linguas ou inter-wikis. Pode que conteña un ou máis caracteres que non se poden empregar nos títulos.',
+'allpagesbadtitle' => 'O título dado á páxina non era válido ou tiña un prefixo interlingüístico ou interwiki.
+Poida que conteña un ou máis caracteres dos que non se poden empregar nos títulos.',
'allpages-bad-ns' => '{{SITENAME}} carece do espazo de nomes "$1".',
'allpages-hide-redirects' => 'Agochar as redireccións',
'undeletedrevisions' => '$1 {{PLURAL:$1|revisión restaurada|revisións restauradas}}',
'undeletedrevisions-files' => '$1 {{PLURAL:$1|revisión|revisións}} e $2 {{PLURAL:$2|ficheiro restaurado|ficheiros restaurados}}',
'undeletedfiles' => '$1 {{PLURAL:$1|ficheiro restaurado|ficheiros restaurados}}',
-'cannotundelete' => 'Non se restaurou a páxina porque alguén xa o fixo antes.',
+'cannotundelete' => 'Houbo un erro durante a restauración:
+$1',
'undeletedpage' => "'''A páxina \"\$1\" foi restaurada'''
Comprobe o [[Special:Log/delete|rexistro de borrados]] para ver as entradas recentes no rexistro de páxinas eliminadas e restauradas.",
'blocklogpage' => 'Rexistro de bloqueos',
'blocklog-showlog' => 'Este usuario xa foi bloqueado con anterioridade. Velaquí está o rexistro de bloqueos por se quere consultalo:',
'blocklog-showsuppresslog' => 'Este usuario xa foi bloqueado e agochado con anterioridade. Velaquí está o rexistro de supresións por se quere consultalo:',
-'blocklogentry' => 'bloqueou a "[[$1]]" cun tempo de duración de $2 $3',
+'blocklogentry' => 'bloqueou a [[$1]] cun tempo de duración de $2 $3',
'reblock-logentry' => 'cambiou as configuracións do bloqueo de "[[$1]]" cunha caducidade de $2 $3',
'blocklogtext' => 'Este é o rexistro das accións de bloqueo e desbloqueo de usuarios.
Non se listan os enderezos IP bloqueados automaticamente.
'immobile-target-namespace-iw' => 'A ligazón interwiki non é válida para o movemento da páxina.',
'immobile-source-page' => 'Esta páxina non se pode mover.',
'immobile-target-page' => 'Non se pode mover a ese título.',
+'bad-target-model' => 'O destino desexado utiliza un modelo de contido diferente. Non se pode facer a conversión entre $1 e $2.',
'imagenocrossnamespace' => 'Non se pode mover o ficheiro a un espazo de nomes que non o admite',
'nonfile-cannot-move-to-file' => 'Non se pode mover algo que non é un ficheiro ao espazo de nomes reservado aos ficheiros',
'imagetypemismatch' => 'A nova extensión do fiheiro non coincide co seu tipo',
# Info page
'pageinfo-title' => 'Información sobre "$1"',
+'pageinfo-not-current' => 'Unicamente se pode mostrar a información sobre a revisión actual.',
'pageinfo-header-basic' => 'Información básica',
'pageinfo-header-edits' => 'Historial de edicións',
'pageinfo-header-restrictions' => 'Protección da páxina',
'pageinfo-magic-words' => '{{PLURAL:$1|Palabra máxica|Palabras máxicas}} ($1)',
'pageinfo-hidden-categories' => '{{PLURAL:$1|Categoría agochada|Categorías agochadas}} ($1)',
'pageinfo-templates' => '{{PLURAL:$1|Modelo incluído|Modelos incluídos}} ($1)',
+'pageinfo-toolboxlink' => 'Información da páxina',
# Skin names
'skinname-standard' => 'Clásica',
# Scary transclusion
'scarytranscludedisabled' => '[A transclusión interwiki está desactivada]',
'scarytranscludefailed' => '[Fallou a busca do modelo "$1"]',
+'scarytranscludefailed-httpstatus' => '[Fallou a busca do modelo "$1": HTTP $2]',
'scarytranscludetoolong' => '[O enderezo URL é demasiado longo]',
# Delete conflict
'qbbrowse' => 'Ἀλάου',
'qbedit' => 'Μεταγράφειν',
'qbpageoptions' => 'Ἥδε ἡ δέλτος',
-'qbpageinfo' => 'Συγκείμενον',
'qbmyoptions' => 'Οἱ δέλτοι μου',
'qbspecialpages' => 'Εἰδικαὶ δέλτοι',
'faq' => 'Τὰ πολλάκις αἰτηθέντα',
'qbbrowse' => 'Blättre',
'qbedit' => 'Ändere',
'qbpageoptions' => 'Sytenoptione',
-'qbpageinfo' => 'Sytedate',
'qbmyoptions' => 'Ystellige',
'qbspecialpages' => 'Spezialsytene',
'faq' => 'Froge, wo vilmol gstellt wäre',
'edit-no-change' => 'Dyyni Bearbeitig isch ignoriert wore, wel kei Änderig am Täxt gmacht woren isch.',
'edit-already-exists' => 'Di nej Syte het nid chenne aaglait wäre, wel s si scho git.',
'defaultmessagetext' => 'Standardtext',
+'content-failed-to-parse' => 'Parse vum Inhalt $2 fir Modell $1 fählgschlaa: $3',
+'invalid-content-data' => 'Uugiltigi Inhaltsdate',
+'content-not-allowed-here' => 'Dr Inhalt „$1“ isch uf dr Syte [[$2]] nit erlaubt',
+
+# Content models
+'content-model-wikitext' => 'Wikitext',
+'content-model-text' => 'Klartext',
+'content-model-javascript' => 'JavaScript',
+'content-model-css' => 'CSS',
# Parser/template warnings
'expensive-parserfunction-warning' => 'Achtig: In däre Syte het s z vyyl Ufruef vu ufwändige Parserfunktione.
'undeletedrevisions' => '{{PLURAL:$1|ei Revision|$1 Revisione}} wider zruckgholt.',
'undeletedrevisions-files' => '{{PLURAL:$1|1 Version|$1 Versione}} un {{PLURAL:$2|1 Datei|$2 Dateie}} sin widerhärgstellt wore',
'undeletedfiles' => '{{PLURAL:$1|1 Datei isch|$1 Dateie sin}} widerhärgstellt wore',
-'cannotundelete' => 'Widerhärstellig isch nit gange; eber ander het villicht d Syte scho widerhärgstellt.',
+'cannotundelete' => 'D Widerhärstellig isch nit gange:
+
+$1',
'undeletedpage' => "'''„$1“''' isch widerhärgstellt wore.
Im [[Special:Log/delete|Lesch-Logbuech]] findsch e Ibersicht vu dr gleschte un widerhärgstellte Syte.",
'immobile-target-namespace-iw' => 'E Interwiki-Link isch kei gültigs Ziil für e Syteverschiebig.',
'immobile-source-page' => 'Die Syte cha nüt verschobe werde.',
'immobile-target-page' => 'Uf die Ziilsyte cha nüt verschobe werde.',
+'bad-target-model' => 'Di gwinsche Ziilsyte brucht e ander Inhaltsmodell. S Inhaltsmodell $1 cha nit in s Inhaltsmodell $2 umgwandlet wäre.',
'imagenocrossnamespace' => 'Dateie chönne nüt ussem {{ns:file}}-Namensruum use verschobe werde',
'nonfile-cannot-move-to-file' => 'Nit-Dateie chenne nit in dr Datei-Namensruum verschobe wäre',
'imagetypemismatch' => 'D nöii Dateierwiiterig passt nüt zu sym Typ',
'qbbrowse' => 'બ્રાઉઝ',
'qbedit' => 'ફેરફાર કરો',
'qbpageoptions' => 'આ પાનું',
-'qbpageinfo' => 'સંદર્ભ',
'qbmyoptions' => 'મારાં પાનાં',
'qbspecialpages' => 'ખાસ પાનાં',
'faq' => 'FAQ
તમે [[Special:Search/{{PAGENAME}}|આ શબ્દ]] ધરાવતાં અન્ય લેખો શોધી શકો છો, <span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} સંલગ્ન માહિતિ પત્રકોમાં શોધી શકો છો],
અથવા [{{fullurl:{{FULLPAGENAME}}|action=edit}} આ પાનામાં ફેરફાર કરી] માહિતિ ઉમેરવાનું શરૂ કરી શકો છો</span>.',
'noarticletext-nopermission' => 'આ પાનામાં હાલમાં કોઇ માહિતિ નથી.
-તમે [[Special:Search/{{PAGENAME}}|આ શબ્દ]] ધરાવતાં અન્ય લેખો શોધી શકો છો, <span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} સંલગ્ન માહિતિ પત્રકોમાં શોધી શકો છો],
-અથવા [{{fullurl:{{FULLPAGENAME}}|action=edit}} આ પાનામાં ફેરફાર કરી] માહિતિ ઉમેરવાનું શરૂ કરી શકો છો</span>.',
+તમે [[Special:Search/{{PAGENAME}}|આ શબ્દ]] ધરાવતાં અન્ય લેખો શોધી શકો છો, અથવા <span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} સંલગ્ન માહિતિ પત્રકોમાં શોધી શકો છો], પરંતુ તમને આ પાનું બનાવવાની મંજૂરી નથી.',
'userpage-userdoesnotexist' => 'સભ્ય ખાતું "<nowiki>$1</nowiki>"ની નોંધણીનથી થઈ.
શું તમે ખરેખર આ પાનાની રચના કે ફેરફાર કરવા માંગો છો',
'userpage-userdoesnotexist-view' => 'સભ્યના ખાતા $1 ની નોંધણી નથી થઈ',
'wantedfiles' => 'ઇચ્છિત ફાઈલો',
'wantedfiletext-cat' => 'નીચેની ફાઈલ વપરાઈ છે પણ તે વિહરમન નથી. ફાઈલ અહીં હોવા તેવી ફાઈલોને પણ પરદેશી રીપોસીટરીમાંથી ફાઈલો યાદીમાં જોઈ શકાય છે. આવા પુનરાવર્તનોને <del>struck out</del> કાઢી નાખવામાં આવશે.વધારામાં, અસ્તિત્વમાં નહોય તેવી ફાઈલધરાવતાં પાનાની યાદી [[:$1]].',
'wantedfiletext-nocat' => 'નીચેની ફાઈલ વપરાઈ છે પણ તે અસ્તિત્વમાં નથી. ફાઈલ અહીં હોવા તેવી ફાઈલોને પણ પરદેશી રીપોસીટરીમાંથી ફાઈલો યાદીમાં જોઈ શકાય છે. આવા પુનરાવર્તનોને <del>struck out</del> કાઢી નાખવામાં આવશે.',
-'wantedtemplates' => 'àª\9cà«\8bàª\88તા ઢાંચા',
+'wantedtemplates' => 'àª\87àª\9aà«\8dàª\9bિત ઢાંચા',
'mostlinked' => 'સૌથી વધુ કડીઓ દ્વારા જોડાયેલ પાનું',
'mostlinkedcategories' => 'સૌથી વધુ શ્રેણીઓ દ્વારા જોડાયેલ પાનું',
'mostlinkedtemplates' => 'સૌથી વધુ ઢાંચાઓ દ્વારા જોડાયેલ પાનું',
'mostcategories' => 'સૌથી વધુ શ્રેણીઓ ધરાવતાં પાનાં',
'mostimages' => 'સૌથી વધુ કડીઓ દ્વારા જોડાયેલી ફાઇલ',
+'mostinterwikis' => 'સૌથી વધુ આંતરવિકી કડીઓ ધરાવતાં પાના',
'mostrevisions' => 'સૌથી વધુ ફેરફાર થયેલા પાનાં',
'prefixindex' => 'પૂર્વાક્ષર સૂચિ',
'prefixindex-namespace' => 'શરૂઆતમાં ($1 namespace) ધરાવતા પાનાં',
'shortpages' => 'નાનાં પાનાં',
'longpages' => 'લાંબા પાનાઓ',
-'deadendpages' => 'લà«\87àª\96 સમાપà«\8dતિ પાના',
+'deadendpages' => 'મà«\83તાàª\82ત પાના',
'deadendpagestext' => 'નીચેના પાના {{SITENAME}}ના અન્ય પાનાને કડીઓ દ્વારા નથી જોડતાં.',
'protectedpages' => 'સંરક્ષિત પાનાઓ',
'protectedpages-indef' => 'ફક્ત અનિશ્ચિત સુરક્ષા ધરાવતા પાના',
'sp-deletedcontributions-contribs' => 'યોગદાન',
# Special:LinkSearch
-'linksearch' => 'બાહ્ય કડીઓ શોધ',
-'linksearch-pat' => 'શોધા આલેખ',
+'linksearch' => 'બાહ્ય કડીઓ શોધો',
+'linksearch-pat' => 'શોધ આલેખ',
'linksearch-ns' => 'નામાવકાશ:',
'linksearch-ok' => 'શોધ',
'linksearch-text' => '"*.wikipedia.org" જેવા વાઈલ્ડાકાર્ડ અહીં વાપર્યા હોઈ શકે છે.
'qbbrowse' => 'Chhà-khon',
'qbedit' => 'Phiên-siá',
'qbpageoptions' => 'Vùn-chông sién-hong',
-'qbpageinfo' => 'Vùn-chông chṳ̂-liau',
'qbmyoptions' => 'Ngô-ke sién-hong',
'qbspecialpages' => 'Thi̍t-sû hong-mien',
'faq' => 'Sòng-kien mun-thì kié-tap',
'qbbrowse' => 'דפדוף',
'qbedit' => 'עריכה',
'qbpageoptions' => 'אפשרויות דף',
-'qbpageinfo' => 'מידע על הדף',
'qbmyoptions' => 'האפשרויות שלי',
'qbspecialpages' => 'דפים מיוחדים',
'faq' => 'שאלות ותשובות',
'edit-already-exists' => 'לא ניתן ליצור דף חדש.
הוא כבר קיים.',
'defaultmessagetext' => 'טקסט ההודעה המקורי',
+'content-failed-to-parse' => 'פענוח $2 כתוכן מסוג $1 נכשל: $3',
+'invalid-content-data' => 'מידע שגוי על התוכן',
+'content-not-allowed-here' => 'תוכן מסוג "$1" אינו מותר בדף [[$2]]',
+
+# Content models
+'content-model-wikitext' => 'טקסט ויקי',
+'content-model-text' => 'טקסט פשוט',
+'content-model-javascript' => 'JavaScript',
+'content-model-css' => 'CSS',
# Parser/template warnings
'expensive-parserfunction-warning' => "'''אזהרה:''' דף זה כולל יותר מדי קריאות לפונקציות מפענח שגוזלות משאבים.
'shared-repo' => 'מקום איחסון משותף',
'shared-repo-name-wikimediacommons' => 'ויקישיתוף',
'filepage.css' => '/* הסגנונות הנכתבים כאן יוכללו בדף תיאור הקובץ, כולל באתרי ויקי זרים */',
-'upload-disallowed-here' => '×\9c×\9eר×\91×\94 ×\94צער, ×\90×\99×\9f ×\9c×\9a ×\94רש×\90×\94 ×\9c×\94×¢×\9c×\95ת ×\92רס×\94 ×\90×\97רת ש×\9c ×\94ת×\9e×\95× ×\94 ×\94×\96×\90ת.',
+'upload-disallowed-here' => '×\90×\99×\9f ×\91×\90פשר×\95ת×\9b×\9d ×\9c×\93ר×\95ס ×\90ת ×\94ק×\95×\91×¥ ×\94×\96×\94.',
# File reversion
'filerevert' => 'שחזור $1',
'undeletedrevisions' => '{{PLURAL:$1|שוחזרה גרסה אחת|שוחזרו $1 גרסאות}}',
'undeletedrevisions-files' => '{{PLURAL:$1|גרסה אחת|$1 גרסאות}} ו{{PLURAL:$2|קובץ אחד|־$2 קבצים}} שוחזרו',
'undeletedfiles' => '{{PLURAL:$1|שוחזר קובץ אחד|שוחזרו $1 קבצים}}',
-'cannotundelete' => 'השחזור נכשל; ייתכן שמישהו אחר כבר שחזר את הדף.',
+'cannotundelete' => 'השחזור נכשל:
+$1',
'undeletedpage' => "'''הדף $1 שוחזר בהצלחה.'''
ראו את [[Special:Log/delete|יומן המחיקות]] לרשימה של מחיקות ושחזורים אחרונים.",
'immobile-target-namespace-iw' => 'קישור בינוויקי אינו יעד תקין להעברת דף.',
'immobile-source-page' => 'דף זה אינו ניתן להעברה.',
'immobile-target-page' => 'לא ניתן להעביר אל כותרת יעד זו.',
+'bad-target-model' => 'היעד המבוקש משתמש בסוג תוכן שונה. לא ניתן להמיר $1 ל{{grammar:תחילית|$2}}.',
'imagenocrossnamespace' => 'לא ניתן להעביר קובץ למרחב שם אחר',
'nonfile-cannot-move-to-file' => 'לא ניתן להעביר דף שאינו קובץ למרחב קובץ',
'imagetypemismatch' => 'סיומת הקובץ החדשה אינה מתאימה לסוג הקובץ',
# Info page
'pageinfo-title' => 'מידע על "$1"',
+'pageinfo-not-current' => 'המידע יכול להיות מוצג רק עבור הגרסה הנוכחית.',
'pageinfo-header-basic' => 'מידע בסיסי',
'pageinfo-header-edits' => 'היסטוריית עריכות',
'pageinfo-header-restrictions' => 'הגנה על הדף',
# Scary transclusion
'scarytranscludedisabled' => '[הכללת דפים בין אתרים מבוטלת]',
-'scarytranscludefailed' => '[×\94×\9b×\9c×\9cת ×\94ת×\91× ×\99ת × ×\9bש×\9c×\94 עבור $1]',
-'scarytranscludefailed-httpstatus' => '[×\94×\9b×\9c×\9cת ×\94ת×\91× ×\99ת × ×\9bש×\9c×\94 עבור $1‏: HTTP $2]',
+'scarytranscludefailed' => '[×\90×\97×\96×\95ר ×\94ת×\91× ×\99ת × ×\9bש×\9c עבור $1]',
+'scarytranscludefailed-httpstatus' => '[×\90×\97×\96×\95ר ×\94ת×\91× ×\99ת × ×\9bש×\9c עבור $1‏: HTTP $2]',
'scarytranscludetoolong' => '[כתובת ה־URL ארוכה מדי]',
# Delete conflict
'qbbrowse' => 'ब्राउज़',
'qbedit' => 'बदलें',
'qbpageoptions' => 'यह पृष्ठ',
-'qbpageinfo' => 'पृष्ठ जानकारी',
'qbmyoptions' => 'मेरे पृष्ठ',
'qbspecialpages' => 'विशेष पृष्ठ',
'faq' => 'बहुधा पूछित प्रश्न',
'qbbrowse' => 'Browse karo',
'qbedit' => 'Badlo',
'qbpageoptions' => 'Ii panna',
-'qbpageinfo' => 'Vishay',
'qbmyoptions' => 'Hamar panna',
'qbspecialpages' => 'Khaas panna',
'faq' => 'Sab time puchhe waala sawal',
'vector-action-protect' => 'Bachao',
'vector-action-undelete' => 'Pahile jaise karo',
'vector-action-unprotect' => 'Surakchha ke badlo',
-'vector-simplesearch-preference' => 'Aur achchhaa se khoje salah do (Khaali vector skin)',
+'vector-simplesearch-preference' => 'Aur achchhaa se khoje ke salah do (Khaali vector skin)',
'vector-view-create' => 'Banao',
'vector-view-edit' => 'Badlo',
'vector-view-history' => 'Itihaas dekho',
'youhavenewmessages' => 'Aapke pass hai $1 ($2).',
'newmessageslink' => 'nawaa khabar',
'newmessagesdifflink' => 'pahile waala badlao',
+'youhavenewmessagesfromusers' => 'Aap ke lage {{PLURAL:$3|duusra sadasya|$3 sadasya}} ke lage se $1 hae ($2).',
+'youhavenewmessagesmanyusers' => 'Aap ke lage dher sadasya se $1 hae ($2).',
+'newmessageslinkplural' => '{{PLURAL:$1|ek nawaa sandes|nawaa sandes}}',
+'newmessagesdifflinkplural' => 'pichhla {{PLURAL:$1|badlao}}',
'youhavenewmessagesmulti' => 'Aap ke khatir $1 pe sandes hai',
'editsection' => 'badlo',
'editold' => 'badlao',
'error' => 'Galti',
'databaseerror' => 'Database me galti hai',
'dberrortext' => 'Database ke khoj me syntax error hoe gais hae.
-Saait software me bug hoi.
+Iske matlab ii hoe sake hae ki saait software me bug hoi.
Pahile waala database ke khoj ke kosis rahaa:
-<blockquote><tt>$1</tt></blockquote>
-"<tt>$2</tt>" function ke bhitar se.
-Database ke galti sandes rahaa "<tt>$3: $4</tt>".',
+<blockquote><code>$1</code></blockquote>
+"<code>$2</code>" function ke bhitar se.
+Database ke galti sandes rahaa "<samp>$3: $4</samp>".',
'dberrortextcl' => 'Database ke khoj me syntax error hoe gais hae.
Pahile waala database ke khoj ke kosis rahaa:
"$1"
'protectedpagetext' => 'Ii panna ke badlao ke roke ke khatir band kar dewa gais hai.',
'viewsourcetext' => 'Aap ii panna ke source ke dekhe aur nakal utare kare sakta hai:',
'viewyourtext' => "Aap '''aapan badlao''' ke source ke dekhe aur copy kare saktaa hae",
-'protectedinterface' => 'Ii panna software ke interface text dewe hai, aur iske barbaadi se roke ke khatir band kar dewa gais hai.',
+'protectedinterface' => 'Ii panna, ii wiki ke khatir, software ke interface text dewe hai, aur iske barbaadi se roke ke khatir band kar dewa gais hai.
+Sab wiki me anuwaad ke jorre nai to badle ke khatir, meharbaani kar ke [//translatewiki.net/ translatewiki.net], the MediaWiki localisation project ke kaam me laao.',
'editinginterface' => "'''Chetawani:''' Aap ek panna ke badaltaa hai jon ki software ke interface text dewe hae.
-Ii panna me badlao ke asar duusra sadasya ke interface ke bhi hoi.
-Translation khatir [//translatewiki.net/wiki/Main_Page?setlang=en translatewiki.net], the MediaWiki localisation project, ke kaam me lao.",
+Ii panna me badlao ke asar duusra sadasya ke interface pe bhi hoi.
+Translation khatir [//translatewiki.net/ translatewiki.net], the MediaWiki localisation project, ke kaam me lao.",
'sqlhidden' => '(SQL query lukawal hai)',
'cascadeprotected' => 'Ii panna ke badlao se bachawa gais hai, kahe ki iske {{PLURAL:$1|panna, jon ki|panna, jon ki}} surakchhit hae "cascading" option turned on ke saathe me rakkhaa gais hai:
$2',
'remembermypassword' => 'Ii computer pe hamaar login yaad rakho (jaada se jaada $1 {{PLURAL:$1|din|din}} talak)',
'securelogin-stick-https' => 'Login kare ke baad HTTPS se connected raho',
'yourdomainname' => 'Aap ke domain:',
+'password-change-forbidden' => 'Aap ii wiki me password nai badle saktaa hae.',
'externaldberror' => 'Koi bahaari database authentication error hai, nai to aap ke bahaari account badle ke adhikar nai hai.',
'login' => 'Log in karo',
'nav-login-createaccount' => 'Log in karo/ nawaa account banao',
Aap saktaa hai [[Special:Search/{{PAGENAME}}|ii panna ke title khoje]] duusra panna me,
<span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} search the related logs],
nai to [{{fullurl:{{FULLPAGENAME}}|action=edit}} ii panna ke badlo]</span>.',
-'noarticletext-nopermission' => 'Abhi ii panna pe koi chij likha nai hae.
+'noarticletext-nopermission' => 'Abhi ii panna me koi chij likha nai hae.
Aap sakta hae [[Special:Search/{{PAGENAME}}|ii panna ke title ke khoje]] duusra panna me,
-nai to <span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} search the related logs]</span>.',
+nai to <span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} search the related logs]</span>, lekin aap ke ii panna ke banae ke ijaaja tnai hae.',
+'missing-revision' => 'Panna "{{PAGENAME}}" me #$1 badlao nai hae.
+Iske kaaran ii hoe sake hae ki ek mitawa gais panna se link karaa jaawe hae.
+Iske baare me aur jaankari [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} deletion log] me paawa jaae sake hae.',
'userpage-userdoesnotexist' => 'User account "<nowiki>$1</nowiki>" abi registered nai hai.
Check karo ki Ii panna ke aap banae/badle mangta hai.',
'userpage-userdoesnotexist-view' => 'User account "$1" abhi register nai karaa gais hae',
'blocked-notice-logextract' => 'Ii sadasya ke abhi rok dewa gais hae.
Sab se nawaa block log entry, aap ke reference ke khatir, niche dewa gais hae:',
'clearyourcache' => "'''Note:''' - Save kare ke baad, aap ke sait browser ke cache ke bypass kare ke parri badlao ke dekhe khatir.
-* '''Mozilla / Firefox / Safari:''' ''Shift'' ke dabae ke ''Reload,'' pe click karo, nai to chaahe ''Ctrl-F5'' nai to ''Ctrl-R'' (''Command-R'' Mac pe)
-* '''Google Chrome:''' ''Ctrl-Shift-R'' dabao (''Command-Shift-R'' Mac pe)
-* '''Internet Explorer:''' ''Ctrl'' dabae ke ''Refresh'' pe click karo, nai to ''Ctrl-F5'' dabao
-* '''Konqueror: ''' ''Reload'' click karo nai to ''F5 dabao;''
-* '''Opera:''' ''Tools → Preferences'' me se cache ke safaa karo",
+* '''Firefox / Safari:''' me ''Shift'' ke dabae ke ''Reload,'' pe click karo, nai to chaahe ''Ctrl-F5'' nai to ''Ctrl-R'' (''⌘-R'' Mac pe)
+* '''Google Chrome:''' me ''Ctrl-Shift-R'' dabao (''⌘-Shift-R'' Mac pe)
+* '''Internet Explorer:''' me ''Ctrl'' dabae ke ''Refresh'' pe click karo, nai to ''Ctrl-F5'' dabao",
'usercssyoucanpreview' => "'''Salah:''' Bachae se pahile \"{{int:showpreview}}\" button ke kaam me laae ke aapan nawaa CSS ke test karo.",
'userjsyoucanpreview' => "'''Salah:''' Bachae se pahile \"{{int:showpreview}}\" button ke kaam me laae ke aapan nawaa JavaScript ke test karo.",
'usercsspreview' => "'''Yaad rakhna ki aap khali aapan CSS ke jhalak dekhta hai.
'edit-already-exists' => 'Nawaa panna nai banae sakaa hai.
Ii naam ke panna abhi hai.',
'defaultmessagetext' => 'Default message text',
+'content-failed-to-parse' => '$1 model ke khatir $2 ke parse nai kare sakaa hae: $3',
+'invalid-content-data' => 'Panna me likha gais chij right nai hae',
+'content-not-allowed-here' => 'Panna [[$2]] me "$1" likhe ke ijaajat nai hae',
# Parser/template warnings
'expensive-parserfunction-warning' => "'''Chetauni''': Ii panna me bahut jaada expensive parser function calls hai.
'expansion-depth-exceeded-warning' => 'Panna expansion depth ke exceed karis hae',
'parser-unstrip-loop-warning' => 'Unstrip loop ke pawa gai shae',
'parser-unstrip-recursion-limit' => 'Unstrip recursion limit ke exceed karaa gais hae ($1)',
+'converter-manual-rule-error' => 'Bhasa ke anuwaad kare waala niyam me galti hae',
# "Undo" feature
'undo-success' => 'Ii badlao ke pahile jaise karaa jaae sake hai.
Meharbani ka ke logs ke check karo.',
'revdelete-only-restricted' => 'Jon chij aap $2, $1 ke lukae mangta rahaa me galti hoe gais hae: aap administrator log se koi chij lukae nai saktaa hae bina duursa dekhe waala option ke chune.',
'revdelete-reason-dropdown' => '*Mitae ke jaada kar ke kaaran
-** Bina chhape ke adikar se
+** Bina chhape ke adikar nai
** Aapan baare me fajuul jankari
+** Kharaab sadasya ke naam
** Ninda kare waala jankari',
'revdelete-otherreason' => 'Duusra/aur kaaran:',
'revdelete-reasonotherlist' => 'Duusra kaaran',
'timezoneregion-indian' => 'Indian Ocean',
'timezoneregion-pacific' => 'Pacific Ocean',
'allowemail' => 'Aur sadasya se e-mail enable karo',
-'prefs-searchoptions' => 'Khoje ke option',
+'prefs-searchoptions' => 'Khojo',
'prefs-namespaces' => 'Naam:',
'defaultns' => 'Default se ii namespaces me khojo:',
'default' => 'baaki',
'right-writeapi' => 'Likhe waala API ke kaam me lawa jaawe hae',
'right-delete' => 'Panna ke mitao',
'right-bigdelete' => 'Barraa itihaas waala panna ke mitao',
+'right-deletelogentry' => 'Mitawa aur khola gais panna ke baare me log entires',
'right-deleterevision' => 'Panna ke khaas badlao ke mitao nai to bachao',
'right-deletedhistory' => 'Mitawa gais itihass ke entry ke binaa saathe waala text ke dekho',
'right-deletedtext' => 'Mitawa gais text aur mitawa gais badlao ke biich waala badlao ke dekho',
'backend-fail-internal' => 'Storage backend "$1" me ek unknown error hoe gais hae.',
'backend-fail-contenttype' => 'Ii nai pataa lagae sakaa hae ki "$1" me bachae ke khaatir file kon rakam ke hae.',
'backend-fail-batchsize' => 'Storage backend ke $1 file {{PLURAL:$1|operation|operations}} ke ek batch ke dewa gais hae ; limit $2 {{PLURAL:$2|operation|operation}} hae.',
-'backend-fail-usable' => 'File $1 ke nai likhe sakaa hae kaahe ki iske khatir jaruri ijajat nai hae, nai to directories/containers nai hae.',
+'backend-fail-usable' => 'File $1 me nai likhe, nai to nai parrhe, sakaa hae kaahe ki iske khatir jaruri ijajat nai hae, nai to directories/containers nai hae.',
# File journal errors
'filejournal-fail-dbconnect' => 'Storage backend "$1" ke khatir journal database se nai jorre sakaa hae.',
'disambiguations' => 'Garrbarri ke sudhare waala panna',
'disambiguationspage' => 'Template:disambig',
'disambiguations-text' => "Niche ke panna '''disambiguation panna''' se link hoe hai.
-They should link to the appropriate topic instead.<br />
-A page is treated as disambiguation page if it uses a template which is linked from [[MediaWiki:Disambiguationspage]]",
+Saait isse aur achchha panna se link hoi. <br />
+Ek panna ke disambiguation panna maana jaae hae jab ki ii ek template ke kaam me laae hae jon ki [[MediaWiki:Disambiguationspage]] se link hoe hae.",
'doubleredirects' => 'Dugna redirects',
'doubleredirectstext' => 'Ii panna uu panna ke suchi de hai jon ki duusra redirect panna pe redirect kare hai.
'qbbrowse' => 'Pregledaj',
'qbedit' => 'Uredi',
'qbpageoptions' => 'Postavke stranice',
-'qbpageinfo' => 'O stranici',
'qbmyoptions' => 'Moje stranice',
'qbspecialpages' => 'Posebne stranice',
'faq' => 'Najčešća pitanja',
'qbbrowse' => 'Přepytować',
'qbedit' => 'Wobdźěłać',
'qbpageoptions' => 'Tuta strona',
-'qbpageinfo' => 'Kontekst',
'qbmyoptions' => 'Moje strony',
'qbspecialpages' => 'Specialne strony',
'faq' => 'Husto stajene prašenja (FAQ)',
'edit-already-exists' => 'Njebě móžno nowu stronu wutworić.
Eksistuje hižo.',
'defaultmessagetext' => 'Standardny tekst zdźělenki',
+'content-failed-to-parse' => 'Parsowanje wobsaha $2 za model $1 je so njeporadźiło: $3',
+'invalid-content-data' => 'Njepłaćiwe wobsahowe daty',
+'content-not-allowed-here' => 'Wobsah "$1" njeje na stronje [[$2]] dowoleny',
+
+# Content models
+'content-model-wikitext' => 'wikitekst',
+'content-model-text' => 'luty tekst',
+'content-model-javascript' => 'JavaScript',
+'content-model-css' => 'CSS',
# Parser/template warnings
'expensive-parserfunction-warning' => 'Warnowanje: Tuta strona wobsahuje přewjele parserowych wołanjow.
'undeletedrevisions' => '$1 {{PLURAL:$1|wersija|wersiji|wersije|wersijow}} {{PLURAL:$1|wobnowjena|wobnowjenej|wobnowjene|wobnowjene}}',
'undeletedrevisions-files' => '$1 {{PLURAL:$1|wersija|wersiji|wersije|wersijow}} a $2 {{PLURAL:$2|dataja|dataji|dataje|datajow}} {{PLURAL:$2|wobnowjena|wobnowjenej|wobnowjene|wobnowjene}}',
'undeletedfiles' => '$1 {{PLURAL:$1|dataja|dataji|dataje|datajow}} {{PLURAL:$1|wobnowjena|wobnowjenej|wobnowjene|wobnowjene}}.',
-'cannotundelete' => 'Wobnowjenje zwrěšćiło; něchtó druhi je stronu prjedy wobnowił.',
+'cannotundelete' => 'Wobnowjenje zwrěšćiło:
+$1',
'undeletedpage' => "'''Strona $1 bu z wuspěchom wobnowjena.'''
Hlej [[Special:Log/delete|protokol]] za lisćinu aktualnych wušmórnjenjow a wobnowjenjow.",
'immobile-target-namespace-iw' => 'Interwiki-wotkaz njeje płaćiwy cil za přesunjenja stronow.',
'immobile-source-page' => 'Strona njeda so přesunyć.',
'immobile-target-page' => 'Njemóžno do teje ciloweje strony přesunyć.',
+'bad-target-model' => 'Požadany cil wužiwa druhi wobsahowy model. $1 njeda so do $2 konwertować.',
'imagenocrossnamespace' => 'Wobraz njeda so do druheho mjenoweho ruma hač wobraz přesunyć',
'nonfile-cannot-move-to-file' => 'Njedataje njedadźa so do datajoweho mjenoweho ruma přesunyć',
'imagetypemismatch' => 'Nowa dataja swojemu typej njewotpowěduje',
'qbbrowse' => 'Böngészés',
'qbedit' => 'Szerkesztés',
'qbpageoptions' => 'Lapbeállítások',
-'qbpageinfo' => 'Lapinformáció',
'qbmyoptions' => 'Lapjaim',
'qbspecialpages' => 'Speciális lapok',
'faq' => 'GyIK',
'vector-action-protect' => 'Lapvédelem',
'vector-action-undelete' => 'Visszaállítás',
'vector-action-unprotect' => 'Védelem módosítása',
-'vector-simplesearch-preference' => 'Továbbfejlesztett keresési javaslatok engedélyezése (csak Vector felületen)',
+'vector-simplesearch-preference' => 'Egyszerűsített keresési sáv engedélyezése (csak Vector felületen)',
'vector-view-create' => 'Létrehozás',
'vector-view-edit' => 'Szerkesztés',
'vector-view-history' => 'Laptörténet',
'youhavenewmessages' => '$1 a vitalapodon! ($2 külön is megtekintheted.)',
'newmessageslink' => 'új üzenet vár',
'newmessagesdifflink' => 'az utolsó üzenetet',
+'youhavenewmessagesfromusers' => '$1 a vitalapodon {{PLURAL:$3|egy|$3}} szerkesztőtől! ($2 külön is megtekintheted.)',
'youhavenewmessagesmanyusers' => '$1ed van több szerkesztőtől ($2).',
'newmessageslinkplural' => '{{PLURAL:$1|Új üzenet vár|Új üzenetek várnak}}',
'newmessagesdifflinkplural' => 'Az utolsó {{PLURAL:$1|változtatást|változtatásokat}}',
'cannotdelete' => 'A(z) $1 lapot vagy fájlt nem lehet törölni.
Talán már valaki más törölte.',
'cannotdelete-title' => 'Nem lehet törölni a(z) „$1” lapot',
+'delete-hook-aborted' => 'A törlés meg lett szakítva egy hook által.
+Nem lett magyarázat csatolva.',
'badtitle' => 'Hibás cím',
'badtitletext' => 'A kért oldal címe érvénytelen, üres, vagy rosszul hivatkozott nyelvközi vagy wikiközi cím volt. Olyan karaktereket is tartalmazhatott, melyek a címekben nem használhatóak.',
'perfcached' => "Az alábbi adatok gyorsítótárból (''cache''-ből) származnak, és ezért lehetséges, hogy nem a legfrissebb változatot mutatják. Legfeljebb {{PLURAL:$1|egy|$1 }} eredmény áll rendelkezésre a gyorsítótárban.",
'timezoneregion-indian' => 'Indiai-óceán',
'timezoneregion-pacific' => 'Csendes-óceán',
'allowemail' => 'E-mail engedélyezése más szerkesztőktől',
-'prefs-searchoptions' => 'A keresés beállításai',
+'prefs-searchoptions' => 'Keresés',
'prefs-namespaces' => 'Névterek',
'defaultns' => 'Egyébként a következő névterekben keressen:',
'default' => 'alapértelmezett',
'right-writeapi' => 'a szerkesztő-API használata',
'right-delete' => 'lapok törlése',
'right-bigdelete' => 'nagy történettel rendelkező fájlok törlése',
+'right-deletelogentry' => 'bizonyos napló bejegyzések törlése és visszaállítása',
'right-deleterevision' => 'lapok adott változatainak törlése és helyreállítása',
'right-deletedhistory' => 'törölt lapváltozatok megtekintése, a szövegük nélkül',
'right-deletedtext' => 'törölt változatok szövegének és a változatok közötti eltérés megtekintése',
# Recent changes
'nchanges' => '{{PLURAL:$1|egy|$1}} változtatás',
'recentchanges' => 'Friss változtatások',
-'recentchanges-legend' => 'A friss változások beállításai',
+'recentchanges-legend' => 'A friss változtatások beállításai',
'recentchanges-summary' => 'Ezen a lapon a wikiben történt legutóbbi fejleményeket lehet nyomon követni.',
'recentchanges-feed-description' => 'Kövesd a wiki friss változtatásait ezzel a hírcsatornával.',
'recentchanges-label-newpage' => 'Ezzel a szerkesztéssel egy új lap jött létre',
'recentchanges-label-bot' => 'Ezt a szerkesztést egy bot hajtotta végre',
'recentchanges-label-unpatrolled' => 'Ezt a szerkesztést még nem ellenőrizték',
'rcnote' => "Alább az utolsó '''{{PLURAL:$2|egy|$2}}''' nap utolsó '''{{PLURAL:$1|egy|$1}}''' változtatása látható. A lap generálásának időpontja $4, $5.",
-'rcnotefrom' => 'Alább a <b>$2</b> óta történt változások láthatóak (<b>$1</b> db).',
-'rclistfrom' => '$1 után történt változások megtekintése',
+'rcnotefrom' => 'Alább a <b>$2</b> óta történt változtatások láthatóak (<b>$1</b> db).',
+'rclistfrom' => '$1 után történt változtatások megtekintése',
'rcshowhideminor' => 'apró szerkesztések $1',
'rcshowhidebots' => 'botok szerkesztéseinek $1',
'rcshowhideliu' => 'bejelentkezett felhasználók szerkesztéseinek $1',
'recentchangeslinked-feed' => 'Kapcsolódó változtatások',
'recentchangeslinked-toolbox' => 'Kapcsolódó változtatások',
'recentchangeslinked-title' => 'A(z) $1 laphoz kapcsolódó változtatások',
-'recentchangeslinked-noresult' => 'A megadott időtartam alatt nem történt változás a kapcsolódó lapokon.',
+'recentchangeslinked-noresult' => 'A megadott időtartam alatt nem történt változtatás a kapcsolódó lapokon.',
'recentchangeslinked-summary' => "Alább azon lapoknak a legutóbbi változtatásai láthatóak, amelyekre hivatkozik egy megadott lap (vagy tagjai a megadott kategóriának).
A [[Special:Watchlist|figyelőlistádon]] szereplő lapok '''félkövérrel''' vannak jelölve.",
'recentchangeslinked-page' => 'Lap neve:',
# Miscellaneous special pages
'nbytes' => '{{PLURAL:$1|egy|$1}} bájt',
'ncategories' => '{{PLURAL:$1|egy|$1}} kategória',
+'ninterwikis' => '{{PLURAL:$1|egy|$1}} interwiki',
'nlinks' => '{{PLURAL:$1|egy|$1}} hivatkozás',
'nmembers' => '{{PLURAL:$1|egy|$1}} elem',
'nrevisions' => '{{PLURAL:$1|egy|$1}} változat',
'mostlinkedtemplates' => 'Legtöbbet hivatkozott sablonok',
'mostcategories' => 'Legtöbb kategóriába tartozó lapok',
'mostimages' => 'Legtöbbet hivatkozott fájlok',
+'mostinterwikis' => 'Legtöbb interwikit tartalmazó lapok',
'mostrevisions' => 'Legtöbbet szerkesztett lapok',
'prefixindex' => 'Keresés előtag szerint',
'prefixindex-namespace' => 'Összes lap adott előtaggal ($1 névtér)',
'undeletedrevisions' => '{{PLURAL:$1|egy|$1}} változat helyreállítva',
'undeletedrevisions-files' => '{{PLURAL:$1|egy|$1}} változat és {{PLURAL:$2|egy|$2}} fájl visszaállítva',
'undeletedfiles' => '{{PLURAL:$1|egy|$1}} fájl visszaállítva',
-'cannotundelete' => 'Nem lehet a lapot visszaállítani; lehet, hogy azt már valaki visszaállította.',
+'cannotundelete' => 'Lap visszaállítása sikertelen: $1',
'undeletedpage' => "'''$1 helyreállítva'''
Lásd a [[Special:Log/delete|törlési naplót]] a legutóbbi törlések és helyreállítások listájához.",
'import-interwiki-templates' => 'Az összes sablon hozzáadása',
'import-interwiki-submit' => 'Importálás',
'import-interwiki-namespace' => 'Célnévtér:',
+'import-interwiki-rootpage' => 'Cél gyökér lap (opcionális):',
'import-upload-filename' => 'Fájlnév:',
'import-comment' => 'Megjegyzés:',
'importtext' => 'Exportáld a fájlt a forráswikiből az [[Special:Export|exportáló eszköz]] segítségével.
'import-error-interwiki' => '„$1” lap nem került importálásra, mert a név külső hivatkozásokra van fenntartva (interwiki).',
'import-error-special' => '„$1” lap nem került importálásra, mert olyan speciális névtérbe tartozik, amelyen nem engedélyezettek a lapok.',
'import-error-invalid' => '„$1” lap nem került importálásra, mert a neve nem érvényes.',
+'import-options-wrong' => 'Rossz {{PLURAL:$2|opció|opciók}}: <nowiki>$1</nowiki>',
+'import-rootpage-invalid' => 'A megadott gyökér oldal címe érvénytelen.',
# Import log
'importlogpage' => 'Importnapló',
# Info page
'pageinfo-title' => 'Információk a(z) „$1” lapról',
+'pageinfo-header-basic' => 'Alapinformációk',
'pageinfo-header-edits' => 'Szerkesztések története',
+'pageinfo-header-restrictions' => 'Lapvédelem',
+'pageinfo-header-properties' => 'Lap tulajdonságok',
+'pageinfo-display-title' => 'Megjelenített cím',
+'pageinfo-default-sort' => 'Alapértelmezett rendezési kulcs',
+'pageinfo-length' => 'Lap hossza (bájtokban)',
+'pageinfo-article-id' => 'Lapazonosító',
+'pageinfo-robot-policy' => 'Kereső motor státusz',
+'pageinfo-robot-index' => 'Indexelhető',
+'pageinfo-robot-noindex' => 'Nem indexelhető',
'pageinfo-views' => 'Megtekintések száma',
'pageinfo-watchers' => 'Figyelők száma',
+'pageinfo-redirects-name' => 'Átirányítások erre a lapra',
+'pageinfo-subpages-name' => 'Az lap allapjai',
+'pageinfo-subpages-value' => '$1 ($2 {{PLURAL:$2|átirányítás}}; $3 {{PLURAL:$3|nem átirányítás}})',
+'pageinfo-firstuser' => 'Lap létrehozója',
+'pageinfo-firsttime' => 'A lap létrehozás ideje',
+'pageinfo-lastuser' => 'Utolsó szerkesztő',
+'pageinfo-lasttime' => 'Az utolsó szerkesztés ideje',
'pageinfo-edits' => 'Szerkesztések teljes száma',
'pageinfo-authors' => 'Egyedi szerkesztők teljes száma',
+'pageinfo-recent-edits' => 'Friss változtatások száma (elmúlt $1 alatt)',
+'pageinfo-recent-authors' => 'Friss változtatások szerkesztőinek száma',
+'pageinfo-magic-words' => 'Varázs{{PLURAL:$1|szó|szavak}} ($1)',
+'pageinfo-hidden-categories' => 'Rejtett {{PLURAL:$1|kategória|kategóriák}} ($1)',
+'pageinfo-templates' => 'Felhasznált {{PLURAL:$1|sablon|sablonok}} ($1)',
# Skin names
'skinname-standard' => 'Klasszikus',
'file-info-size-pages' => '$1 × $2 képpont, fájlméret: $3, MIME típus: $4, $5 oldal',
'file-nohires' => 'Nem érhető el nagyobb felbontású változat.',
'svg-long-desc' => 'SVG fájl, névlegesen $1 × $2 képpont, fájlméret: $3',
+'svg-long-desc-animated' => 'Animált SVG fájl, névlegesen $1 × $2 képpont, fájlméret: $3',
'show-big-image' => 'A kép nagyfelbontású változata',
'show-big-image-preview' => 'Az előnézet mérete: $1',
'show-big-image-other' => 'További {{PLURAL:$2|felbontás|felbontások}}: $1.',
'file-info-png-looped' => 'ismétlődik',
'file-info-png-repeat' => 'lejátszva {{PLURAL:$1|egy|$1}} alkalommal',
'file-info-png-frames' => '{{PLURAL:$1|egy|$1}} képkocka',
+'file-no-thumb-animation' => "'''Megjegyzés: technikai korlátok miatt a fájl bélyegképe nem lesz animált.'''",
+'file-no-thumb-animation-gif' => "'''Megjegyzés: technikai korlátok miatt a nagy felbontású GIF képekből készített bélyegkép nem lesz animált.'''",
# Special:NewFiles
'newimages' => 'Új fájlok galériája',
'qbbrowse' => 'Թերթել',
'qbedit' => 'Խմբագրել',
'qbpageoptions' => 'Այս էջը',
-'qbpageinfo' => 'Հոդվածի մասին',
'qbmyoptions' => 'Իմ էջերը',
'qbspecialpages' => 'Սպասարկող էջեր',
'faq' => 'ՀՏՀ',
'qbbrowse' => 'Foliar',
'qbedit' => 'Modificar',
'qbpageoptions' => 'Iste pagina',
-'qbpageinfo' => 'Contexto',
'qbmyoptions' => 'Mi paginas',
'qbspecialpages' => 'Paginas special',
'faq' => 'FAQ',
'vector-action-protect' => 'Proteger',
'vector-action-undelete' => 'Restaurar',
'vector-action-unprotect' => 'Cambiar protection',
-'vector-simplesearch-preference' => 'Activar le suggestiones de recerca meliorate (solmente in apparentia Vector)',
+'vector-simplesearch-preference' => 'Activar le barra de recerca simplificate (solmente in apparentia Vector)',
'vector-view-create' => 'Crear',
'vector-view-edit' => 'Modificar',
'vector-view-history' => 'Vider historia',
'edit-already-exists' => 'Non poteva crear un nove pagina.
Illo existe ja.',
'defaultmessagetext' => 'Texto predefinite del message',
+'content-failed-to-parse' => 'Impossibile processar le contento $2 pro le modello $1: $3',
# Parser/template warnings
'expensive-parserfunction-warning' => 'Attention: Iste pagina contine troppo de appellos costose al functiones del analysator syntactic.
'qbbrowse' => 'Navigasi',
'qbedit' => 'Sunting',
'qbpageoptions' => 'Halaman ini',
-'qbpageinfo' => 'Konteks halaman',
'qbmyoptions' => 'Halaman saya',
'qbspecialpages' => 'Halaman istimewa',
'faq' => 'FAQ',
'tog-numberheadings' => 'Automatiko a pabilangan dagiti paulo',
'tog-showtoolbar' => 'Ipakita ti ramit ti panag-urnos (masapul ti JavaScript)',
'tog-editondblclick' => 'Urnosen dagiti panid iti mamindua a panagtakla (masapul ti JavaScript)',
-'tog-editsection' => 'Pakabaelan ti panag-urnos iti paset babaen kadagiti [urnosen] a panilpo',
+'tog-editsection' => 'Pakabaelan ti paset a panag-urnos babaen kadagiti [urnosen] a panilpo',
'tog-editsectiononrightclick' => 'Pakabaelan ti paset a panag-urnos babaen ti agtakla ti kanawan kadagiti paset a titulo (masapul ti JavaScript)',
'tog-showtoc' => 'Ipakita ti tabla dagiti linaon (para kadagiti panid nga adda ti ad-adu ngem dagiti 3 a paulo)',
'tog-rememberpassword' => 'Laglagipem ti iseserrekko iti daytoy a pagbasabasa (iti kapaut nga $1 {{PLURAL:$1|aldaw|al-aldaw}})',
'category-empty' => "''Daytoy a kategoria ket agdama a saan nga aglaon kadagiti panid wenno midia.''",
'hidden-categories' => '{{PLURAL:$1|Nailemmeng a kategoria|Nailemmeng a katkategoria}}',
'hidden-category-category' => 'Nailemmeng a katkategoria',
-'category-subcat-count' => '{{PLURAL:$2|Daytoy a kategoria ket adda laeng ti sumaganad nga apo ti kategoria.|Daytoy a kategoria ket adda kadagiti sumaganad nga {{PLURAL:$1|nga apo ti kategoria|$1 nga apo dagiti kategoria}}, manipud ti dagup nga $2.}}',
+'category-subcat-count' => '{{PLURAL:$2|Daytoy a kategoria ket adda laeng ti sumaganad nga apo ti kategoria.|Daytoy a kategoria ket adda kadagiti sumaganad {{PLURAL:$1|nga apo ti kategoria|$1 nga apo dagiti kategoria}}, manipud ti dagup nga $2.}}',
'category-subcat-count-limited' => 'Daytoy a kategoria ket adda ti sumaganad {{PLURAL:$1|nga apo ti kategoria|$1 nga apo dagiti kategoria}}.',
'category-article-count' => '{{PLURAL:$2|Daytoy a kategoria ket aglaon laeng ti sumaganad a panid.|Ti sumaganad {{PLURAL:$1|a panid|$1 a pampanid}} ket adda iti daytoy a kategoria, manipud ti dagup nga $2.}}',
'category-article-count-limited' => 'Ti sumaganad {{PLURAL:$1|a panid |$1 a pampanid}} ket adda iti agdama a kategoria.',
'qbbrowse' => 'Agbasabasa',
'qbedit' => 'Urnosen',
'qbpageoptions' => 'Daytoy a panid',
-'qbpageinfo' => 'Linaon',
'qbmyoptions' => 'Pampanidko',
'qbspecialpages' => 'Espesial a pampanid',
'faq' => 'FAQ',
'vector-action-protect' => 'Salakniban',
'vector-action-undelete' => 'Isubli ti inikkat',
'vector-action-unprotect' => 'Sukatan ti salaknib',
-'vector-simplesearch-preference' => 'Pakabaelan ti napasayaat a singasing ti panagbiruk (Kudil a Vector laeng)',
+'vector-simplesearch-preference' => 'Pakabaelan ti napalaka a baras ti panagbiruk (Kudil a Vector laeng)',
'vector-view-create' => 'Agaramid',
'vector-view-edit' => 'Urnosen',
'vector-view-history' => 'Kitaen ti pakasaritaan',
Ti database ket nangipatulod ti biddut "$3: $4".',
'laggedslavemode' => 'Ballaag: Mabalin a ti panid ket saan nga aglaon kadagiti naudi a panagpabaro.',
'readonly' => 'Nakandadoan ti database',
-'enterlockreason' => 'Agikabil ti maysa a rasaon para iti kandado, agraman ti karkulo no kaano a maluktan ti kandado',
+'enterlockreason' => 'Agikabil ti maysa a rason para iti kandado, agraman ti karkulo no kaano a malukatan ti kandado',
'readonlytext' => 'Ti database ket agdama a naikandado kadagiti baro a panagikabil ken panagbaliw, mabalin a gapu dagiti kanayon a pagsimpa, ket no malpas kadawyanto nga agsubli.
Ti administrador a nangkandado ket nangited ti daytoy a palawag: $1',
'sitejspreview' => "'''Laglagipem nga ipadpadasmo laeng ti kodigo daytoy a JavaScript.'''
'''Saan pay nga naidulin!'''",
'userinvalidcssjstitle' => "'''Ballaag:''' Awan ti kudil a \"\$1\".
-Annawid a .css ken .js dagiti titulo ket agususar ti napababa a letra, a kas dagiti {{ns:user}}:Foo/vector.css saan ket a {{ns:user}}:Foo/Vector.css.",
+Annawid a .css ken .js dagiti titulo ket agususar ti babassit a letra, a kas dagiti {{ns:user}}:Foo/vector.css saan ket a {{ns:user}}:Foo/Vector.css.",
'updated' => '(Napabaro)',
'note' => "'''Paammo:'''",
'previewnote' => "'''Laglagipem a daytoy ket panagipadas laeng.'''
'editingold' => "'''Ballag: Ururnosem ti daan a panag-baliw iti daytoy a panid.'''
No idulinmo, mapukaw amin a sinukatam iti daytoy a panag-baliw.",
'yourdiff' => 'Dagiti nagdudumaan',
-'copyrightwarning' => "Laglagipenyo koma, apo, nga amin a parawad iti {{SITENAME}} ket maibilang a mairuar iti babaen ti $2 (kitaen ti $1 para kadagiti salaysay).
+'copyrightwarning' => "Laglagipenyo koma, apo, nga amin a maiparawad iti {{SITENAME}} ket maibilang a mairuar babaen ti $2 (kitaen ti $1 para kadagiti salaysay).
No dimo kayat a ti sinuratmo ket maurnos nga awanan-asi ken maiwaras nga awan sungsungbatan kenka, saanmo laengen nga ip-ipan wenno ipabpablaak ditoy.<br />
-Kasta met nga ikarim kadakami a bukodmo a sinurat wenno gapuanan daytoy, wenno tinuladmo manipud ti maysa a nawaya a pagturayan ti publiko wenno ti kapadpadana a lnawaya a nagtaudan.
+Kasta met nga ikarim kadakami a bukodmo a sinurat wenno gapuanan daytoy, wenno tinuladmo manipud ti maysa a nawaya a pagturayan ti publiko wenno ti kapadpadana a nawaya a nagtaudan.
'''Saan a mangited ti adda karbenganna a panagipablaak nga obra no awan ti pammalubos!'''",
-'copyrightwarning2' => "Pangngaasiyo, apo, a laglagipen nga amin a parawad iti {{SITENAME}} ket mabalin a maurnos, masuktan, wenno ikkaten dagiti sabali pay nga agar-aramat.
+'copyrightwarning2' => "Pangngaasiyo, apo, a laglagipen nga amin a maiparawad iti {{SITENAME}} ket mabalin a maurnos, masuktan, wenno ikkaten dagiti sabali pay nga agar-aramat.
No dimo kayat a ti sinuratmo ket maurnos nga awanan-asi ken maiwaras nga awan sungsungbatan kenka, saanmo laengen nga ip-ipan wenno ipabpablaak ditoy.<br />
Kasta met nga ikarim kadakami a bukodmo a sinurat wenno gapuanan daytoy, wenno tinuladmo manipud ti maysa a nawaya a pagturayan ti publiko wenno ti kapadpadana a nawaya a pagtaudan (kitaen ti $1 para iti salaysay).
'''Saan a mangipan iti adda ti karbenganna a panagpablaak nga obra no awan ti pammalubos!'''",
'searchresults-title' => 'Dagiti nabirukan a nagbanagan para iti "$1"',
'searchresulttext' => 'Para iti adu pay a pakaammo a maipanggep ti panagbiruk {{SITENAME}}, kitaem ti [[{{MediaWiki:Helppage}}|{{int:help}}]].',
'searchsubtitle' => 'Nagbirukka para iti \'\'\'[[:$1]]\'\'\' ([[Special:Prefixindex/$1|amin a panid a mangrugi iti "$1"]]{{int:pipe-separator}}[[Special:WhatLinksHere/$1|amin a panid nga agsilpo iti "$1"]])',
-'searchsubtitleinvalid' => "Nagbiruk ka para iti '''$1'''",
+'searchsubtitleinvalid' => "Nagbirukka para iti '''$1'''",
'toomanymatches' => 'Adu unay ti napasubli nga agpapada, pangngaasi a padasem ti sabali a panagsapul',
'titlematches' => 'Dagiti kapadpada a titulo ti panid',
'notitlematches' => 'Awan dagiti kapadpada a titulo ti panid',
'allowemail' => 'Pakabaelam ti e-surat a naggapu kadagiti sabali nga agar-aramat',
'prefs-searchoptions' => 'Biruken',
'prefs-namespaces' => 'Nagan ti luglugar',
-'defaultns' => 'Wenno no saan agbirukka kadagitoy a nagan ti luglugar:',
+'defaultns' => 'Wenno saan agbirukka kadagitoy a nagan ti luglugar:',
'default' => 'kasisigud',
'prefs-files' => 'Dagiti papeles',
'prefs-custom-css' => 'Naiduma a CSS',
# Upload
'upload' => 'Mangipan iti papeles',
'uploadbtn' => 'Mangipan iti papeles',
-'reuploaddesc' => 'Ukasen ti pag-ipan ken absubli idiay kabuklan ti pag-ipan',
+'reuploaddesc' => 'Ukasen ti pag-ipan ken agsubli idiay kabuklan ti pag-ipan',
'upload-tryagain' => 'Ited ti napabaro a panagipalawag ti papeles',
'uploadnologin' => 'Saan a nakastrek',
'uploadnologintext' => 'Masapul a [[Special:UserLogin|nakaserrekka]] tapno makaipanka iti papeles.',
'windows-nonascii-filename' => 'Daytoy a wiki ket saanna a tapayaen dagiti nagan ti papeles nga adda ti kangrunaan a kababalin',
'fileexists' => 'Adda ti papeles nga agnagan ti kastoy, pangngaasi a kitaemti <strong>[[:$1]]</strong> no saanka a sigurado a mangsukat.
[[$1|thumb]]',
-'filepageexists' => 'Ti panangipalpalawag a panid ti daytoy a papeles ket naaramiden idiay <strong>[[:$1]]</strong>, mgem awan ti agnagan ti katoy a papeles.
+'filepageexists' => 'Ti panangipalpalawag a panid ti daytoy a papeles ket naaramiden idiay <strong>[[:$1]]</strong>, ngem awan ti agnagan ti katoy a papeles.
Ti pakabuklan nga inkabilmo ket saan nga agparang idiay panid ti panangipalpalawag.
Tapno ti pakabuklan ket agparang idiay, masapul a baliwam idiay.
[[$1|thumb]]',
'upload-curl-error6-text' => 'Ti URL a naited ket saan a madanon.
Pangngaasi ta kitaem manen no husto ti URL ken adda dayta a pagsaadan.',
'upload-curl-error28' => 'Nagsardeng ti panag-ipan',
-'upload-curl-error28-text' => 'Ti pagsaadan ket nabayag unay nga simmungbat.
-Pangngaasi ti kitaen no naipatakder ti pagsaadan, aguray no madamdama ket padasem manen.
-Baka kayatmo a padasen no saan da a makumikom.',
+'upload-curl-error28-text' => 'Ti pagsaadan ket nabayag unay a simmungbat.
+Pangngaasi a kitaen no naipatakder ti pagsaadan, aguray no madamdama ket padasem manen.
+Baka kayatmo a padasen no saan a makumikom nga oras.',
'license' => 'Lisensia:',
'license-header' => 'Lisensia',
'sharedupload-desc-create' => 'Daytoy a papeles ket naggapu manipud idiay $1 ken mabalin a mausar babaen dagiti sabali a gandat.
Baka kayatmo nga urnosen ti bukodna a deskripsionna idiay [$2 deskripsion ti papeles a panid].',
'filepage-nofile' => 'Awan ti agnagan ti kasta a papeles.',
-'filepage-nofile-link' => 'Awan ti agnagan ti kastoy a papeles, ngem mabalin mo ti [$1 mangipan].',
+'filepage-nofile-link' => 'Awan ti agnagan ti kastoy a papeles, ngem mabalinmo ti [$1 mangipan].',
'uploadnewversion-linktext' => 'Mangipan ti kabarbaro a bersion iti daytoy a papeles',
'shared-repo-from' => 'Naggapo iti $1',
'shared-repo' => 'iti pagbingbingayan a nagikabilan',
'filerevert-defaultcomment' => 'Naisubli ti bersion manipud idi $2, $1',
'filerevert-submit' => 'Isubli',
'filerevert-success' => "Ti '''[[Media:$1|$1]]''' ket naipasubli idiay [$4 bersion ti oras ken petsa $3, $2].",
-'filerevert-badversion' => 'Awan ti dati a lokal a bersion daytoy a papelesa naited ti dayta nga oras ken petsa.',
+'filerevert-badversion' => 'Awan ti dati a lokal a bersion daytoy a papeles a naited ti dayta nga oras ken petsa.',
# File deletion
'filedelete' => 'Ikkaten ti $1',
'filedelete-reason-dropdown' => '*Kadawyan a rasrason ti pannakaikkat
** Panagsalungasing iti karbengan ti panagkopia
** Nadoble a papeles',
-'filedelete-edit-reasonlist' => 'Unosen dagiti rason ti panagikkat',
+'filedelete-edit-reasonlist' => 'Urnosen dagiti rason ti panagikkat',
'filedelete-maintenance' => 'Ti panagikkat ken panagisubli kadagiti papaeles ket nabaldado iti las-ud ti panagtartaripatu.',
'filedelete-maintenance-title' => 'Saan a maikkat daytoy a papeles',
'statistics-pages' => 'Pampanid',
'statistics-pages-desc' => 'Dagiti amin a panid ti wiki, a mairaman dagiti tungtungan a panid, dagiti baw-ing, ken dadduma pay',
'statistics-files' => 'Ti naipapan a papeles',
-'statistics-edits' => 'Dagit naurnos a panid manipud idi nairugi ti {{SITENAME}}',
-'statistics-edits-average' => 'Pagtengngaan nga urnos tungal maysa a panid',
+'statistics-edits' => 'Dagiti naurnos a panid manipud idi nairugi ti {{SITENAME}}',
+'statistics-edits-average' => 'Pagtengngaan nga urnos ti tunggal maysa a panid',
'statistics-views-total' => 'Dagiti dagup ti panagkita',
'statistics-views-total-desc' => 'Saan a naikabil ti panagkita dagiti awan a panid ken dagiti espesial a panid',
-'statistics-views-peredit' => 'Mano a panagkita tunggal maysa nga urnos',
+'statistics-views-peredit' => 'Mano a panagkita ti tunggal maysa nga urnos',
'statistics-users' => 'Dagiti nakarehistro nga [[Special:ListUsers|agar-aramat]]',
'statistics-users-active' => 'Dagiti nasiglat nga agar-aramat',
-'statistics-users-active-desc' => 'Dagiti agar-aramat a nagtungpal iti aramid idi napalubos nga {{PLURAL:$1|aldaw|$1 al-aldaw}}',
+'statistics-users-active-desc' => 'Dagiti agar-aramat a nagtungpal ti aramid ti napalabas nga {{PLURAL:$1|aldaw|$1 nga al-aldaw}}',
'statistics-mostpopular' => 'Kaaduan a nabuya a pampanid',
'disambiguations' => 'Dagiti panid a nakasilpo kadagiti panangilawlawag',
'booksources-search-legend' => 'Agsapul kadagiti nagtaudan ti liblibro',
'booksources-go' => 'Inkan',
'booksources-text' => 'Dita baba ket listaan dagiti panilpo ti sabsali a lugar nga aglaklako ti liblibro, ken baka adda pay adu a pakaammo da kadagiti liblibro a kitkitaem:',
-'booksources-invalid-isbn' => 'Ti naited nga ISBN ket kasla saan nga umisu; kitaen dagiti biddut ti pinagtulad kadagiti naggappuanna a taudan.',
+'booksources-invalid-isbn' => 'Ti naited nga ISBN ket kasla saan nga umisu; kitaen dagiti biddut ti panagtulad kadagiti naggappuanna a taudan.',
# Special:Log
'specialloguserlabel' => 'Ti nagtungpal:',
'alreadyrolled' => 'Saan a maipasubli ti kinaudi a panagurnos iti [[:$1]] babaen ni [[User:$2|$2]] ([[User talk:$2|tungtungan]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]]);
adda sabali a naurnos wenno nagipasubli ti panid.
-Ti kinaudi a panagurnos daytoy a panid ket babaen ni [[User:$3|$3]] ([[User talk:$3|tungtungan]]{{int:pipe-separator}}[[Special:Contributions/$3|{{int:contribslink}}]]).',
+Ti kinaudi a panagurnos ti daytoy a panid ket babaen ni [[User:$3|$3]] ([[User talk:$3|tungtungan]]{{int:pipe-separator}}[[Special:Contributions/$3|{{int:contribslink}}]]).',
'editcomment' => "Ti panagurnos a pakabuklan idi ket: \"''\$1''\".",
'revertpage' => 'Insubli ti panagurnos babaen ni [[Special:Contributions/$2|$2]] ([[User talk:$2|tungtungan]]), naisubli ti kinaudi a panagbaliw babaen ni [[User:$1|$1]]',
'revertpage-nouser' => 'Naisubli ti panagurnos babaen ni (naikkat ti nagan ti agar-aramat) ti kinaudi a panagbaliw babaen ni [[User:$1|$1]]',
'ipb-blocklist' => 'Kitaen dagiti adda a serra',
'ipb-blocklist-contribs' => 'Dagiti naaramidan ni $1',
'unblockip' => 'Lukatan ti serra ti agar-aramat',
-'unblockiptext' => 'Usaren ti kinabuklan dita baba ti pinagisubli ti pinagserrek nga agsurat ti napalubos a naserran nga IP a pagtaengan wenno nagan ti agar-aramat.',
+'unblockiptext' => 'Usaren ti kinabuklan dita baba ti pinagisubli ti pinagserrek nga agsurat ti napalabas a naserran nga IP a pagtaengan wenno nagan ti agar-aramat.',
'ipusubmit' => 'Ikkaten daytoy a serra',
'unblocked' => 'Naikkat ti panakaserra ni [[User:$1|$1]]',
'unblocked-range' => '$1 naikkaten ti serra na',
'talkexists' => "'''Sibaballigi a naiyalis ti panid, nupay kasta saan a maiyalis ti panid ti tungtongan gapu ta addan panid-tungtongan iti baro a titulo.
Pangngaasim ta i-manualmo lattan a pagtiponem ida.'''",
'movedto' => 'naiyalis iti',
-'movetalk' => 'Iyalis ti mainaig a panid ti tungtongan',
+'movetalk' => 'Iyalis ti mainaig a panid ti tungtungan',
'move-subpages' => 'Iyalis dagiti apo ti panid (aginggana ti $1)',
'move-talk-subpages' => 'Iyalis dagiti apo ti panid iti tungtungan ti panid (aginggana ti $1)',
'movepage-page-exists' => 'Ti panid ti $1 ket addan ken saan a mautomatiko a suratan manen.',
'delete_and_move_text' => '== Masapul nga ikkaten ==
Ti pangipanan ti panid ket "[[:$1]]" addan.
Kayatmo nga ikkaten tapno makaiyalis ka?',
-'delete_and_move_confirm' => 'Wen, ikkatenen ti panid',
+'delete_and_move_confirm' => 'Wen, ikkaten ti panid',
'delete_and_move_reason' => 'Naikkat tapno mawayaan ti panaka-iyalis idiay "[[$1]]"',
'selfmove' => 'Ti titulo ti taudan ken ti pangipanan ket agpadpada;
saanmo a maiyalis ti panid ti isu met laeng a panid.',
'semiprotectedpagemovewarning' => "'''Pakaammo:''' Nasalakniban daytoy a panid tapno dagiti laeng nakarehistro nga agar-aramat ti makaiyalis daytoy.
Ti kinaudi a naikabil ti listaan ket adda iti baba tapno mausar a reperensia:",
'move-over-sharedrepo' => '== Addaan ti papeles ==
-[[:$1]] addaan idiay pagbingayan a nagikabilan. Ti panagiyalis ti papeles iti titulo nga itoy ket paawanen na ti pagbingayan a papeles.',
+[[:$1]] addaan idiay pagbingayan a nagikabilan. Ti panagiyalis ti papeles iti titulo nga itoy ket paawanenna ti pagbingayan a papeles.',
'file-exists-sharedrepo' => 'Ti napilim a nagan ti papeles ket naususaren idiay pagbingayan a pagikabilan.
Pangngaasi nga agpilika ti sabali a nagan.',
'exporttext' => 'Maipanmo ti testo ken pakasaritaan ti inurnos iti maysa a panid wenno pampanid a nabalkut ti XML.
Daytoy ket mabalin a maikabil iti sabali a wiki nga agususar ti MediaWiki nga usaren ti [[Special:Import|pinagala ti panid]].
-Ti pinagipan ti panid, ikabil ti titulo dita kahon ti testo dita baba, maysa a titulo iti maysa a linia, ken agpili ka no ti kayatmo ket ti agdama a pinagbaliw ken amin nga daan a pinagbalbaliw, nga addaan ti linia ti pakasaritaan ti pampanid, wenno ti agdama a pinagbaliw nga addaan ti pakaammo a maipapan ti kinaudi a pinagurnos.
+Ti pinagipan ti panid, ikabil ti titulo dita kahon ti testo dita baba, maysa a titulo iti maysa a linia, ken agpili ka no ti kayatmo ket ti agdama a pinagbaliw ken amin nga daan a panagbalbaliw, nga addaan ti linia ti pakasaritaan ti pampanid, wenno ti agdama a panagbaliw nga addaan ti pakaammo a maipapan ti kinaudi a panagurnos.
No iti kinaudi a kaso mabalinmo nga usaren ti panilpo, a kas pagarigan [[{{#Special:Export}}/{{MediaWiki:Mainpage}}]] para iti panid "[[{{MediaWiki:Mainpage}}]]".',
'exportall' => 'Ipan amin a pampanid',
'exportcuronly' => 'Iraman laeng ti kinaudi a panagbaliw, saan a ti napno a pakasaritaan',
'exportnohistory' => "----
-'''Palagip:''' Ti pagipapan dagiti punno a pakasaritaan dagiti panid iti daytoy a kinabuklan ket nabaldado gapu dagiti pinakalaing ti pinagandar a rason.",
+'''Palagip:''' Ti pagipapan dagiti punno a pakasaritaan dagiti panid iti daytoy a kinabuklan ket nabaldado gapu dagiti panakalaing ti panagandar a rason.",
'exportlistauthors' => 'Iraman ti amin a listaan kadagiti nagaramid iti tunggal a maysa a panid',
'export-submit' => 'Agipan',
'export-addcattext' => 'Agnayon kadagiti panid a naggapu idiay kategoria:',
'allmessagesdefault' => 'Kasisigud a testo ti mensahe',
'allmessagescurrent' => 'Agdama a testo ti mensahe',
'allmessagestext' => 'Daytoy ti listaan dagiti mensahe ti sistema a magun-od idiay MediaWiki a nagan ti lugar.
-Pangngaasi a bisitaeen ti [//www.mediawiki.org/wiki/Localisation MediaWiki Localisation] ken [//translatewiki.net translatewiki.net] no kayatmo ti agparawad kadagiti sapasap a panagipatarus ti MediaWiki.',
+Pangngaasi a bisitaen ti [//www.mediawiki.org/wiki/Localisation Lokalisasion ti MediaWiki] ken [//translatewiki.net translatewiki.net] no kayatmo ti agparawad kadagiti sapasap a panagipatarus ti MediaWiki.',
'allmessagesnotsupportedDB' => "Saan a mausar daytoy a panid ngamin ket ti '''\$wgUseDatabaseMessages''' ket nabaldado.",
'allmessages-filter-legend' => 'Sagat',
-'allmessages-filter' => 'Sagaten ti naiduma a estado:',
+'allmessages-filter' => 'Sagaten babaen ti naipaduma nga estado:',
'allmessages-filter-unmodified' => 'Saan a nabaliwan',
'allmessages-filter-all' => 'Amin',
'allmessages-filter-modified' => 'Napabaro',
'thumbnail_invalid_params' => 'Imbalido a parametro ti imahen',
'thumbnail_dest_directory' => 'Saan a nakaaramid ti pangipanan a direktoria.',
'thumbnail_image-type' => 'Daytoy a kita ti imahen ket saan a nasuportaran.',
-'thumbnail_gd-library' => 'Saan a kompleto a GD bibliotika a pinakaaramid: Awan ti opisio $1',
+'thumbnail_gd-library' => 'Saan a kompleto a GD biblioteka a panakaaramid: Awan ti opisio $1',
'thumbnail_image-missing' => 'Daytoy a papeles ket kasla napukaw: $1',
# Special:Import
'importunknownsource' => 'Di amammo a kita ti taudan ti innala',
'importcantopen' => 'Saan a maluktan ti innala a papeles',
'importbadinterwiki' => 'Saan a nasayaat a panilpo nga interwiki',
-'importnotext' => 'Awanan linaon wenno awanan testo',
+'importnotext' => 'Awan linaon wenno awan ti testo',
'importsuccess' => 'Nalpasen ti pinagala!',
'importhistoryconflict' => 'Adda kasinnungat a pinagbaliw ti pakasaritaan (baka naala daytoy a panid idi)',
'importnosources' => 'Awan ti innala a taudan ti transwiki ti naipalawag ken ti dagus a pakasaritaan ti pinag-ipan ket nabaldado.',
'import-nonewrevisions' => 'Amin a panagbalbaliw ket dati a naala.',
'xml-error-string' => '$1 iti linia $2, tukol $3 (byte $4): $5',
'import-upload' => 'Ipan ti XML data',
-'import-token-mismatch' => 'Napukaw ti gimong ti data.
-Pangngaasi ta padasem manen.',
+'import-token-mismatch' => 'Napukaw ti gimong ti datos.
+Pangngaasi a padasem manen.',
'import-invalid-interwiki' => 'Saan a makaala dita naited a wiki.',
-'import-error-edit' => 'Ti panid ti "$1" ket saan a naala ngamin ket saan mo a mabalin nga urnosen.',
-'import-error-create' => 'Ti panid ti "$1" ket saan a naala ngamin ket saan mo a mabalin nga aramiden.',
+'import-error-edit' => 'Ti panid ti "$1" ket saan a naala ngamin ket saanmo a mabalin nga urnosen.',
+'import-error-create' => 'Ti panid ti "$1" ket saan a naala ngamin ket saanmo a mabalin nga aramiden.',
'import-error-interwiki' => 'Ti panid ti "$1" ket saan a naala ngamin ket ti nagan ket nailasin para iti ruar a panagsilpo (interwiki).',
'import-error-special' => 'Ti panid ti "$1" ket saan a naala ngamin ket bukod ti espesial a nagan a lugar a saan nga agpalubos ti pampanid.',
'import-error-invalid' => 'Ti panid ti "$1" ket saan a naala ngamin ket ti nagan ket imbalido.',
'tooltip-ca-nstab-category' => 'Kitaen ti panid ti kategoria',
'tooltip-minoredit' => 'Markaan daytoy a kas bassit a panag-urnos',
'tooltip-save' => 'Idulin dagiti sinukatam',
-'tooltip-preview' => 'Ipadas dagiti sinukatam, pangngaasimnga usarem daytoy sakbay nga idulinmo ti panid!',
+'tooltip-preview' => 'Ipadas dagiti sinukatam, pangngaasim nga usarem daytoy sakbay nga idulinmo ti panid!',
'tooltip-diff' => 'Ipakita no ania dagiti sinukatan nga inaramidmo iti testo',
-'tooltip-compareselectedversions' => 'Kitaen ti naggidiatan dagiti dua a napili a bersion daytoy a panid.',
+'tooltip-compareselectedversions' => 'Kitaen ti naggidiatan dagiti dua a napili a bersion ti daytoy a panid.',
'tooltip-watch' => 'Inayon daytoy a panid idiay listaan dagiti bambantayam',
'tooltip-watchlistedit-normal-submit' => 'Ikkaten dagiti titulo',
'tooltip-watchlistedit-raw-submit' => 'Pabaruen ti listaan ti bambantayan',
'pageinfo-magic-words' => 'Salamangka {{PLURAL:$1|a balikas|a balbalikas}} ($1)',
'pageinfo-hidden-categories' => 'Nailemmeng {{PLURAL:$1|a kategoria|a katkategoria}} ($1)',
'pageinfo-templates' => 'Nailak-am {{PLURAL:$1|a plantilia|a planplantilia}} ($1)',
+'pageinfo-toolboxlink' => 'Pakaammo ti panid',
# Patrolling
'markaspatrolleddiff' => 'Markaan a kas napatruliaan',
$5
Daytoy a pammasingked a kodigo ket agpaso iti $4.',
-'confirmemail_body_changed' => 'Addaan, baka sika, ti naggapu ti IP a apagtaengam $1,
+'confirmemail_body_changed' => 'Addaan, baka sika, ti naggapu ti IP a pagtaengam $1,
ket nangsukat ti e-surat a pagtaengan ti pakabilangan "$2" iti daytoy a pagtaengan idiay {{SITENAME}}
Tapno mapasingkedan daytoy a pakabilangan ket kukuam ken ti
-pinagpabalin ti e-surat a kita idiay {{SITENAME}}, lukatam daytoy a panilpo dita pabasabasam:
+panagpabalin ti e-surat a kita idiay {{SITENAME}}, lukatam daytoy a panilpo dita pabasabasam:
$3
'qbbrowse' => 'Flakka',
'qbedit' => 'Breyta',
'qbpageoptions' => 'Þessi síða',
-'qbpageinfo' => 'Samhengi',
'qbmyoptions' => 'Mínar síður',
'qbspecialpages' => 'Kerfissíður',
'faq' => 'Algengar spurningar',
'qbbrowse' => 'Sfoglia',
'qbedit' => 'Modifica',
'qbpageoptions' => 'Opzioni pagina',
-'qbpageinfo' => 'Informazioni sulla pagina',
'qbmyoptions' => 'Le mie pagine',
'qbspecialpages' => 'Pagine speciali',
'faq' => 'Domande frequenti',
'edit-already-exists' => 'Impossibile creare una nuova pagina.
Esiste già.',
'defaultmessagetext' => 'Testo predefinito',
+'content-failed-to-parse' => 'Impossibile analizzare $2 per il modello $1: $3',
+'invalid-content-data' => 'Dati contenuti non validi',
+'content-not-allowed-here' => 'Contenuto in "$1" non consentito nella pagine [[$2]]',
+
+# Content models
+'content-model-wikitext' => 'wikitesto',
+'content-model-text' => 'testo normale',
+'content-model-javascript' => 'JavaScript',
+'content-model-css' => 'CSS',
# Parser/template warnings
'expensive-parserfunction-warning' => "'''Attenzione:''' Questa pagina contiene troppe chiamate alle parser functions.
'shared-repo-from' => 'da $1',
'shared-repo' => 'un archivio condiviso',
'filepage.css' => '/* Il CSS messo qui viene incluso nella pagina di descrizione del file, inclusa anche su wiki client esterni */',
-'upload-disallowed-here' => 'Impossibile sovrascrivere questa immagine.',
+'upload-disallowed-here' => 'Impossibile sovrascrivere questo file.',
# File reversion
'filerevert' => 'Ripristina $1',
'undeletedrevisions' => '{{PLURAL:$1|Una revisione recuperata|$1 revisioni recuperate}}',
'undeletedrevisions-files' => '{{PLURAL:$1|Una revisione|$1 revisioni}} e $2 file recuperati',
'undeletedfiles' => '{{PLURAL:$1|Un file recuperato|$1 file recuperati}}',
-'cannotundelete' => 'Ripristino non riuscito; è possibile che la pagina sia già stata recuperata da un altro utente.',
+'cannotundelete' => 'Ripristino non riuscito:
+$1',
'undeletedpage' => "'''La pagina $1 è stata recuperata'''
Consultare il [[Special:Log/delete|log delle cancellazioni]] per vedere le cancellazioni e i recuperi più recenti.",
'immobile-target-namespace-iw' => 'Un collegamento interwiki non è una destinazione valida per spostare la pagina.',
'immobile-source-page' => 'Questa pagina non può essere spostata.',
'immobile-target-page' => 'Non è possibile spostare sul titolo indicato.',
+'bad-target-model' => 'La destinazione desiderata utilizza un modello di contenuti diverso. Non è possibile convertire da $1 a $2.',
'imagenocrossnamespace' => 'Non è possibile spostare un file fuori dal relativo namespace.',
'nonfile-cannot-move-to-file' => 'Non è possibile spostare un file fuori dal relativo namespace.',
'imagetypemismatch' => 'La nuova estensione del file non corrisponde al tipo dello stesso',
# Info page
'pageinfo-title' => 'Informazioni per "$1"',
+'pageinfo-not-current' => 'Le informazioni possono essere visualizzate solo per la versione corrente.',
'pageinfo-header-basic' => 'Informazioni di base',
'pageinfo-header-edits' => 'Cronologia delle modifiche',
'pageinfo-header-restrictions' => 'Protezione della pagina',
'qbbrowse' => '閲覧',
'qbedit' => '編集',
'qbpageoptions' => 'このページについて',
-'qbpageinfo' => '関連情報',
'qbmyoptions' => '自分のページ',
'qbspecialpages' => '特別ページ',
'faq' => 'よくある質問と回答',
'viewyourtext' => "このページへの'''あなたの編集'''のソースの閲覧やコピーができます:",
'protectedinterface' => 'このページにはこのウィキのソフトウェアのインターフェイスに使用されるテキストが保存されており、いたずらなどの防止のために保護されています。
すべてのウィキに対して翻訳を追加/変更する場合は、MediaWiki の地域化プロジェクト [//translatewiki.net/ translatewiki.net] を使用してください。',
-'editinginterface' => "'''è¦å\91\8a:''' ã\82½ã\83\95ã\83\88ã\82¦ã\82§ã\82¢ã\81®ã\82¤ã\83³ã\82¿ã\83¼ã\83\95ã\82§ã\82¤ã\82¹ã\81«ä½¿ç\94¨ã\81\95ã\82\8cã\82\8bã\81®ã\83\86ã\82ã\82¹ã\83\88ã\81®ã\83\9aã\83¼ã\82¸ã\82\92ç·¨é\9b\86ã\81\97ã\81¦ã\81\84ã\81¾ã\81\99ã\80\82
+'editinginterface' => "'''警告:''' ソフトウェアのインターフェイスに使用されるテキストのページを編集しています。
このページを変更すると、このウィキの他の利用者のユーザーインターフェイスの外観に影響します。
すべてのウィキに対して翻訳を追加/変更する場合は、MediaWiki の地域化プロジェクト [//translatewiki.net/wiki/Main_Page?setlang=ja translatewiki.net] を使用してください。",
'sqlhidden' => '(SQL クエリ非表示)',
'loginreqtitle' => 'ログインが必要',
'loginreqlink' => 'ログイン',
'loginreqpagetext' => '他のページを閲覧するには$1する必要があります。',
-'accmailtitle' => 'パスワードを送信しました。',
+'accmailtitle' => 'パスワードをお送りしました。',
'accmailtext' => "[[User talk:$1|$1]]のために無作為に生成したパスワードを、$2に送信しました。
この新アカウントのパスワードは、ログインした際に''[[Special:ChangePassword|パスワード変更]]''ページで変更できます。",
'editingold' => "'''警告:このページの古い版を編集しています。'''
保存すると、この版以降に追加されていた変更がすべて失われます。",
'yourdiff' => '差分',
-'copyrightwarning' => "{{SITENAME}}への投稿は、すべて$2(詳細は$1を参照)のもとで公開したと見なされることにご注意ください。
+'copyrightwarning' => "{{SITENAME}}への投稿は、すべて$2 (詳細は$1を参照) のもとで公開したと見なされることにご注意ください。
あなたが投稿したものを、他人がよって遠慮なく編集し、それを自由に配布するのを望まない場合は、ここには投稿しないでください。<br />
また、投稿するのは、あなたが書いたものか、パブリック ドメインまたはそれに類するフリーな資料からの複製であることを約束してください。
-'''著作権保護されている作品を、許諾なしに投稿しないでください!'''",
+'''著作権保護されている作品を、許諾なしに投稿しないでください!'''",
'copyrightwarning2' => "{{SITENAME}}へのすべての投稿は、他の利用者が編集、変更、除去する可能性があります。
あなたの投稿を、他人が遠慮なく編集するのを望まない場合は、ここには投稿しないでください。<br />
また、投稿するのは、あなたが書いたものか、パブリック ドメインまたはそれに類するフリーな資料からの複製であることを約束してください(詳細は$1を参照)。
'edit-already-exists' => '新しいページを作成できませんでした。
そのページは既に存在しています。',
'defaultmessagetext' => '既定のメッセージ文',
+'content-failed-to-parse' => '$2 の本文を$1モデルとして構文解析できませんでした: $3',
+'invalid-content-data' => '本文データが無効です',
+'content-not-allowed-here' => 'ページ [[$2]] では、「$1」コンテンツは許可されていません',
+
+# Content models
+'content-model-wikitext' => 'ウィキテキスト',
+'content-model-text' => 'プレーンテキスト',
+'content-model-javascript' => 'JavaScript',
+'content-model-css' => 'CSS',
# Parser/template warnings
'expensive-parserfunction-warning' => "'''警告:'''このページでの高負荷なパーサー関数の呼び出し回数が多過ぎます。
'revdelete-hide-comment' => '編集の要約を隠す',
'revdelete-hide-user' => '投稿者の利用者名またはIPを隠す',
'revdelete-hide-restricted' => '他の利用者と同様に管理者からもデータを隠す',
-'revdelete-radio-same' => '(変更しない)',
+'revdelete-radio-same' => '(変更しない)',
'revdelete-radio-set' => 'はい',
'revdelete-radio-unset' => 'いいえ',
'revdelete-suppress' => '他の利用者と同様に管理者からもデータを隠す',
'search-suggest' => 'もしかして:$1',
'search-interwiki-caption' => '姉妹プロジェクト',
'search-interwiki-default' => '$1の結果:',
-'search-interwiki-more' => '(続き)',
+'search-interwiki-more' => '(続き)',
'search-relatedarticle' => '関連',
'mwsuggest-disable' => 'Ajaxによる検索候補の提示を無効にする',
'searcheverything-enable' => 'すべての名前空間を検索',
'prefs-watchlist-edits-max' => '最大数:1000',
'prefs-watchlist-token' => 'ウォッチリストのトークン:',
'prefs-misc' => 'その他',
-'prefs-resetpass' => 'ã\83\91ã\82¹ã\83¯ã\83¼ã\83\89ã\81®変更',
+'prefs-resetpass' => 'ã\83\91ã\82¹ã\83¯ã\83¼ã\83\89ã\82\92変更',
'prefs-changeemail' => 'メールアドレスを変更',
'prefs-setemail' => 'メールアドレスを設定',
'prefs-email' => 'メールの設定',
'right-suppressredirect' => '転送ページを作成せずにページを移動',
'right-upload' => 'ファイルをアップロード',
'right-reupload' => '既存のファイルに上書き',
-'right-reupload-own' => '自分自身がアップロードした既存のファイルに上書き',
+'right-reupload-own' => '自身がアップロードした既存のファイルに上書き',
'right-reupload-shared' => '共有メディアリポジトリ上のファイルにローカルで上書き',
'right-upload_by_url' => 'URL からファイルをアップロード',
'right-purge' => '確認なしでサイトのキャッシュを破棄',
'right-hideuser' => '利用者名をブロックして公開記録から隠す',
'right-ipblock-exempt' => 'IPブロック、自動ブロック、広域ブロックを回避',
'right-proxyunbannable' => 'プロキシの自動ブロックを回避',
-'right-unblockself' => '自分自身に対するブロックを解除',
+'right-unblockself' => '自身に対するブロックを解除',
'right-protect' => '保護レベルを変更し、保護されたページを編集',
'right-editprotected' => '保護ページ(カスケード保護を除く)を編集',
'right-editinterface' => 'ユーザーインターフェイスを編集',
'right-import' => '他のウィキからページを取り込み',
'right-importupload' => 'ファイルアップロードでページを取り込み',
'right-patrol' => '他人の編集を巡回済みにする',
-'right-autopatrol' => '自分の編集を自動的に巡回済みにする',
+'right-autopatrol' => '自身の編集を自動で巡回済みにする',
'right-patrolmarks' => '最近の更新で巡回済み印を閲覧',
'right-unwatchedpages' => 'ウォッチされていないページ一覧を閲覧',
'right-mergehistory' => 'ページの履歴を統合',
'shared-repo' => '共有リポジトリ',
'shared-repo-name-wikimediacommons' => 'ウィキメディア・コモンズ',
'filepage.css' => '/* ここに記述したCSSはファイル解説ページにて読み込まれます。また外部のクライアントウィキにも影響します */',
-'upload-disallowed-here' => '残念ながらこの画像には上書きできません。',
+'upload-disallowed-here' => 'このファイルには上書きできません。',
# File reversion
'filerevert' => '$1を差し戻す',
'booksources-isbn' => 'ISBN:',
'booksources-go' => '検索',
'booksources-text' => 'お探しの書籍の新品/中古品を販売している外部サイトへのリンクを以下に列挙します。この書籍についてさらに詳しい情報があるかもしれません:',
-'booksources-invalid-isbn' => '指定したISBN番号は有効ではないようです。情報源から写し間違えていないか確認してください。',
+'booksources-invalid-isbn' => '指定した ISBN は有効ではないようです。情報源から写し間違えていないか確認してください。',
# Special:Log
'specialloguserlabel' => '実行者:',
'restriction-level' => '制限レベル:',
'minimum-size' => '最小サイズ',
'maximum-size' => '最大サイズ:',
-'pagesize' => '(バイト)',
+'pagesize' => '(バイト)',
# Restrictions (nouns)
'restriction-edit' => '編集',
'undeletedrevisions' => '{{PLURAL:$1|$1版}}を復元しました',
'undeletedrevisions-files' => '{{PLURAL:$1|$1版}}と{{PLURAL:$2|$2ファイル}}を復元しました',
'undeletedfiles' => '{{PLURAL:$1|$1ファイル}}を復元しました',
-'cannotundelete' => '復元に失敗しました。
-他の誰かがこのページを既に復元した可能性があります。',
+'cannotundelete' => '復元に失敗しました:
+$1',
'undeletedpage' => "'''$1を復元しました。'''
最近の削除と復元の記録については[[Special:Log/delete|削除記録]]を参照してください。",
# Namespace form on various pages
'namespace' => '名前空間:',
-'invert' => '名前空間の選択を反転',
+'invert' => '選択したものを除く',
'tooltip-invert' => '選択した名前空間 (チェックを入れている場合は、関連付けられた名前空間も含む) のページの変更を非表示にするには、このボックスにチェックを入れる',
'namespace_association' => '関連付けられた名前空間も含める',
'tooltip-namespace_association' => '選択した名前空間に関連付けられたトークページ (逆にトークページの名前空間を選択した場合も同様) の名前空間も含めるには、このボックスにチェックを入れる',
'ipb-blocklist' => '現在有効なブロックを表示',
'ipb-blocklist-contribs' => '$1の投稿の一覧',
'unblockip' => 'ブロックを解除',
-'unblockiptext' => '以ä¸\8bã\81®ã\83\95ã\82©ã\83¼ã\83 ã\82\92使ç\94¨ã\81\97ã\81¦ã\80\81以å\89\8dã\83\96ã\83ã\83\83ã\82¯ã\81\97ã\81\9fIPã\82¢ã\83\89ã\83¬ã\82¹ã\81¾ã\81\9fã\81¯å\88©ç\94¨è\80\85ã\81\8bã\82\89ã\81®æ\9b¸ã\81\8dè¾¼ã\81¿ã\82¢ã\82¯ã\82»ã\82¹ã\82\92ã\83\96ã\83ã\83\83ã\82¯解除できます。',
+'unblockiptext' => '以ä¸\8bã\81®ã\83\95ã\82©ã\83¼ã\83 ã\81§å\88©ç\94¨è\80\85ã\81¾ã\81\9fã\81¯IPã\82¢ã\83\89ã\83¬ã\82¹ã\81®ã\83\96ã\83ã\83\83ã\82¯ã\82\92解除できます。',
'ipusubmit' => 'このブロックを解除',
'unblocked' => '[[User:$1|$1]]のブロックを解除しました',
'unblocked-range' => '$1のブロックは解除されています',
'immobile-target-namespace-iw' => 'ウィキ間リンクは、ページの移動先には指定できません。',
'immobile-source-page' => 'このページは移動できません。',
'immobile-target-page' => '移動先ページ名に移動させることができません。',
+'bad-target-model' => '指定した移動先では、異なるコンテンツ モデルを使用しています。$1から$2には変換できません。',
'imagenocrossnamespace' => 'ファイルを、ファイル名前空間以外に移動させることはできません',
'nonfile-cannot-move-to-file' => 'ファイルではないものを、ファイル名前空間に移動させることはできません',
'imagetypemismatch' => '新しいファイルの拡張子がファイルのタイプと一致していません',
# Info page
'pageinfo-title' => '「$1」の情報',
+'pageinfo-not-current' => '現在のバージョンの情報のみが表示される可能性があります。',
'pageinfo-header-basic' => '基本情報',
'pageinfo-header-edits' => '編集履歴',
'pageinfo-header-restrictions' => 'ページの保護',
'exif-flash-mode-1' => '強制発光モード',
'exif-flash-mode-2' => '強制非発光モード',
'exif-flash-mode-3' => '自動発光モード',
-'exif-flash-function-1' => 'ストロボ機能無し',
+'exif-flash-function-1' => 'ストロボ機能なし',
'exif-flash-redeye-1' => '赤目軽減有り',
'exif-focalplaneresolutionunit-2' => 'インチ',
'qbbrowse' => 'Navigasi',
'qbedit' => 'Sunting',
'qbpageoptions' => 'Kaca iki',
-'qbpageinfo' => 'Kontèks kaca',
'qbmyoptions' => 'Opsiku',
'qbspecialpages' => 'Kaca-kaca astaméwa',
'faq' => 'FAQ (Pitakonan sing kerep diajokaké)',
'qbbrowse' => 'გადახედე',
'qbedit' => 'რედაქტირება',
'qbpageoptions' => 'ეს გვერდი',
-'qbpageinfo' => 'კონტექსტი',
'qbmyoptions' => 'ჩემი გვერდები',
'qbspecialpages' => 'სპეციალური გვერდები',
'faq' => 'ხშირი შეკითხვები',
'vector-action-protect' => 'დაცვა',
'vector-action-undelete' => 'აღდგენა',
'vector-action-unprotect' => 'დაცვის დონის შეცვლა',
-'vector-simplesearch-preference' => 'á\83«á\83\94á\83\91á\83\9cá\83\98á\83¡ á\83\92á\83\90á\83¤á\83\90á\83 á\83\97á\83\9dá\83\94á\83\91á\83£á\83\9aá\83\98 á\83\9bá\83\98á\83\9cá\83\98á\83¨á\83\9cá\83\94á\83\91á\83\94á\83\91ის ჩართვა (მხოლოდ ვექტორული იერსახისთვის)',
+'vector-simplesearch-preference' => 'á\83«á\83\94á\83\91á\83\9cá\83\98á\83¡ á\83\92á\83\90á\83¤á\83\90á\83 á\83\97á\83\9dá\83\94á\83\91á\83£á\83\9aá\83\98 á\83\95á\83\94á\83\9aის ჩართვა (მხოლოდ ვექტორული იერსახისთვის)',
'vector-view-create' => 'შექმნა',
'vector-view-edit' => 'რედაქტირება',
'vector-view-history' => 'ისტორია',
'edit-already-exists' => 'ახალი გვერდის შექმნა არ მოხერხდა.
ის უკვე არსებობს.',
'defaultmessagetext' => 'შეტყობინების სტანდარტული ტექსტი',
+'content-failed-to-parse' => '$2-ის შინაარსი არ შეესაბამება $1-ის ტიპს: $3.',
+'invalid-content-data' => 'დაუშვებელი მონაცემები',
+'content-not-allowed-here' => '„$1“-ის შინაარსი დაუშვებელია [[$2]] გვერდზე',
+
+# Content models
+'content-model-wikitext' => 'ვიკიტექსტი',
+'content-model-text' => 'უბრალო ტექსტი',
+'content-model-javascript' => 'ჯავასკრიპტი',
+'content-model-css' => 'CSS',
# Parser/template warnings
'expensive-parserfunction-warning' => 'ყურადღება. მოცემული გვერდი შეიცავს ძალიან ბევრ მძიმე ფუნქციას.
'shared-repo-from' => ' $1-დან',
'shared-repo' => 'საერთო საცავიდან',
'shared-repo-name-wikimediacommons' => 'ვიკისაწყობი',
-'upload-disallowed-here' => 'á\83¡á\83\90á\83\9bá\83¬á\83£á\83®á\83\90á\83 á\83\9dá\83\93, á\83\97á\83¥á\83\95á\83\94á\83\9c á\83\90á\83 á\83¨á\83\94á\83\92á\83\98á\83«á\83\9aá\83\98á\83\90á\83\97 á\83\90á\83\9b á\83¡á\83£á\83 á\83\90á\83\97ზე გადაწერა.',
+'upload-disallowed-here' => 'á\83\97á\83¥á\83\95á\83\94á\83\9c á\83\90á\83 á\83¨á\83\94á\83\92á\83\98á\83«á\83\9aá\83\98á\83\90á\83\97 á\83\90á\83\9b á\83¤á\83\90á\83\98á\83\9aზე გადაწერა.',
# File reversion
'filerevert' => 'დააბრუნე $1',
'undeletedrevisions' => '$1 ვერსია აღდგენილია',
'undeletedrevisions-files' => '$1 ვერსია და $2 ფაილი აღდგენილია',
'undeletedfiles' => '$1 ფაილი აღდგენილია',
-'cannotundelete' => 'წაშლის გაუქმება ვერ განხორციელდა; შესაძლოა თქვენამდე სხვამ უკვე გააუქმა წაშლა.',
+'cannotundelete' => 'წაშლის გაუქმება ვერ განხორციელდა:
+$1',
'undeletedpage' => "'''$1 აღდგენილია'''
უკანასკნელი წაშლილთა და აღდგენის სია შეგიძლიათ ნახოთ [[Special:Log/delete|წაშლილთა სიაში]].",
'immobile-target-namespace-iw' => 'ინტერვიკის ბმული შეუძლებელია გამოყენებული იქნას გადარქმევისთვის.',
'immobile-source-page' => 'ამ გვეერდის გადატანა შეუძლებელია.',
'immobile-target-page' => 'შეუძლებელია მოცემულ სახელზე გადატანა.',
+'bad-target-model' => 'შეუძლებელია $1-ის გარდაქმნა $2-ზე: მონაცემების შეუსაბამო მოდელი.',
'imagenocrossnamespace' => 'შეუძლებელია ფაილს მიეცეს სახელი სახელთა სხვა სივრციდან',
'nonfile-cannot-move-to-file' => 'შეუძლებელია არაფაილების გადატანა ფაილის სახელთა სივრცეში',
'imagetypemismatch' => 'ფაილს ახალი გაფართოება არ შეესაბამება მის ტიპს',
# Info page
'pageinfo-title' => 'ინფორმაცია „$1“-თვის',
+'pageinfo-not-current' => 'მონაცემები წარმოდგენილია მხოლოდ მიმდინარე რედაქტირებისათვის.',
'pageinfo-header-basic' => 'საბაზისო ინფორმაცია',
'pageinfo-header-edits' => 'რედაქტირების ისტორია',
'pageinfo-header-restrictions' => 'გვერდის დაცვა',
'pageinfo-magic-words' => 'ჯადოსნური {{PLURAL:$1|სიტყვა|სიტყვა}} ($1)',
'pageinfo-hidden-categories' => 'დამალული {{PLURAL:$1|კატეგორია|კატეგორია}} ($1)',
'pageinfo-templates' => 'ინტეგრირებულია {{PLURAL:$1|თარგი|თარგი}} ($1)',
+'pageinfo-toolboxlink' => 'გვერდის ინფორმაცია',
# Skin names
'skinname-standard' => 'კლასიკური',
# Scary transclusion
'scarytranscludedisabled' => '[«Interwiki transcluding» გათიშულია]',
'scarytranscludefailed' => '[$1-თან დაკავშირების შეცდომა]',
+'scarytranscludefailed-httpstatus' => '[ვერ მოხერხდა თარგის ჩატვირთვა $1-თვის: HTTP $2]',
'scarytranscludetoolong' => '[URL ძალიან გრძელია]',
# Delete conflict
* @author Azwaw
* @author Mmistmurt
* @author MoubarikBelkasim
+ * @author Salem333
* @author Teak
* @author Urhixidur
*/
'qbbrowse' => 'Ẓer isebtar',
'qbedit' => 'Beddel',
'qbpageoptions' => 'Asebter-agi',
-'qbpageinfo' => 'Asatal',
'qbmyoptions' => 'isebtar inu',
'qbspecialpages' => 'isebtar usligen',
'faq' => 'Isteqsiyen',
'fileappenderror' => 'Ulamek an seffes « $1 » ar « $2 ».',
'filecopyerror' => 'Ur yezmir ara ad yexdem alsaru n ufaylu "$1" ar "$2".',
'filerenameerror' => 'Ur yezmir ara ad ibeddel isem ufaylu "$1" ar "$2".',
-'filedeleteerror' => 'Ur yezmir ara ad yemḥu afaylu "$1".',
+'filedeleteerror' => 'Ulamek an mḥu afaylu "$1".',
'directorycreateerror' => 'Ulamek an snulfu akaram « $1 ».',
'filenotfound' => 'Ur yezmir ara ad yaf afaylu "$1".',
'fileexistserror' => 'Ulamek an aru afaylu « $1 » : afaylu agi yesnulfad yakan.',
'logdelete-selected' => "'''{{PLURAL:$1|Tamirt n uɣmis tettwafren|Isallen n uɣmis ttwafernen}}:'''",
'revdelete-text' => 'Ileqman d tidyanin yettumḥan ad qqimen deg umezruy n usebter dɣa deg iɣmisen, maca agbur nsen ur i sɛu ara tuffart i uzayez."
Inedbalen wiyaḍ deg {{SITENAME}} zemren ad ẓṛen imuren i yettwafren u zemren a ten-mḥan, ḥaca ma llan icekkilen.',
+'revdelete-confirm' => 'Sergeg ma tebɣiḍ ad xedmeḍ tigawt agi, fehmeḍ inalkamen, dɣa temtawiḍ s [[{{MediaWiki:Policy-url}}|ilugan]].',
+'revdelete-suppress-text' => "Ilaq tukksa at illi kan deg tijṛa agi :
+* tilɣa n yiwen ur ezgan ara
+*: ''tansa, uṭṭun n tilifun, uṭṭun n taɣellist tamettit, …''",
'revdelete-legend' => 'Sbebd akref n tamuɣli',
'revdelete-hide-text' => 'Ffer aḍris n tsiwelt',
'revdelete-hide-image' => 'Ffer ayen yellan deg ufaylu',
'revdelete-log' => 'Ayɣer',
'revdelete-submit' => 'Snes {{PLURAL:$1|i tacaggart i tettwafren|i ticggarin i tettwafren}}',
'revdelete-success' => "''Asekkud n ileqman yemucce war uguren.'''",
+'revdelete-failure' => "'''Iẓṛi n lqem ur yemucceḍ ara :'''
+$1",
'logdelete-success' => "'''Asekkud n tamirt yettuxdem.'''",
+'logdelete-failure' => "'''Iẓṛi n uɣmis ur yezmer ara ad yesbadu :'''
+$1",
'revdel-restore' => 'beddel timezrit',
'revdel-restore-deleted' => 'allas iqḍeεen',
'revdel-restore-visible' => 'allas i nezmer an ẓeṛ',
'pagehist' => 'Amezruy n usebter',
'deletedhist' => 'Amezruy yemḥa',
+'revdelete-hide-current' => 'Yella anezri imi nemḥa aferdis yezemzen ass n $1 af $2 : d lqem aneggaru.
+Ur yezmer ara ad yemḥu.',
+'revdelete-show-no-access' => 'Yella anezri imi n beqqeḍ aferdis yezemzen ass n $1 af $2 : yecreḍ am "ukrif".
+Ur tesɛiḍ ara izerfan n wadduf.',
+'revdelete-modify-no-access' => 'Yella anezri imi nebeddel aferdis yezemzen ass n $1 af $2 : yecreḍ am "ukrif".
+Ur tesɛiḍ ara izerfan n wadduf.',
+'revdelete-modify-missing' => 'Yella anezri imi nebeddel aferdis yesɛan ID $1 : Ulac-it deg taffa n isefka !',
+'revdelete-no-change' => "'''Ɣur-wet :''' Aferdis yezemzen ass n $1 af $2 yesɛa yakan iɣewwaren n iẓṛi i tebɣiḍ.",
+'revdelete-concurrent-change' => 'Yella anezri imi nebeddel aferdis yezemzen ass n $1 af $2 : aẓayeris yetwebeddel sɣur amḍan nniḍen mi tbeddeleḍ
+Ẓeṛ iɣmisen.',
+'revdelete-only-restricted' => 'Yella anezri imi nemḥa asekcem yezemzen ass n $1 af $2 : ur tzemreḍ ara ad mḥuḍ iferdisen agi i inedbalen war ad fruḍ tixtiṛiyin nniḍen n umḥu.',
+'revdelete-reason-dropdown' => 'Tiɣẓinin timiranin n umḥu :
+** Akukel n izerfan umeskar (copyright) ;
+** Iwenniten naɣ tilɣa n yiwen ur yezgan ara ;
+** Tilɣa i zemren ad rgemen.',
'revdelete-otherreason' => 'Taɣẓint nniḍen / taɣzint tamarnant :',
'revdelete-reasonotherlist' => 'Taɣẓint nniḍen',
+'revdelete-edit-reasonlist' => 'Beddel tiɣẓinin n umḥu i-d-yettuɣalen',
'revdelete-offender' => 'Ameskar n tacaggart :',
# Suppression log
'suppressionlog' => 'Aɣmis n isfaḍen',
+'suppressionlogtext' => 'Ddaw-agi, umuɣ n tukksiwin d ikyafen yellan ɣef ugbur yeffren i inedbalen.
+Ẓeṛ [[Special:BlockList|umuɣ ikyafen]] i umuɣ n tiririyin d ikyafen yellan d imahlanen.',
# History merging
+'mergehistory' => 'Zdi amezruy n isebtar',
+'mergehistory-header' => 'Asebtar agi aken yeǧǧ ad tesduklem ileqman n umezruy n usebtar unṣib γer usebtar amaynut.
+Senked d akken tamhelt agi ad eǧǧ amezruy n usebtar ad ikemmel.',
+'mergehistory-box' => 'Zdi lqem n sin isebtar',
'mergehistory-from' => 'Azar n usebter :',
'mergehistory-into' => 'Aserken n usebter :',
'mergehistory-list' => 'Amezruy n ibeddilen i nezmer an zdi',
+'mergehistory-merge' => 'Ileqman id iteddun n [[:$1]] zemren ad twasduklen d [[:$2]]. Seqdec tigejdit n tqeffalt ṛadyu iwakken ad tesdukleḍ ala ileqman yettwasnulfan seg tazwara armi d azmez yettwamlan. Ẓeṛ d akken aseqdec n iseγwan n tunigin ad iwennez tigejdit agi.',
'mergehistory-go' => 'Ẓeṛ ibeddilen i nezmer an zdi',
'mergehistory-submit' => 'Azday n ileqman',
'mergehistory-empty' => 'Ulac lqem i nezmer an zdi.',
+'mergehistory-success' => '$3 {{PLURAL:$3|lqem|ileqman}} n [[:$1]] {{PLURAL:$3|yezdukel|zdukelen}} deg [[:$2]].',
+'mergehistory-fail' => 'Ulamek an zdukel imezruyen. Fru tikkelt nniḍen asebter d iɣewwaren is n uzmez.',
'mergehistory-no-source' => 'Azar n usebter $1 ulac-it.',
'mergehistory-no-destination' => 'Aserken n usebter $1 ulac-it',
'mergehistory-invalid-source' => 'Azar n usebter ilaq ad i sɛu azwel i ɣbelen.',
'mergehistory-invalid-destination' => 'Aserken n usebter ilaq ad i sɛu azwel i ɣbelen.',
+'mergehistory-autocomment' => '[[:$1]] yezdukel s [[:$2]]',
+'mergehistory-comment' => '[[:$1]] yezdukel s [[:$2]] : $3',
+'mergehistory-same-destination' => 'Asebter n azar d usebter n userken ur zemren ara ad illin d yiwen',
'mergehistory-reason' => 'Ayɣer',
# Merge log
'mergelog' => 'Aɣmis n izdayen',
+'pagemerge-logentry' => '[[$1]] yezdukel s [[$2]] (lqem alama d $3)',
'revertmerge' => 'Fru',
+'mergelogpagetext' => 'Attan tebdart n wesdukel umezruy usebtar deg win n usebtar nniḍen amaynut.',
# Diffs
'history-title' => 'Tiẓṛi tiss sint umezruy n "$1"',
'difference-multipage' => '(Tameẓla gar isebtar)',
'lineno' => 'Ajerriḍ $1:',
'compareselectedversions' => 'Ẓer imgerraden ger tisiwal i textareḍ',
+'showhideselectedversions' => 'Ssken/Ffer ileqman i xtiṛen',
'editundo' => 'ssefsu',
'diff-multi' => '({{PLURAL:$1|Yiwet tasiwelt tabusarit|$1 n tisiwal tibusarin}} af {{PLURAL:$2|amseqdac|$2 imseqdacen}} {{PLURAL:$1|ur ttumlal ara|ur ttumlalent ara}})',
+'diff-multi-manyusers' => '({{PLURAL:$1|Yiwen lqem agrawan|$1 ileqman igrawanen}} af {{PLURAL:$2|aseqdac|$2 iseqdacen}} {{PLURAL:$1|yeffer|ffren}})',
+'difference-missing-revision' => '{{PLURAL:$1|Yiwet tacaggart|$1 ticaggartin}} n tameẓla agi ($1) {{PLURAL:$2|ur tella ara (ulac)|ur llant ara (ulac)}}.
+
+Acku azday n tameẓla, ɣef wayen tsennedeḍ, d-aqbur. Asebter yemḥa.
+Tzemreḍ ad affeḍ tilɣa deg [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} uɣmis n isebtar yekksen].',
# Search results
'searchresults' => 'Igmad n unadi',
'searchresulttext' => 'Akken ad tessneḍ amek ara tnadiḍ deg {{SITENAME}}, ẓer [[{{MediaWiki:Helppage}}|{{int:help}}]].',
'searchsubtitle' => "Tnudaḍ « '''[[:$1]]''' » ([[Special:Prefixindex/$1|akkw isebtar i zwiren s « $1 »]]{{int:pipe-separator}}[[Special:WhatLinksHere/$1|Akkw isebtar yesɛan azday ɣer « $1 »]])",
'searchsubtitleinvalid' => "Tnadiḍ ɣef '''$1'''",
+'toomanymatches' => 'Teceggeɛeḍ amḍan ameqqṛan n igemmaḍ, ilaq ad ceggeɛeḍ tuttra nniḍen.',
'titlematches' => 'Ayen yecban azwel n umegrad',
'notitlematches' => 'Ulac ayen yecban azwel n umegrad',
'textmatches' => 'Ayen yecban azwel n usebter',
'searchmenu-new' => "'''Snulfud asebter « [[:$1|$1]] » deg wiki agi !'''",
'searchhelp-url' => 'Help:Agbur',
'searchmenu-prefix' => '[[Special:PrefixIndex/$1|Nadi isebtar i zwaren s adat agi]]',
-'searchprofile-articles' => 'Isebtaren n ugbur',
+'searchprofile-articles' => 'Isebtar n ugbur',
'searchprofile-project' => 'Isebtaren n tallat dɣa n usenfa',
'searchprofile-images' => 'Agetmedia',
'searchprofile-everything' => 'Akk',
'searchprofile-advanced-tooltip' => 'Fren ideggen n isemawen i unadi',
'search-result-size' => '$1 ({{PLURAL:$2|1 awal|$2 awalen}})',
'search-result-category-size' => '$1 {{PLURAL:$1|amseqdac|imseqdacen}} $2 ({{PLURAL:$2|adu-taggayt|adu-tiggayin}}, $3 {{PLURAL:$3|afaylu|ifuyla}})',
+'search-result-score' => 'Taflest : $1%',
'search-redirect' => '(asemmimeḍ $1)',
'search-section' => '(tigezmi $1)',
'search-suggest' => 'D awal $1 i tnadiḍ ?',
'powersearch' => 'Anadi amahlan',
'powersearch-legend' => 'Anadi amahlan',
'powersearch-ns' => 'Nadi deg tallunin n isemawen',
-'powersearch-redir' => 'Beqqeḍ alsinamuden',
+'powersearch-redir' => 'Beqqeḍ isemmimḍen',
'powersearch-field' => 'Nadi',
'powersearch-togglelabel' => 'Ɛellem :',
'powersearch-toggleall' => 'Akkw',
'columns' => 'Tigejda:',
'searchresultshead' => 'Anadi',
'resultsperpage' => 'Geddac n tiririyin i mkul asebter:',
+'stub-threshold' => 'Talast timinegt i <a href="#" class="stub">izdayen ɣer ibegzan</a> (itamḍanen) :',
'stub-threshold-disabled' => 'Yensa',
+'recentchangesdays' => 'Amḍan n ussan an beqqeḍ deg ibeddilen ineggura.',
'recentchangesdays-max' => 'Afellay $1 {{PLURAL:$1|ass|ussan}}',
'recentchangescount' => 'Amḍan n ibeddilen i ubeqqeḍ s lexṣas :',
+'prefs-help-recentchangescount' => 'Wagi yesɛa deg-es ibeddilen ineggura, isebtar n umezruy d iɣmisen.',
+'prefs-help-watchlist-token' => 'Ččuṛ taɣzut agi s azal lbaḍna dɣa asuddem RSS ad yetwarew i umuɣ inek/inem n uɛassi.
+Akkw amdan yesnen tiddest agi ad yezmer ad i ɣeṛ umuɣ inek/inem n uɛassi, ihi ilaq ad xtiṛeḍ azal yegdelen.
+Ha-t-an azal agacuran i tzemreḍ ad seqdeceḍ : $1',
'savedprefs' => 'Isemyifiyen inek yettusmektan.',
'timezonelegend' => 'Iẓḍi n ukud :',
'localtime' => 'Asrag adigan :',
'prefs-custom-css' => 'CSS asagen',
'prefs-custom-js' => 'JavaScript asagen',
'prefs-common-css-js' => 'JavaScript d CSS azduklan i akkw lebsa :',
+'prefs-reset-intro' => 'Tzemreḍ ad seqdeceḍ asebter agi iwakken ad erreḍ iɣewwaren inek/inem ar azalen n lexṣas n usmel.
+Wagi ur yezmer ara ad yetwekkes.',
+'prefs-emailconfirm-label' => 'Aragag n tirawt :',
+'prefs-textboxsize' => 'Tiddi n usfaylu n ubeddel',
'youremail' => 'E-mail *:',
'username' => 'Isem n wemseqdac:',
'uid' => 'Amseqdac ID:',
+'prefs-memberingroups' => 'Aεeggal n {{PLURAL:$1|ugraw|igrawen}} :',
+'prefs-registration' => 'Azmez n tiggezt :',
'yourrealname' => 'Isem n ṣṣeḥ *:',
'yourlanguage' => 'Tutlayt:',
'yourvariant' => 'Lqem nniḍen n tutlayt n ugbur :',
+'prefs-help-variant' => 'Lqem naɣ inun inek/inem iwakken an beqqeḍ agbur n wiki agi.',
'yournick' => 'Azmul amaynut :',
+'prefs-help-signature' => 'Iwenniten ɣef isebtar n umeslay ilaq ad illin zmelen s « <nowiki>~~~~</nowiki> », sakin ad i sɛu aselkat ɣer azmul inek/inem dɣa azmez d usrag.',
'badsig' => 'Azmul mačči d ṣaḥiḥ; Ssenqed tags n HTML.',
+'badsiglength' => 'Azmul inek/inem, teɣwzi-s tameqqṛant aṭas.
+Ur ilaq ara ad i sɛu ugar n $1 {{PLURAL:$1|asekkil|isekkilen}}.',
'yourgender' => 'Tawsit :',
'gender-unknown' => 'Ulac tumlin',
'gender-male' => 'Amalay',
'gender-female' => 'Untay',
+'prefs-help-gender' => 'Axetṛan : yetseqdec iwakken ad yefk tawsit i inzan n ugrudem. Talɣut agi at illi tazayert.',
'email' => 'E-mail',
'prefs-help-realname' => '* Isem n ṣṣeḥ (am tebɣiḍ): ma textareḍ a t-tefkeḍ, ad yettuseqdac iwakken ad snen medden anwa yura tikkin inek.',
'prefs-help-email' => '* E-mail (am tebɣiḍ): Teǧǧi imseqdacen wiyaḍ a k-aznen email mebla ma ẓren tansa email inek.',
'prefs-displaywatchlist' => 'Tixtiṛiyin n ubeqqeḍ',
'prefs-diffs' => 'Timeẓliwin',
+# User preference: e-mail validation using jQuery
+'email-address-validity-valid' => 'E-mail agi teɣbel',
+'email-address-validity-invalid' => 'Telaq tansa e-mail i ɣbelen !',
+
# User rights
'userrights' => 'Laɛej iserfan n wemseqdac',
'userrights-lookup-user' => 'Laɛej iderman n yimseqdacen',
'userrights-editusergroup' => 'Beddel iderman n wemseqdac',
'saveusergroups' => 'Smekti iderman n yimseqdacen',
'userrights-groupsmember' => 'Amaslad deg:',
+'userrights-groupsmember-auto' => 'Aεeggal udrig n :',
+'userrights-groups-help' => 'Tzemreḍ ad beddeleḍ igrawen anda yella aseqdac agi :
+* Taxxamt i tekkin : aseqdac yella deg ugraw agi.
+* Taxxamt ur tekkin ara : aseqdac ur yella ara deg ugraw agi
+* Titrit (*) : ur tzemreḍ ara ad ekkeseḍ agraw agi sakin i tid ernuḍ, naɣ bis-bersa.',
'userrights-reason' => 'Ayɣer',
+'userrights-no-interwiki' => 'Ur tesɛiḍ ara turagt iwakken ad beddeleḍ izerfan n iseqdacen ɣef wiki nniḍen.',
+'userrights-nodatabase' => 'Taffa n isefka « $1 » ulac itt naɣ mačči d tadigant.',
+'userrights-nologin' => 'Ilaq ad [[Special:UserLogin|qqeneḍ]] s yiwen umiḍan anedbal iwakken ad beddeleḍ izerfan n useqdac.',
+'userrights-notallowed' => 'Amiḍan inek/inem ur yesɛa ara turagt iwakken ad beddeleḍ izerfan n useqdac.',
'userrights-changeable-col' => 'Igrawen i tzemreḍ ad beddeleḍ',
'userrights-unchangeable-col' => 'Igrawen ur tzemreḍ ara ad beddeleḍ',
'right-move-subpages' => 'Beddel isem n isebtar d adu-isebtar nsen',
'right-move-rootuserpages' => 'Beddel isem n usebtar amenzawi n useqdac',
'right-movefile' => 'Beddel isem n ifuyla',
+'right-suppressredirect' => 'Ur snulfu ara asemmimeḍ seg azwel amezwaru s ubeddel n isem usebter',
'right-upload' => 'Azen ifuyla',
'right-reupload' => 'Sefxes afaylu yellan',
'right-reupload-own' => 'Sefxes afaylu id n-azen.',
+'right-reupload-shared' => 'Ɛefes deg udigan afaylu yellan ɣef azadur azduklan',
+'right-upload_by_url' => 'Kter afaylu seg tansa URL',
+'right-purge' => 'Senger tazarkatut n isebtar war asuter n uragag',
+'right-autoconfirmed' => 'Beddel isebtar azinsegdelen',
+'right-bot' => 'Ad yilli yesniret am ukala yeswurmen',
+'right-nominornewtalk' => 'Ur ndeḥ ara tazmilt n inzan imaynuten ma neseqdac abeddel amectuḥ ɣef usebtar n umeslay n yiwen useqdac',
+'right-apihighlimits' => 'Seqdec tilisa tid ɛlayen deg tuttriwin API',
+'right-writeapi' => 'Seqdec API n ubeddel',
+'right-delete' => 'Mḥu isebtar',
+'right-bigdelete' => 'Mḥu isebtar yesɛan amezruy affuyan',
+'right-deletelogentry' => 'Ekkes ḍɣa erred yiwen asekcem n uɣmis',
+'right-deleterevision' => 'Ekkes dɣa erred yiwen lqem n usebter',
+'right-deletedhistory' => 'Ẓeṛ isekcam n imezruyen yekksen, maca war aḍris nsen',
+'right-deletedtext' => 'Ẓeṛ aḍris yemḥan d timeẓliwin gar ileqman yemḥan',
+'right-browsearchive' => 'Nadi ɣef isebtar yettumḥan',
+'right-undelete' => 'Erred asebter yemḥan',
+'right-suppressrevision' => 'Ssekyed dɣa erred ileqman yefren i inedbalen',
+'right-suppressionlog' => 'Ẓeṛ iɣmisen usligen',
+'right-block' => 'Kyef deg tira iseqdacen nniḍen',
+'right-blockemail' => 'Sḍiqqef aceggaɛ n tira (e-mail) i yiwen useqdac',
+'right-hideuser' => 'Kyef aseqdac s tuffra n isem-is ar udem n uzayez',
+'right-ipblock-exempt' => 'Zizdew tansiwin IP yekyefen, ikyafen iwurmanen d ikyafen n tagrummiwin IP',
+'right-proxyunbannable' => 'Zizdew ikyafen iwurmanen n iqeddacen proxy',
+'right-unblockself' => 'Ad ekkesen akyaf imanen nsen',
+'right-protect' => 'Beddel aswir n umesten n isebtar dɣa beddel isebtar i gdelen',
+'right-editprotected' => 'Beddel isebtar i gdelen (war asegdel s uceṛcuṛ)',
+'right-editinterface' => 'Beddel agrudem n useqdac',
+'right-editusercssjs' => 'Beddel ifuyla CSS d JavaScript n iseqdacen nniḍen',
+'right-editusercss' => 'Beddel ifuyla CSS n iseqdacen nniḍen',
+'right-edituserjs' => 'Beddel ifuyla JavaScript n iseqdacen nniḍen',
+'right-rollback' => 'Ekkes s urured ibeddilen n umedraw aneggaru deg yiwen asebter',
+'right-markbotedits' => 'Creḍ ibeddilen yetwekkesen am aken d aṛubut i tni beddelen.',
+'right-noratelimit' => 'Ur i tilli ara yeswaɣ sɣur tilisa n utug',
+'right-import' => 'Kter ifuyla seg iWikiyen nniḍen',
+'right-importupload' => 'Azen isebtar seg ufaylu',
+'right-patrol' => 'Creḍ ibeddilen n wiyaḍ nniḍen am aken selkenen',
+'right-autopatrol' => 'Ad i sɛu ibeddilen is creḍen s uwurman am aken ɛessan',
+'right-patrolmarks' => 'Ẓeṛ ticraḍ n uɛassi deg ibeddilen imaynuten',
+'right-unwatchedpages' => 'Ẓeṛ umuɣ n isebtar ur sɛan ara iɛssasen',
+'right-mergehistory' => 'Sdukel amezruy n isebtar',
+'right-userrights' => 'Beddel akkw izerfan n yiwen aseqdac',
+'right-userrights-interwiki' => 'Beddel izerfan n iseqdacen yellan deg awiki nniḍen',
+'right-siteadmin' => 'Sekkweṛ naɣ kkes aseḍru i taffa n isefka',
+'right-override-export-depth' => 'Sifeḍ isebtar akkw d isebtar iqqenen alama tadrut n 5 iswiren',
+'right-sendemail' => 'Ceggaɛ tirawt i iseqdacen nniḍen',
+'right-passwordreset' => 'Ẓeṛ tira n uwennez n awalen uɛaddi',
# User rights log
'rightslog' => 'Aɣmis n yizerfan n wemseqdac',
'rightslogtext' => 'Wagi d aɣmis n yibeddlen n yizerfan n wemseqdac',
'rightslogentry' => 'Yettubeddel izerfan n wemseqdac $1 seg $2 ar $3',
+'rightslogentry-autopromote' => 'yesnerna s uwurman seg $2 ar $3',
'rightsnone' => '(ulaḥedd)',
# Associated actions - in the sentence "You do not have permission to X"
'action-movefile' => 'beddel isem n ufaylu agi',
'action-upload' => 'Azen afaylu agi',
'action-reupload' => 'Sefxes afaylu yellan',
+'action-reupload-shared' => 'fel deg udigan afaylu agi yellan ɣef azadur azduklan',
'action-upload_by_url' => 'Azen afaylu agi seg tansa URL',
'action-writeapi' => 'seqdec API n tira',
'action-delete' => 'mḥu asebter-agi',
'action-deletedhistory' => 'ẓeṛ amezruy yemḥan n usebter agi',
'action-browsearchive' => 'nadi ɣef isebtar yettumḥan',
'action-undelete' => 'erred asebter agi',
+'action-suppressrevision' => 'sekyed dɣa uɣaled ar lqem agi yetwekkesen',
'action-suppressionlog' => 'ẓeṛ aɣmis agi uslig',
'action-block' => 'Kyef deg tira aseqdac agi',
'action-protect' => 'beddel iswiren n umesten i usebter agi',
+'action-rollback' => 'ekkes s urured ibeddilen n umedraw aneggaru yebeddelen yiwen usebter',
'action-import' => 'Kter asebter agi seg wiki nniḍen',
'action-importupload' => 'Kter asebter agi seg ufaylu n wezdam (upload)',
+'action-patrol' => 'Creḍ abeddel n wiyaḍ nniḍen am aken tesɛa tacaggart',
+'action-autopatrol' => 'ad sɛuḍ tacaggart i ubeddil ik',
'action-unwatchedpages' => 'Sken-d tabdart n isebtaren ur yettwalan ara.',
'action-mergehistory' => 'Sdukel amezruy n usebtar agi',
'action-userrights' => 'Ẓreg izerfan n imseqdacen yark',
'action-userrights-interwiki' => 'Ẓreg izerfan n umseqdac deg wikis wiyaḍ',
+'action-siteadmin' => 'sekkweṛ naɣ kkes aseḍru i taffa n isefka',
+'action-sendemail' => 'Ceggaɛ tira',
# Recent changes
'nchanges' => '$1 {{PLURAL:$1|Abeddel|Ibeddlen}}',
'number_of_watching_users_pageview' => '[$1 {{PLURAL:$1|aɛessas|iɛessasen}}]',
'rc_categories' => 'Ḥedded i taggayin (ferreq s "|")',
'rc_categories_any' => 'Ulayɣer',
+'rc-change-size-new' => '$1 {{PLURAL:$1|atamḍan|itamḍanen}} sakin abeddel',
+'newsectionsummary' => '/* $1 */ tigezmi tamaynut',
'rc-enhanced-expand' => 'Ẓeṛ tilɣa (yeḥwaǧ JavaScript)',
'rc-enhanced-hide' => 'Ffer tilɣa',
+'rc-old-title' => 'yesnulfad s uzwel « $1 »',
# Recent changes linked
'recentchangeslinked' => 'Ibeddlen imaynuten n isebtar myezdin',
'upload' => 'Azen afaylu',
'uploadbtn' => 'Azen afaylu',
'reuploaddesc' => 'Semmewet dɣa uɣaled ar tiferkit n tuznin.',
+'upload-tryagain' => 'Ceggaɛ aglam n ufaylu ibeddelen',
'uploadnologin' => 'Ur tekcimeḍ ara',
'uploadnologintext' => 'Yessefk [[Special:UserLogin|ad tkecmeḍ]]
iwakken ad tazneḍ afaylu.',
+'upload_directory_missing' => 'Akaram n taktert n ufaylu ($1) ulac-it dɣa ur d-yesnulfa ara sɣur aqeddac web.',
'upload_directory_read_only' => 'Weserver/serveur Web ur yezmir ara ad yaru deg ($1).',
'uploaderror' => 'Agul deg usekcam',
+'upload-recreate-warning' => "'''Ɣur-wet : Afaylu s isem agi yetwekkes naɣ yetembiwel.'''
+Aɣmis n tukksiwin d win n ittembiwilen n usebter agi beqqeḍen d-agi i tilɣa :",
'uploadtext' => "Sseqdec tiferkit agi iwakken ad ktereḍ ifuyla ɣef uqeddac.
Iwakken ad ẓṛeḍ naɣ ad nadiḍ tugniwin i ktren uqbel, ẓeṛ [[Special:FileList|umuɣ n tugniwin]]. Taktert tella daɣen deg [[Special:Log/upload|aɣmis n taktert n ifuyla]], dɣa inuzal deg [[Special:Log/delete|aɣmis n inuzal]].
'illegalfilename' => 'Isem n ufaylu "$1" yesɛa isekkilen ur tettalaseḍ ara a ten-tesseqdceḍ deg yizwal n isebtar. G leɛnayek beddel isem n ufaylu u azen-it tikkelt nniḍen.',
'filename-toolong' => 'Isem ufaylu ilaq ad yesεu m-ay aṭas 240 iṭamḍanen (bytes).',
'badfilename' => 'Isem ufaylu yettubeddel ar "$1".',
+'filetype-mime-mismatch' => 'Asiɣzef n ufaylu « .$1 » ur yesɛa ara tuqqna s tawsit MIME id n-ufa deg ufaylu ($2).',
'filetype-badmime' => 'Ur tettalaseḍ ara ad tazneḍ ufayluwen n anaw n MIME "$1".',
+'filetype-bad-ie-mime' => 'Afaylu ur yezmer ara ad yetwekter acku yetwaf am « $1 » sɣur Internet Explorer. Tawsit agi d tazanbagt acku d tamihawt.',
+'filetype-unwanted-type' => "'''« .$1 »''' d amasal n ufaylu azanbag.
+Ilaq ad seqdeceḍ {{PLURAL:$3|amasal|imusal}} $2.",
+'filetype-banned-type' => "''' « .$1 » '''mačči d {{PLURAL:$4|amasal yesɛan turagt|imusal yesɛan turagt}}.
+{{PLURAL:$3|Amasal yesɛan turagt d-wagi :|Imusal yesɛan turagt d-wigi :}} $2.",
'filetype-missing' => 'Afaylu ur yesɛi ara taseggiwit (am ".jpg").',
'empty-file' => 'Afaylu id cegɛeḍ d-ilem.',
'file-too-large' => 'Afaylu id cegɛed d-ameqqṛan aṭas.',
'windows-nonascii-filename' => 'Wiki agi ur yebra ara isemawen n ifuyla s isekkilen usligen.',
'fileexists' => 'Afaylu s yisem-agi yewǧed yagi, ssenqed <strong>[[:$1]]</strong> ma telliḍ mačči meḍmun akken a t-tbeddleḍ.
[[$1|thumb]]',
+'filepageexists' => 'Asebter n uglam i ufaylu agi yesnulfad yakan d-agi <strong>[[:$1]]</strong>, maca ulac asebter s isem agi.
+Agzul ad efkeḍ tura ur d yettban ara ɣef asebter n uglam.
+Ma tebɣiḍ ad yeban, ilaq ad beddeleḍ s awfus asebter. [[$1|thumb]]',
'fileexists-extension' => 'Afaylu s yisem yecban wagi yella : [[$2|thumb]]
* Isem n ufaylu i tezneḍ: <strong>[[:$1]]</strong>
* Isem n ufaylu i yellan: <strong>[[:$2]]</strong>
Ma tebɣiḍ ad azeneḍ afaylu inek/inem, ilaq ad uɣaleḍ ar deffir dɣa ad as efkeḍ isem amaynut.
[[File:$1|thumb|center|$1]]',
'file-exists-duplicate' => 'Afaylu agi d-asleg n {{PLURAL:$1|ufaylu agi|ifuyla agi}} :',
+'file-deleted-duplicate' => 'Afaylu am wagi ([[:$1]]) yetwekkes yakan. Ilaq ad selkeneḍ aɣmis n tukksiwin n ufaylu agi uqbel atid ktereḍ tikkelt nniḍen.',
'uploadwarning' => 'Aɣtal deg wazan n ufayluwen',
+'uploadwarning-text' => 'Beddel aglam n ufaylu dɣa ɛreḍ tikkelt nniḍen',
'savefile' => 'Smekti afaylu',
'uploadedimage' => '"[[$1]]" yettwazen',
+'overwroteimage' => 'yekter lqem amaynut n « [[$1]] »',
'uploaddisabled' => 'Suref-aɣ, azen n ufayluwen yettwakkes',
+'copyuploaddisabled' => 'Taktert n ufaylu s URL yensan.',
+'uploadfromurl-queued' => 'Afaylu inek/inem yekcem deg udras n ugani.',
'uploaddisabledtext' => 'Azen n ifuyla yettwakkes deg wiki agi.',
+'php-uploaddisabledtext' => 'Taktert n ifuyla tensa deg PHP. Selken taxtiṛit n tawila file_uploads.',
'uploadscripted' => 'Afaylu-yagi yesɛa angal n HTML/script i yexdem agula deg browser/explorateur.',
'uploadvirus' => 'Afaylu-nni yesɛa anfafad asenselkim (virus)! Ẓer kter: $1',
+'uploadjava' => 'Wagi d afaylu ZIP yesɛan afaylu Java .class.
+Azdam n ifuyla Java ur yesɛa ara turagt, acku zemren ad zizdewen ikyafen n taɣellist.',
+'upload-source' => 'Afaylu aɣbalu',
'sourcefilename' => 'Isem n ufaylu aɣbalu :',
'sourceurl' => 'URL aγbalu',
'destfilename' => 'Isem n ufaylu deg aserken',
+'upload-maxfilesize' => 'Tiddi tafellayt n ufaylu : $1',
+'upload-description' => 'Aglam n ufaylu',
+'upload-options' => 'Tixtiṛiyin n taktert ifuyla',
'watchthisupload' => 'Ɛass asebter agi',
'filewasdeleted' => 'Afaylu s yisem-agi yettwazen umbeɛd yettumḥa. Ssenqed $1 qbel ad tazniḍ tikelt nniḍen.',
+'filename-bad-prefix' => "Isem n ufaylu yezwer s '''« $1 »''', wagi d isem i sedgeren s uwurman sɣur timsakenwin tumḍinin.
+Xteṛ isem n ufaylu agelmaw.",
'upload-success-subj' => 'Azen yekfa',
+'upload-success-msg' => 'Taktert inek/inem seg [$2] yesmures. Af-it d-agi : [[:{{ns:file}}:$1]]',
+'upload-failure-subj' => 'Ugur n taktert',
+'upload-failure-msg' => 'Yella ugur s taktert inek/inem seg [$2] :
+
+$1',
+'upload-warning-subj' => 'Alɣu deg taktert',
+'upload-warning-msg' => 'Yella ugur s taktert seg [$2]. Tzemreḍ ad uɣaleḍ ar [[Special:Upload/stash/$1|tiferkit n taktert]] iwakken ad fruḍ ugur agi.',
'upload-proto-error' => 'Agul deg protokol',
'upload-proto-error-text' => 'Assekcam yenṭerr URL i yebdan s <code>http://</code> neɣ <code>ftp://</code>.',
'upload-misc-error-text' => 'Anezri warisem yegweḍeḍ asmi yettwazen afaylu.
Ilaq ad selkeneḍ ma URL nni teɣbel, dɣa ɛreḍ tikkelt nniḍen.
Ma yella daɣen anezri, ilaq ad meslaye ḍ s [[Special:ListUsers/sysop|unedbal]].',
+'upload-too-many-redirects' => 'URL agi yesɛa aṭas illusen n wesnili.',
+'upload-unknown-size' => 'Tiddi warisem',
+'upload-http-error' => 'Anezri HTTP : $1',
+'upload-copy-upload-invalid-domain' => 'Anɣel n izdamen ur yezmer ara seg taɣult agi.',
+
+# File backend
+'backend-fail-stream' => 'Ulamek an ɣeṛ afaylu $1.',
+'backend-fail-backup' => 'Ulamek an ḥrez afaylu $1.',
+'backend-fail-notexists' => 'Afaylu $1 ulac it.',
+'backend-fail-hashes' => 'Ulamek an sɛu idwayen n ufaylu i usnemhel.',
+'backend-fail-notsame' => 'Afaylu imeẓli yella yakan i $1.',
+'backend-fail-invalidpath' => '$1 mačči d abrid n uḥraz iɣbelen.',
+'backend-fail-delete' => 'Ulamek an mḥu afaylu "$1".',
+'backend-fail-alreadyexists' => 'Afaylu $1 yella yakan.',
+'backend-fail-store' => 'Ulamek an ḥrez afaylu $1 deg $2.',
+'backend-fail-copy' => 'Ulamek an nɣel afaylu $1 deg $2.',
+'backend-fail-move' => 'Ulamek an sekḥer afaylu $1 deg $2.',
+'backend-fail-opentemp' => 'Ulamek an ldi afaylu akudan.',
+'backend-fail-writetemp' => 'Ulamek an aru deg ufaylu akudan.',
+'backend-fail-closetemp' => 'Ulamek an mdel afaylu akudan.',
+'backend-fail-read' => 'Ulamek an ɣeṛ afaylu "$1".',
+'backend-fail-create' => 'Ulamek an aru afaylu "$1".',
+'backend-fail-maxsize' => 'Ulamek an aru afaylu "$1" acku yugar {{PLURAL:$2|yiwen atamḍan|$2 itamḍanen}}.',
+'backend-fail-readonly' => 'Tannalt n uḥraz "$1" yella deg taɣuri kan. Taɣzint id yefka : "$2"',
+'backend-fail-synced' => 'Afaylu "$1" yesɛa addad azanbag deg tannalin n uḥraz tigensanin',
+'backend-fail-connect' => 'Ulamek an qqen ar tannalt n uḥraz "$1".',
+'backend-fail-internal' => 'Yella anezri warisem deg tannalt n uraz "$1".',
+'backend-fail-contenttype' => 'Ulamek ad n efk tawsit n ugbur n ufaylu an ḥrez deg "$1".',
+'backend-fail-batchsize' => 'Tannalt n uḥraz yefkad akemmus n $1 {{PLURAL:$1|tamhelt|timehlin}} n ufaylu ; talast tella ar $2 {{PLURAL:$2|tamhelt|timehlin}}.',
+'backend-fail-usable' => 'Ulamek an ɣeṛ naɣ an aru afaylu « $1 » acku drus izerfan naɣ ikaramen xusen.',
+
+# File journal errors
+'filejournal-fail-dbconnect' => 'Ulamek an qqen ar taffa n isefka n uɣmis i ixf n uḥraz "$1".',
+'filejournal-fail-dbquery' => 'Ulamek an mucceḍ taffa n isefka n uɣmis i ixf n uḥraz "$1".',
+
+# Lock manager
+'lockmanager-notlocked' => 'Ulamek an kkes aseḍru « $1 » ; ur yesɛa ara asekkweṛ.',
+'lockmanager-fail-closelock' => 'Ulamek an mdel afaylu n uzekṛun i « $1 ».',
+'lockmanager-fail-deletelock' => 'Ulamek an mḥu afaylu n uzekṛun i « $1 ».',
+'lockmanager-fail-acquirelock' => 'Ulamek an krez azekṛun i « $1 ».',
+'lockmanager-fail-openlock' => 'Ulamek an ldi afaylu n uzekṛun i « $1 ».',
+'lockmanager-fail-releaselock' => 'Ulamek an bru azekṛun i « $1 ».',
+'lockmanager-fail-db-bucket' => 'Ulamek an siwel ddeqs taffa n isefka n usekkweṛ deg uqbuc $1.',
+'lockmanager-fail-db-release' => 'Ulamek an bru izekṛunen ɣef taffa n isefka $1.',
+'lockmanager-fail-svr-acquire' => 'Ulamek an krez izekṛunen ɣef uqeddac $1.',
+'lockmanager-fail-svr-release' => 'Ulamek an bru izekṛunen ɣef uqeddac $1.',
+
+# ZipDirectoryReader
+'zip-file-open-error' => 'Yella agul mi d neldi afaylu i senqeden ifuyla zip.',
+'zip-wrong-format' => 'Afaylu agi mačči d afaylu n weɣbaṛ ZIP.',
+'zip-bad' => 'Afaylu agi d afaylu n weɣbaṛ ameggafsu naɣ ur nezmer ara an ɣaṛ deg-es.
+Ur nezmer ara aten selken i taɣellist.',
+'zip-unsupported' => 'Afaylu agi d afaylu n weɣbaṛ i seqdacen tiɣariwin ur yeḥemmel ara MediaWiki.
+Taɣellist ines ur tezmer ara at illi teseklen.',
+
+# Special:UploadStash
+'uploadstash' => 'Tazarkatut n taktert',
+'uploadstash-summary' => 'Asebter agi yetefk addaf i ifuyla yekteren (naɣ yesɛan taktert tanazzalt), maca mazal i beqqeḍen deg wiki. Ifuyla agi mazal id banen, ḥaca i useqdac i tni kteren.',
+'uploadstash-clear' => 'Sfeḍ ifuyla deg tazarkatut',
+'uploadstash-nofiles' => 'Ur tesɛiḍ ara ifuyla deg tazarkatut n taktert',
+'uploadstash-badtoken' => 'Aselkem n tigawt agi yexseṛ, ahat acku tilɣa inek/inem n usulu gweḍent ar tasewti nsent. Ɛreḍ tikkelt nniḍen.',
+'uploadstash-errclear' => 'Asfeḍ n ifuyla yefkad taruẓi',
+'uploadstash-refresh' => 'Mucceḍ umuɣ n ifuyla',
+'invalid-chunk-offset' => 'Tiggit n iɣil ur teɣbel ara',
+
+# img_auth script messages
+'img-auth-accessdenied' => 'Addaf yugwi',
+'img-auth-nopathinfo' => 'Yexus BATH_INFU.
+Aqeddac inek/inem ur yeseɣwer ara iwakken ad i ɛeddi talɣut agi.
+Ahat i lḥu s CGI dɣa ur s-yezmer ara i img_auth.
+Ẓeṛ https://www.mediawiki.org/wiki/Manual:Image_Authorization.',
+'img-auth-notindir' => 'Abrid yesuteren mačči d akaram n taktert yellan deg tawila.',
+'img-auth-badtitle' => 'Ulamek an ssali azwel i ɣbelen seg « $1 ».',
+'img-auth-nologinnWL' => 'Ur teqqneḍ ara dɣa « $1 » ur yella ara deg umuɣ amellal.',
+'img-auth-nofile' => 'Afaylu « $1 » ulac it.',
+'img-auth-isdir' => 'Tɛerdeḍ ad ldiḍ akaram « $1 ».
+Tzemreḍ kan ad ldiḍ ifuyla.',
+'img-auth-streaming' => 'Taɣuri tamaɣlalt n « $1 ».',
+'img-auth-public' => 'Tasɣent n img_auth.php tella i ubeqqeḍ n ifuyla n yiwen wiki uslig.
+Wiki agi yesɣwer am wiki azayez.
+I taɣellist tameqqṛant, img_auth.php yensa.',
+'img-auth-noread' => 'Aseqdac ur yesɛa ara azref deg taɣuri ɣef « $1 ».',
+'img-auth-bad-query-string' => 'URL tesɛa azrar n tuttra ur i ɣbelen ara.',
+
+# HTTP errors
+'http-invalid-url' => 'URL ur teɣbel ara : $1',
+'http-invalid-scheme' => 'URL s uzenziɣ « $1 » ur ɣbelen ara d-agi.',
+'http-request-error' => 'Anezri warisem deg uceggaɛ n tuttra.',
+'http-read-error' => 'Anezri n taɣuri HTTP.',
+'http-timed-out' => 'Tuttra HTTP teneffeṛ.',
+'http-curl-error' => 'Anezri deg tiririt n URL : $1',
+'http-host-unreachable' => 'Ulamek an siḍes URL',
+'http-bad-status' => 'Yella ugur deg tuttra HTTP : $1 $2',
# Some likely curl errors. More could be added from <http://curl.haxx.se/libcurl/c/libcurl-errors.html>
'upload-curl-error6' => 'Ur yezmir ara ad yessglu URL',
'license' => 'Turagt',
'license-header' => 'Turagt',
'nolicense' => 'Ur textareḍ acemma',
+'license-nopreview' => '(Azarskan ur yestufa ara)',
'upload_source_url' => ' (URL saḥiḥ)',
'upload_source_file' => ' (afaylu deg uselkim inek)',
# Special:ListFiles
+'listfiles-summary' => 'Asebter agi uslig i εemmed ad yefk umu n akkw ifuyla i kteren.
+Ma aseqdac as yernu tastayt, ala ifuyla s lqem taneggarut id yekter aseqdac nni ad beqqeḍen.',
'listfiles_search_for' => 'Nadi ɣef yisem n tugna:',
'imgfile' => 'afaylu',
'listfiles' => 'Umuɣ n tugniwin',
+'listfiles_thumb' => 'Aqmamaḍ',
'listfiles_date' => 'Azemz',
'listfiles_name' => 'Isem',
'listfiles_user' => 'Amseqdac',
'listfiles_size' => 'Tiddi (bytes/octets)',
'listfiles_description' => 'Aglam',
+'listfiles_count' => 'Ileqman',
# File description page
'file-anchor-link' => 'Afaylu',
'filehist' => 'Amazray n tugna',
'filehist-help' => 'Senned ɣef yiwen azmez d usrag iwakken ad ẓṛeḍ afaylu aken yella deg imir nni.',
+'filehist-deleteall' => 'ekkes akkw',
+'filehist-deleteone' => 'ekkes',
'filehist-revert' => 'Uɣal ar tasiwelt ssabeq',
'filehist-current' => 'Lux a',
'filehist-datetime' => 'Azmez/Asrag',
-'filehist-thumb' => 'Tugna tamecṭuḥt',
+'filehist-thumb' => 'Aqmamaḍ',
'filehist-thumbtext' => 'Tugna tamectuḥt i lqem n $1',
+'filehist-nothumb' => 'Ulac aqmamaḍ',
'filehist-user' => 'Amseqdac',
'filehist-dimensions' => 'Iseggiwen',
+'filehist-filesize' => 'Tiddi n ufaylu',
'filehist-comment' => 'Awennit',
+'filehist-missing' => 'Afaylu ulac-it',
'imagelinks' => 'Izdayen',
'linkstoimage' => '{{PLURAL:$1|Asebter agi teseqdac|$1 isebtaren agi teseqdacen}} afaylu agi :',
+'linkstoimage-more' => 'Ugar n {{PLURAL:$1|yiwen asebter|$1 isebtar}} tseqdacen afaylu agi.
+Umuɣ agi yebeqqeḍ ala {{PLURAL:$1|asebter amezwaru|$1 isebtar imezwura}} i seqdacen afaylu agi.
+Yella [[Special:WhatLinksHere/$2|umuɣ ummid]].',
'nolinkstoimage' => 'Ulaḥedd seg isebtar sɛan azday ar afaylu-agi.',
+'morelinkstoimage' => 'Ẓeṛ [[Special:WhatLinksHere/$1|izdayen nniḍen]] ɣer afaylu agi.',
+'linkstoimage-redirect' => '$1 (allus n wesnili n ufaylu) $2',
+'duplicatesoffile' => '{{PLURAL:$1|Afaylu agi d anɣel|Ifuyla agi d ineɣlan}} n ufaylu agi ([[Special:FileDuplicateSearch/$2|tilɣa timarnanin]]) :',
'sharedupload' => 'Afaylu agi yettuseqdac seg : $1. Yezmer ad yettuseqdac deg isenfaṛen nniḍen',
+'sharedupload-desc-there' => 'Afaylu agi yussad seg : $1. Yezmer ad yetseqdac sɣur isenfaren nniḍen.
+Ẓeṛ [$2 asebter is n uglam] ma tebɣiḍ tilɣa nniḍen.',
'sharedupload-desc-here' => 'Afaylu agi yusad seg : $1. Ahat yeseqdec deg isenfaṛen nniḍen.
Aglam-is ɣef [$2 asebter n aglam] ye beqqeḍ ddaw-agi.',
+'sharedupload-desc-edit' => 'Afaylu agi yussad seg : $1. Yezmer ad yetseqdac sɣur isenfaren nniḍen.
+Ahat tebɣiḍ ad beddeleḍ aglam is ɣef [$2 asebter is n uglam].',
+'sharedupload-desc-create' => 'Afaylu agi yussad seg : $1. Yezmer ad yetseqdac sɣur isenfaren nniḍen.
+Ẓeṛ [$2 asebter is n uglam] ma tebɣiḍ tilɣa nniḍen.',
+'filepage-nofile' => 'Ulac afaylu s isem agi.',
+'filepage-nofile-link' => 'Ulac afaylu s isem agi, maca tzemreḍ ad [$1 ktereḍ yiwen].',
'uploadnewversion-linktext' => 'tazneḍ tasiwelt tamaynut n ufaylu-yagi',
+'shared-repo-from' => 'seg : $1',
+'shared-repo' => 'azadur azduklan',
+'upload-disallowed-here' => 'Ur tzemreḍ ara ad semselsiḍ afaylu agi.',
+
+# File reversion
+'filerevert' => 'Erred $1',
+'filerevert-legend' => 'Erred afaylu',
+'filerevert-intro' => "Ha-ta-n ad uɣaleḍ ar ufaylu '''[[Media:$1|$1]]''' ar [$4 lqem n $2 af $3].",
+'filerevert-comment' => 'Taɣẓint :',
+'filerevert-defaultcomment' => 'Uɣaleḍ ar lqem n $1 af $2',
+'filerevert-submit' => 'Erred',
+'filerevert-success' => "'''[[Media:$1|$1]]''' yuɣaled ar [$4 lqem n $2 af $3].",
+'filerevert-badversion' => 'Ulac, deg udigan, lqem aqbur n ufaylu yesɛan azmez agi.',
+
+# File deletion
+'filedelete' => 'Kkes $1',
+'filedelete-legend' => 'Kkes asebter',
+'filedelete-intro' => "Ha-ta-n ad ekkeseḍ '''[[Media:$1|$1]]'' akkw d umezruy is.",
+'filedelete-intro-old' => "Ha-ta-n ad mḥuḍ lqem n '''[[Media:$1|$1]]''' n [$4 $2 af $3].",
+'filedelete-comment' => 'Taɣẓint :',
+'filedelete-submit' => 'Ekkes',
+'filedelete-success' => "'''$1''' yetwekkes.",
+'filedelete-success-old' => "Lqem n '''[[Media:$1|$1]]''' n $2 af $3 yetwekkes.",
+'filedelete-nofile' => "'''$1''' ulac-it.",
+'filedelete-nofile-old' => "Ulac lqem i ɣbeṛen n '''$1''' s ayla agi.",
+'filedelete-otherreason' => 'Taɣẓint nniḍen / taɣzint tamarnant :',
+'filedelete-reason-otherlist' => 'Taɣẓint nniḍen',
+'filedelete-reason-dropdown' => '* Tiɣzinin tillusanin n tukksa n ifuyla
+** Akukel n uzref n umeskar
+** Afaylu yesɛan anɣel',
+'filedelete-edit-reasonlist' => 'Beddel tiɣẓinin n umḥu i-d-yettuɣalen',
+'filedelete-maintenance' => 'Tukksa d tiririt n ifuyla yensa (akudan) deg ugani n ibeddi.',
+'filedelete-maintenance-title' => 'Ulamek an kkes afaylu',
# MIME search
'mimesearch' => 'Anadi n MIME',
# Random redirect
'randomredirect' => 'Asemmimeḍ menwala',
+'randomredirect-nopages' => 'Ulac asebter n alsanamad deg tallunt n isemawen « $1 ».',
# Statistics
'statistics' => 'Tisnaddanin',
+'statistics-header-pages' => 'Tisnaddanin n isebtar',
+'statistics-header-edits' => 'Tisnaddanin n ibeddilen',
+'statistics-header-views' => 'Tisnaddanin n iskanen',
'statistics-header-users' => 'Tisnaddanin n wemseqdac',
+'statistics-header-hooks' => 'Tisnaddanin nniḍen',
+'statistics-articles' => 'Isebtar n ugbur',
+'statistics-pages' => 'Isebtar',
+'statistics-pages-desc' => 'Akkw isebtar n wiki agi, ula d isebtar n umeslay, etc...',
+'statistics-files' => 'Ifuyla yettwaznen',
+'statistics-edits' => 'Ibeddilen n isebtar seg aserkeb n {{SITENAME}}',
+'statistics-edits-average' => 'Amḍan allal n ibeddilen sɣur asebter',
+'statistics-views-total' => 'Iskanen',
'statistics-mostpopular' => 'isebtar mmeẓren aṭṭas',
'disambiguations' => 'Isebtar yesɛan izdayen ɣer isebtar n tiynisemt',
'doubleredirectstext' => 'Mkull ajerriḍ yesɛa azday ɣer asmimeḍ amezwaru akk d wis sin, ajerriḍ amezwaru n uḍris n usebter wis sin daɣen, iwumi yefkan asmimeḍ ṣaḥiḥ i yessefk ad sɛan isebtar azday ɣur-s.',
'brokenredirects' => 'Isemmimḍen imerẓa',
-'brokenredirectstext' => 'Isemmimḍen-agi sɛan izdayen ar isebtar ulac-iten:',
+'brokenredirectstext' => 'Isemmimḍen-agi sɛan izdayen ar isebtar ulac-iten :',
'brokenredirects-edit' => 'beddel',
'brokenredirects-delete' => 'mḥu',
'withoutinterwiki-legend' => 'Adat',
'withoutinterwiki-submit' => 'Ssken',
+'fewestrevisions' => 'Isebtar yesɛan cwiṭ ibeddilen',
+
# Miscellaneous special pages
'nbytes' => '$1 {{PLURAL:$1|byte/octet|bytes/octets}}',
'ncategories' => '$1 {{PLURAL:$1|Taggayt|Taggayin}}',
'popularpages' => 'Isebtar iɣerfanen',
'wantedcategories' => 'Taggayin mmebɣant',
'wantedpages' => 'Isebtar mmebɣan',
+'wantedfiles' => 'Ifuyla yettwasutren s waṭas.',
+'wantedtemplates' => 'Talɣiwin yetsuteren',
'mostlinked' => 'Isebtar myezdin aṭas',
'mostlinkedcategories' => 'Taggayin myezdint aṭas',
+'mostlinkedtemplates' => 'Talɣiwin yetseqdacen aṭas.',
'mostcategories' => 'Isebtar i yesɛan aṭṭas taggayin',
'mostimages' => 'Ifuyla i seqdacen aṭas',
+'mostinterwikis' => 'Isebtar yesɛan aṭas interwikis',
'mostrevisions' => 'Isebtar i yettubedlen aṭas',
'prefixindex' => 'Akk isebtaren s yisekkilen imezwura',
+'prefixindex-namespace' => 'Akkw isebtar s adat (tallunt n isemawe $1)',
'shortpages' => 'isebtar imecṭuḥen',
'longpages' => 'Isebtar imeqqranen',
'deadendpages' => 'isebtar mebla izdayen',
'deadendpagestext' => 'Isebtar agi ur sɛan ara izdayen ɣer isebtar nniḍen n {{SITENAME}}.',
'protectedpages' => 'isebtar yettwaḥerzen',
+'protectedpages-indef' => 'Imestenen imeɣlalen kan',
+'protectedpages-cascade' => 'Imestenen s uceṛcuṛ kan',
'protectedpagestext' => 'isebtar-agi yettwaḥerzen seg ubeddel neɣ asemmimeḍ',
'protectedpagesempty' => 'isebtar-agi ttwaḥerzen s imsektayen -agi.',
+'protectedtitles' => 'Izwal ugdilen',
+'protectedtitlestext' => 'Izwal agi ugdilen deg usnulfu nsen',
+'protectedtitlesempty' => 'Ulac azwel yesɛan asegdel s iɣewwaren agi.',
'listusers' => 'Umuɣ n yimseqdacen',
+'listusers-editsonly' => 'Sekned kan iseqdacen yesɛan asekcem naɣ ugar',
+'listusers-creationsort' => 'Fren s azmez n usnulfu',
+'usereditcount' => '$1 {{PLURAL:$1|abeddel|ibeddilen}}',
'usercreated' => '{{GENDER:$3|Yesnulfu-d}} ass n $1 ar $2',
'newpages' => 'isebtar imaynuten',
'newpages-username' => 'Isem n wemseqdac:',
'ancientpages' => 'isebtar iqdimen',
'move' => 'Smimeḍ',
'movethispage' => 'Smimeḍ asebter-agi',
-'unusedimagestext' => 'Ssen belli ideggen n internet sɛan izdayen ɣer tugna-agi s URL n qbala, ɣas akken tugna-nni hatt da.',
+'unusedimagestext' => 'Ifuyla agi llan, maca ulac asebter anda llan.
+Ahat llan ismal nniḍen yesɛan azday ɣer afaylu, ihi yezmer ad yiqqim afaylu d agi dɣa ad yetseqdac deg ismal nni.',
'unusedcategoriestext' => 'Taggayin-agi weǧden meɛna ulac isebtar neɣ taggayin i sseqdacen-iten.',
'notargettitle' => 'Ulac nnican',
'notargettext' => 'Ur textareḍ ara asebter d nnican neɣ asebter n wemseqdac d nnican.',
+'nopagetitle' => 'Ulac asebter asaḍas am wagi',
+'nopagetext' => 'Asebter asaḍas agi ulac-it.',
'pager-newer-n' => '{{PLURAL:$1|amaynut|$1 imaynuten}}',
'pager-older-n' => '{{PLURAL:$1|aqbur|$1 iqburen}}',
+'suppress' => 'Mdi',
+'querypage-disabled' => 'Asebter uslig agi yensa , taɣzint : timellal is.',
# Book sources
'booksources' => 'Iɣbula n yidlisen',
'booksources-search-legend' => 'Nadi ɣef iɣbula n yidlisen',
'booksources-go' => 'Ruḥ',
'booksources-text' => 'Deg ukessar, yella wumuɣ n yizdayen iberraniyen izzenzen idlisen (imaynuten akk d weqdimen), yernu ahat sɛan kter talɣut ɣef idlisen i tettnadiḍ fell-asen:',
+'booksources-invalid-isbn' => 'ISBN agi ur yeɣbel ara ; selken ma ulac anezri deg-es.',
# Special:Log
'specialloguserlabel' => 'Ameskar :',
'speciallogtitlelabel' => 'Asaḍas (azwel naɣ aseqdac) :',
'log' => 'Aɣmis',
'all-logs-page' => 'Akk iɣmisen izayezen',
-'alllogstext' => 'Ssken akk iɣmisen n {{SITENAME}}.
-Tzemreḍ ad textareḍ cwiṭ seg-sen ma tebɣiḍ.',
+'alllogstext' => 'Abeqqeḍ n akkw iɣmisen yestufan ɣef {{SITENAME}}.<br />
+Tzemreḍ ad sageneḍ abeqqeḍ s tixtiṛit n tawsit n uɣmis, isem n useqdac naɣ asebter nni.',
'logempty' => 'Ur yufi ara deg uɣmis.',
'log-title-wildcard' => 'Nadi ɣef izwal i yebdan s uḍris-agi',
+'showhideselectedlogentries' => 'Beqqeḍ/ffer isekcam n uɣmis agi',
# Special:AllPages
'allpages' => 'Akk isebtar',
'nextpage' => 'Asebter ameḍfir ($1)',
'prevpage' => 'Asebter ssabeq ($1)',
'allpagesfrom' => 'Ssken isebtar seg:',
+'allpagesto' => 'Beqqeḍ isebtar alama :',
'allarticles' => 'Akk imagraden',
'allinnamespace' => 'Akk isebtar ($1 isem n taɣult)',
'allnotinnamespace' => 'Akk isebtar (mačči deg $1 isem n taɣult)',
'allpagesprefix' => 'Ssken isebtar s uzwir:',
'allpagesbadtitle' => 'Azwel n usebter mačči ṣaḥiḥ neɣ yesɛa azwir inter-wiki. Waqila yesɛa isekkilen ur ttuseqdacen ara deg izwal.',
'allpages-bad-ns' => '{{SITENAME}} ur yesɛi ara isem n taɣult "$1".',
+'allpages-hide-redirects' => 'Ffer isemmimḍen',
+
+# SpecialCachedPage
+'cachedspecial-refresh-now' => 'Ẓeṛ aneggaru.',
# Special:Categories
'categories' => 'Taggayin',
-'categoriespagetext' => 'Llant taggayin-agi deg wiki-yagi.
-[[Special:UnusedCategories|Unused categories]] are not shown here.
-Also see [[Special:WantedCategories|wanted categories]].',
+'categoriespagetext' => '{{PLURAL:$1|Taggayt agi teseqdec|Taggayin agi teseqdecet}} sɣur isebtar naɣ ifuyla.
+[[Special:UnusedCategories|Taggayin ur sɛan ara aqeddic]]
+Ẓeṛ daɣen [[Special:WantedCategories|taggayin yetwesuteren]].',
+'categoriesfrom' => 'Ssken taggayin seg :',
+'special-categories-sort-count' => 'Afran s amḍan n iferdisen',
+'special-categories-sort-abc' => 'Afran s ugemmay',
+
+# Special:DeletedContributions
+'deletedcontributions' => 'Isekcam yemḥan',
+'deletedcontributions-title' => 'Isekcam yemḥan',
+'sp-deletedcontributions-contribs' => 'Isekcam',
# Special:LinkSearch
+'linksearch' => 'Anadi n izdayen yeffɣen',
+'linksearch-pat' => 'Anadi n tanfalit :',
+'linksearch-ns' => 'Talluntin n isemawen :',
+'linksearch-ok' => 'Nadi',
'linksearch-line' => '$1 yeqqen seg $2',
# Special:ListUsers
'listusers-submit' => 'Ssken',
'listusers-noresult' => 'Ur yufi ḥedd (amseqdac).',
+# Special:ActiveUsers
+'activeusers-from' => 'Ssken iseqdacen seg :',
+'activeusers-noresult' => 'Ur yufi aseqdac.',
+
# Special:Log/newusers
'newuserlogpage' => 'Aɣmis n isnulfan n imiḍanen n imseqdacen',
# Special:ListGroupRights
+'listgrouprights-group' => 'Agraw',
+'listgrouprights-rights' => 'Izerfan',
+'listgrouprights-helppage' => 'Help:Izerfan n igrawen',
'listgrouprights-members' => '(umuɣ n imseqdacen)',
+'listgrouprights-addgroup' => 'Rnu iεeggalen i {{PLURAL:$2|ugraw|igrawen}} : $1',
+'listgrouprights-removegroup' => 'Ekkes iεeggalen i {{PLURAL:$2|ugraw|igrawen}} : $1',
+'listgrouprights-addgroup-all' => 'Rnu iεeggalen i akkw igrawen',
+'listgrouprights-removegroup-all' => 'Ekkes iεeggalen i akkw igrawen',
+'listgrouprights-addgroup-self' => 'Yezmer ad yernu {{PLURAL:$2|agraw|igrawen}} ar umiḍan-is : $1',
+'listgrouprights-removegroup-self' => 'Yezmer ad yekkes {{PLURAL:$2|agraw|igrawen}} ar umiḍan-is : $1',
+'listgrouprights-addgroup-self-all' => 'Yezmer ad yernu akkw igrawen ar umiḍan-is',
+'listgrouprights-removegroup-self-all' => 'Yezmer ad yekkes akkw igrawen ar umiḍan-is',
# E-mail user
'mailnologin' => 'Ur yufi ḥedd (tansa)',
'mailnologintext' => 'Yessefk ad [[Special:UserLogin|tkecmeḍ]] u tesɛiḍ tansa e-mail ṭaṣhiḥt deg [[Special:Preferences|isemyifiyen]] inek
iwakken ad tazneḍ email i imseqdacen wiyaḍ.',
'emailuser' => 'Azen e-mail i wemseqdac-agi',
-'emailpage' => 'Azen e-mail i wemseqdac',
-'emailpagetext' => 'Lukan amseqdac-agi yefka-d tansa n email ṣaḥiḥ
-deg imsifiyen ines, talɣa deg ukessar a t-tazen izen.
-Tansa n email i tefkiḍ deg imisifyen inek ad tban-d
-deg « Expéditeur» n izen inek iwakken amseqdac-nni yezmer a k-yerr.',
+'emailuser-title-target' => 'Ceggaɛ tirawt i {{GENDER:$1|aseqdac agi|taseqdact agi}}',
+'emailuser-title-notarget' => 'Ceggaɛ tirawt i useqdac',
+'emailpage' => 'Ceggaɛ tirawt i useqdac',
+'emailpagetext' => 'Tzemreḍ ad seqdeceḍ tiferkit ddaw agi iwakken ad ceggɛeḍ tirawt i useqdac agi.
+Tansa e-mail id ekfeḍ deg [[Special:Preferences|iɣewwaren inek/inem]] ad tban deg urti "Amceggaɛ" n izen ; akka, anermas ad yezmer ak/akem yefk tiririt.',
'usermailererror' => 'Yella ugul deg uzwel n email:',
'defemailsubject' => '{{SITENAME}} tirawt n useqdac « $1 »',
+'usermaildisabled' => 'Aceggaɛ n tira gar iseqdacen yensa',
+'usermaildisabledtext' => 'Ur tzermeḍ ara ad ceggeɛeḍ tira i iseqdacen nniḍen ɣef wiki agi',
'noemailtitle' => 'E-mail ulac-it',
'noemailtext' => 'Aseqdac-agi ur d-yefka ara tansa e-mail iɣbelen.',
+'nowikiemailtitle' => 'Ulac turagt i e-mail',
+'nowikiemailtext' => 'Aseqdac agi ur yebɣa ara ad yeṭṭef tirawt sɣur iseqdacen nniḍen.',
+'emailnotarget' => 'Isem useqdac n unermas ur yella ara naɣ ur yeɣbel ara.',
+'emailtarget' => 'Sekcem isem useqdac n unermas',
+'emailusername' => 'Isem n useqdac',
+'emailusernamesubmit' => 'Sumer',
+'email-legend' => 'Ceggaɛ tirawt i yiwen useqdac nniḍen n {{SITENAME}}',
'emailfrom' => 'Seg :',
'emailto' => 'I :',
'emailsubject' => 'Asentel :',
'emailccsubject' => 'Alsaru n izen inek i $1: $2',
'emailsent' => 'E-mail yettwazen',
'emailsenttext' => 'Izen n e-mail inek yettwazen.',
+'emailuserfooter' => 'Tirawt agi tetweceggaɛ sɣur « $1 » i « $2 » s tasɣent "Ceggaɛ tirawt i useqdac" n {{SITENAME}}.',
+
+# User Messenger
+'usermessage-summary' => 'Yeǧǧa-d izen anagraw',
+'usermessage-editor' => 'Ameskar n unagraw',
# Watchlist
'watchlist' => 'Umuɣ n uɛessi inu',
'watchlistanontext' => 'G leɛnaya-k $1 iwakken ad twalaḍ neɣ tbeddleḍ iferdas deg wumuɣ n uɛessi inek.',
'watchnologin' => 'Ur tekcimeḍ ara',
'watchnologintext' => 'Yessefk ad [[Special:UserLogin|tkecmeḍ]] iwakken ad tbeddleḍ umuɣ n uɛessi inek.',
+'addwatch' => 'Rnu i umuɣ n uɛassi',
'addedwatchtext' => "Asebter \"[[:\$1]]\" yettwarnu deg [[Special:Watchlist|wumuɣ n uɛessi]] inek.
Ma llan ibeddlen deg usebter-nni neɣ deg usbtar umyennan ines, ad banen dagi,
Deg [[Special:RecentChanges|wumuɣ n yibeddlen imaynuten]] ad banen s '''yisekkilen ibberbuzen''' (akken ad teẓriḍ).
Ma tebɣiḍ ad tekkseḍ asebter seg wumuɣ n uɛessi inek, wekki ɣef \"Fakk aɛessi\".",
+'removewatch' => 'Ekkes seg umuɣ n uɛassi',
'removedwatchtext' => '!!Asebter "[[:$1]]" yettwakkes seg [[Special:Watchlist|umuɣ n uɛessi]] inek.',
'watch' => 'Ɛass',
'watchthispage' => 'Ɛass asebter-agi',
'watchmethod-list' => 'yessenqed isebtar i ttɛassaɣ i ibeddlen imaynuten',
'watchlistcontains' => 'Umuɣ n uɛessi inek ɣur-s $1 n {{PLURAL:$1|usebter|isebtar}}.',
'iteminvalidname' => "Agnu akk d uferdis '$1', isem mačči ṣaḥiḥ...",
-'wlnote' => "Deg ukessar {{PLURAL:$1|yella yiwen ubeddel aneggaru|llan '''$1''' n yibeddlen ineggura}} deg {{PLURAL:$2|saɛa taneggarut|'''$2''' swayeɛ tineggura}}.",
+'wlnote' => "Ddaw agi {{PLURAL:$1|yella abeddel aneggaru|llan '''$1''' ibeddilen ineggura}} n {{PLURAL:$2|usrag aneggaru|'''$2''' isragen ineggura}}, seg $3 af $4.",
'wlshowlast' => 'Ssken $1 n swayeɛ $2 n wussan neɣ $3 ineggura',
'watchlist-options' => 'Tifranin n umuɣ n uɛessi',
'excontentauthor' => "Ayen yella: '$1' ('[[Special:Contributions/$2|$2]]' kan i yekken deg-s)",
'exbeforeblank' => "Ayen yella uqbal ma yettumḥa: '$1'",
'exblank' => 'asebter yella d ilem',
+'delete-confirm' => 'Kkes "$1"',
+'delete-legend' => 'Ekkes',
'historywarning' => 'Ɣur-wet : Asebter i ara temḥuḍ yesɛa amezruy s azal alemmas n $1 {{PLURAL:$1|lqem|ileqman}} :',
'actioncomplete' => 'Axdam yekfa',
'actionfailed' => 'Tigawt agi texser',
'deletionlog' => 'Aɣmis n umḥay',
'reverted' => 'Asuɣal i tasiwel taqdimt',
'deletecomment' => 'Ayɣer',
+'deleteotherreason' => 'Taɣẓint nniḍen / taɣzint tamarnant :',
+'deletereasonotherlist' => 'Taɣẓint nniḍen',
+'delete-edit-reasonlist' => 'Beddel tiɣẓinin n umḥu n usebter',
# Rollback
+'rollback_short' => 'Semmet',
'rollbacklink' => 'semmet',
+'rollbacklinkcount' => 'semmet $1 {{PLURAL:$1|abeddel|ibeddilen}}',
'cantrollback' => 'Ur yezmir ara ad yessuɣal; yella yiwen kan amseqdac iwumi ibeddel/yexleq asebter-agi.',
'editcomment' => "Agzul n ubeddel yella: \"''\$1''\".",
'revertpage' => 'Yessuɣal ibeddlen n [[Special:Contributions/$2|$2]] ([[User talk:$2|Meslay]]); yettubeddel ɣer tasiwelt taneggarut n [[User:$1|$1]]',
'protect-title' => 'Ad yeḥrez "$1"',
'prot_1movedto2' => '[[$1]] yettusmimeḍ ar [[$2]]',
'protect-legend' => 'Sentem tiḥḥerzi',
+'protectcomment' => 'Taɣẓint :',
'protect-default' => '(ameslugen)',
'protect-level-sysop' => 'Inedbalen kan',
'protect-summary-cascade' => 'acercur',
'protect-expiring' => 'yemmut deg $1 (UTC)',
+'protect-otherreason' => 'Taɣẓint nniḍen / taɣzint tamarnant :',
+'protect-otherreason-op' => 'Taɣẓint nniḍen',
+'protect-edit-reasonlist' => 'Beddel tiɣẓinin n usegdel',
+'protect-expiry-options' => '1 asrag:1 hour,1 ass:1 day,1 imalas:1 week,2 imalasen:2 weeks,1 aggur:1 month,3 agguren:3 months,6 agguren:6 months,1 assegwas:1 year,adfi:infinite',
'restriction-type' => 'Turagt',
'minimum-size' => 'Tiddi minimum',
# Restrictions (nouns)
'restriction-edit' => 'Beddel',
'restriction-move' => 'Smimeḍ',
+'restriction-create' => 'Snulfu',
+
+# Restriction levels
+'restriction-level-autoconfirmed' => 'adu-asegdel',
# Undelete
+'undelete' => 'Ẓer isebtar yettumḥan',
+'undeletepage' => 'Ẓeṛ dɣa erred isebtar yetwekkesen',
+'undeletepagetitle' => "'''Umuɣ agi yesɛa ileqman yetwekkesen n [[:$1|$1]]'''.",
'viewdeletedpage' => 'Ẓer isebtar yettumḥan',
+'undelete-fieldset-title' => 'Erred ileqman',
'undeletelink' => 'ẓeṛ/uɣaled',
'undeleteviewlink' => 'ẓeṛ',
'undeletecomment' => 'Taɣẓint :',
'undelete-no-results' => 'Ur yufi ara ulaḥedd n wawalen i tnadiḍ ɣef isebtar deg iɣbaren.',
# Namespace form on various pages
-'namespace' => 'Isem n taɣult:',
+'namespace' => 'Talluntin n isemawen :',
'invert' => 'Snegdam ayen textareḍ',
'blanknamespace' => '(Amenzawi)',
'ipadressorusername' => 'Tansa IP neɣ isem n wemseqdac',
'ipbreason' => 'Ayɣer',
'ipbsubmit' => 'Ɛekkel amseqdac-agi',
-'ipboptions' => '2 isragen:2 hours,1 ass:1 day,3 ussan:3 days,1 imalas:1 week,2 imulas:2 weeks,1 aggur:1 month,3 igguren:3 months,6 igguren:6 months,1 aseggwas:1 year,afdi:infinite',
+'ipboptions' => '2 isragen:2 hours,1 ass:1 day,3 ussan:3 days,1 imalas:1 week,2 imalasen:2 weeks,1 aggur:1 month,3 agguren:3 months,6 agguren:6 months,1 aseggwas:1 year,afdi:infinite',
'ipbotheroption' => 'nniḍen',
'badipaddress' => 'Tansa IP mačči d ṣaḥiḥ',
+'ipusubmit' => 'Ekkes akyaf agi',
+'unblocked' => 'Yetwekkes akyaf n [[User:$1|$1]]',
+'unblocked-range' => 'Yetwekkes akyaf n $1',
+'unblocked-id' => 'Akyaf $1 yetwekkes',
+'blocklist' => 'Iseqdacen id yetkyefen',
'ipblocklist' => 'imseqdacen isewḥelen',
+'ipblocklist-legend' => 'Nadi aseqdac id yetkyefen',
+'blocklist-userblocks' => 'Ffer ikyafen n imiḍanen',
+'blocklist-tempblocks' => 'Ffer ikyafen ikudanen',
+'blocklist-addressblocks' => 'Ffer ikyafen n tansa IP tisuftin',
+'blocklist-rangeblocks' => 'Ffer iḥedran n azrag',
+'blocklist-timestamp' => 'Azmez d usrag',
+'blocklist-target' => 'Asaḍas',
+'blocklist-expiry' => 'Azmez n tasewti',
+'blocklist-by' => 'Anedbal i sexdemen akyaf',
+'blocklist-params' => 'Iɣewwaren n ukyaf',
+'blocklist-reason' => 'Taɣẓint',
'ipblocklist-submit' => 'Nadi',
+'ipblocklist-localblock' => 'Akyaf adigan',
+'ipblocklist-otherblocks' => '{{PLURAL:$1|Akyaf nniḍen|Ikyafen nniḍen}}',
+'infiniteblock' => 'ameɣlal',
+'expiringblock' => 'tasewti ass n $1 af $2',
+'anononlyblock' => 'iseqdacen ur sɛan ara amiḍan kan',
+'noautoblockblock' => 'akyaf awurman yensa',
+'createaccountblock' => 'asnulfu n umiḍan yekyef',
+'emailblock' => 'e-mail yekyef',
+'blocklist-nousertalk' => 'ur yezmer ara ad yebeddel asebter-is n umeslay',
+'ipblocklist-empty' => 'Umuɣ n tansiwin IP i kyefen d-ilem.',
+'ipblocklist-no-results' => 'Tansa IP naɣ aseqdac i sutereḍ ur yekyef ara.',
'blocklink' => 'ɛekkel',
'unblocklink' => 'ekkes asewḥel',
'change-blocklink' => 'beddel asewḥel',
'contribslink' => 'tikkin',
+'emaillink' => 'Ceggaɛ e-mail',
'blocklogpage' => 'Aɣmis n isewḥelen',
'blocklogentry' => 'yesewḥel [[$1]] ; alama : $2 $3',
'block-log-flags-anononly' => 'Imseqdacen udrigen kan',
# Move page
'move-page-legend' => 'Smimeḍ asebter',
-'movepagetext' => "Mi tedsseqdceḍ talɣa deg ukessar ad ibddel isem n usebter, yesmimeḍ akk umezruy-is ɣer isem amaynut.
-Azwel aqdim ad yuɣal azady n wesmimeḍ ɣer azwel amaynut.
-Izdayen ɣer azwel aqdim ur ttubeddlen ara;
-ssenqd-iten u ssenqed izdayen n snat d tlata tikkwal.
-D kečč i yessefk a ten-yessenqed.
-
-Meɛna, ma yella amagrad deg azwel amaynut neɣ azday n wamsmimeḍ mebla amezruy, asebter-inek '''ur''' yettusmimeḍ '''ara'''.
-Yernu, tzemreḍ ad tesmimeḍ asebter ɣer isem-is aqdim ma tɣelṭeḍ.",
+'movepagetext' => "Seqdec tiferkit ddaw agi iwakken ad beddeleḍ isem n usebter, s usiweḍ n akkw amezruy is ɣer isem amaynut. Azwel aqbur ad yuɣal d asebter n usemmime ɣer azwel amaynut. Tzemreḍ ad mucceḍeḍ s uwurman isemmimen amiran i sweṛen ɣer azwel amezwaru. Ma ur tebɣiḍ ara at sexdemeḍ wagi, ilaq ad selkeneḍ akkw [[Special:DoubleRedirects|Asemmimeḍ yeḍran snat tikwal]] naɣ [[Special:BrokenRedirects|asmmimeḍ yerẓan]]. Ilaq ad sɛuḍ talkint belli izdayen tsweṛen ɣer aserken is.
+
+Abeddel n isem ur yezmer ara ad yilli ma yella yakan asebter s isem agi, ḥaca ma ulac amezruy deg-es.
+
+'''Ɣur-wet !'''
+Wagi yezmer ad yexdem abeddel ameqqṛan i asebter s aṭas timerziwin ; ilaq ad fehmeḍ uqbel ad beddeleḍ asebter.",
'movepagetalktext' => "Asebter \"Amyannan\" yettusmimeḍ ula d netta '''ma ulac:'''
*Yella asebter \"Amyannan\" deg isem amaynut, neɣ
*Trecmeḍ tankult deg ukessar.
# Special:NewFiles
'newimages' => 'Umuɣ n ifayluwen imaynuten',
'imagelisttext' => "Deg ukessar yella wumuɣ n '''$1''' {{PLURAL:$1|ufaylu|yifayluwen}} $2.",
+'newimages-legend' => 'Tastayt',
+'newimages-label' => 'Isem n ufaylu (naɣ aḥric ines) :',
+'showhidebots' => '($1 iṛubuten)',
'noimages' => 'Tugna ulac-itt.',
'ilsubmit' => 'Nadi',
'bydate' => 's uzemz',
'sp-newimages-showfrom' => 'Beqqeḍ ifuyla imaynuten seg $1 ar $2',
+# Video information, used by Language::formatTimePeriod() to format lengths in the above messages
+'seconds' => '{{PLURAL:$1|$1 tasint|$1 tasinin}}',
+'minutes' => '{{PLURAL:$1|$1 tamrect|$1 timercin}}',
+'hours' => '{{PLURAL:$1|$1 asrag|$1 isragen}}',
+'days' => '{{PLURAL:$1|$1 ass|$1 ussan}}',
+'ago' => '$1 aya',
+
# Bad image list
'bad_image_list' => 'Amasal d-wagi :
# Metadata
'metadata' => 'Adferisefka',
'metadata-help' => 'Afaylu agi, yesɛa tilɣa tisutay, ahat d-tamsaknewt id ernan tilɣa agi. Ma afaylu yebeddel seg addad-is amezwaru, ahat kra n tilɣa ur zemrent ara ad illint d-timekdant s-ufaylu amiran.',
+'metadata-expand' => 'Beqqeḍ tilɣa tummidin',
+'metadata-collapse' => 'Ffer tilɣa tummidin',
'metadata-fields' => 'Urtan n adferisefka n tugniwin yellan deg umuɣ n izen agi, ad seddun deg usebter n aglam n tugna mi ṭabla n adferisefka at illi tesemẓi. Urtan nniḍen ad illin ffren m-ulac.
* make
* model
# EXIF tags
'exif-imagewidth' => 'Tehri',
+'exif-imagelength' => 'Taɣwzi',
+'exif-bitspersample' => 'Ibitten s isger',
+'exif-compression' => 'Tawsit n asekkusem',
+'exif-photometricinterpretation' => 'Talɣa n uferdis n tugna',
+'exif-orientation' => 'Taɣda',
+'exif-samplesperpixel' => 'Tisegranin s uferdis n tugna',
+'exif-planarconfiguration' => 'Aheggi n isefka',
+'exif-ycbcrsubsampling' => 'Atug n adu-isefka n ulemmec n Y ar C',
+'exif-ycbcrpositioning' => 'Aselfu n Y d C',
+'exif-xresolution' => 'Tabadut taglawit',
+'exif-yresolution' => 'Tabadut taratakt',
+'exif-stripoffsets' => 'Asun n isefka n tugna',
+'exif-rowsperstrip' => 'Amḍan n ijerriden s tasfift',
+'exif-stripbytecounts' => 'Tiddi n itamḍanen s tasfift',
+'exif-jpeginterchangeformat' => 'Ideg n SOI JPEG',
+'exif-jpeginterchangeformatlength' => 'Tiddi s itamḍanen n isefka JPEG',
+'exif-whitepoint' => 'Tiniskit n uqqa amellal',
+'exif-primarychromaticities' => 'Tiniskit n tizwaranin',
+'exif-referenceblackwhite' => 'Azalen n tamselɣut aberkan d umellal',
+'exif-datetime' => 'Azmez n ubeddel',
+'exif-imagedescription' => 'Aglam n tugna',
+'exif-make' => 'Amakras n taweṣṣaft',
+'exif-model' => 'Talɣa n taweṣṣaft',
+'exif-software' => 'Aseɣẓan yetseqdecen',
+'exif-artist' => 'Ameskar',
+'exif-copyright' => 'Amli n uzref n umeskar',
+'exif-exifversion' => 'Lqem EXIF',
+'exif-flashpixversion' => 'Lqem FlashPix',
+'exif-colorspace' => 'Tallunt n tiniskit',
+'exif-componentsconfiguration' => 'Anamek n yal isger',
+'exif-compressedbitsperpixel' => 'Askar n usekkusem n tugna',
+'exif-pixelydimension' => 'Tehri n tugna',
+'exif-pixelxdimension' => 'Taɣwzi n tugna',
+'exif-usercomment' => 'Iwenniten n useqdac',
+'exif-relatedsoundfile' => 'Afaylu n eslu yeqqnen',
+'exif-datetimeoriginal' => 'Azmez n tuddma tamezwarut',
+'exif-datetimedigitized' => 'Azmez n usemḍen',
+'exif-subsectime' => 'Azmez n ubeddel',
+'exif-subsectimeoriginal' => 'Azmez n tuddma tamezwarut',
+'exif-subsectimedigitized' => 'Azmez n usemḍen',
+'exif-exposuretime' => 'Akud n timzikent',
+'exif-exposuretime-format' => '$1 tas ($2 tas)',
+'exif-fnumber' => 'Alday',
+'exif-exposureprogram' => 'Ahil n timzikent',
+'exif-aperturevalue' => 'Alday n APEX',
+'exif-exposurebiasvalue' => 'Aseɣti n timzikent',
+'exif-maxaperturevalue' => 'Alday afellay',
+'exif-subjectdistance' => 'Ameccaq n usentel',
+'exif-meteringmode' => 'Askar n usket',
+'exif-lightsource' => 'Aɣbalu n tafat',
+'exif-flash' => 'Lebṛaq',
+'exif-focallength' => 'Taɣwzi n usaḍas',
+'exif-subjectarea' => 'Asun n usentel',
+'exif-flashenergy' => 'Tanezmart n lebṛaq',
+'exif-focalplanexresolution' => 'Tabadut taglawit n uɣawas asaḍas',
+'exif-focalplaneyresolution' => 'Tabadut taratakt n uɣawas asaḍas',
+'exif-focalplaneresolutionunit' => 'Aferdis n tabadut n uɣawas asaḍas',
+'exif-subjectlocation' => 'Asideg n usentel',
+'exif-exposureindex' => 'Amatar n timzikent',
+'exif-sensingmethod' => 'Tawsit n umaṭṭaf',
+'exif-filesource' => 'Aɣbal n ufaylu',
+'exif-scenetype' => 'Tawsit n usayes',
'exif-worldregiondest' => 'Timnaḍin n umaḍal yebeqqeḍen',
'exif-countrydest' => 'Timura yebeqqeḍen',
'exif-countrycodedest' => 'Tangalt n tamurt yebeqqeḍen',
'exif-originaltransmissionref' => 'Tangalt n usideg n tuzzna tamezwarut',
'exif-identifier' => 'Asulay',
+'exif-copyrighted-true' => 'Ddaw azref n umeskar',
+'exif-copyrighted-false' => 'Taɣuly tazayezt',
+
+'exif-unknowndate' => 'Azmez warisem',
+
+'exif-orientation-1' => 'Amagnu',
+'exif-orientation-2' => 'Tetti s udem aglawan',
+'exif-orientation-3' => 'Tezzi s 180°',
+'exif-orientation-4' => 'Tetti s udem aratak',
+'exif-orientation-5' => 'Tezzi s 90° deg unamek imitti n usrag dɣa tetti s udem aratak',
+'exif-orientation-6' => 'Tezzi s 90° deg unamek imitti n usrag',
+'exif-orientation-7' => 'Tezzi s 90° deg unamek n usrag dɣa tetti s udem aratak',
+'exif-orientation-8' => 'Tezzi s 90° deg unamek n usrag',
+
+'exif-planarconfiguration-1' => 'Isefka iqriben',
+'exif-planarconfiguration-2' => 'Isefka ibrarazen',
+
+'exif-colorspace-65535' => 'Ur yezize ara',
+
+'exif-componentsconfiguration-0' => 'Ulac it',
+
+'exif-exposureprogram-0' => 'Ur yersen ara',
+'exif-exposureprogram-1' => 'Awfus',
+'exif-exposureprogram-2' => 'Ahil alugan',
+'exif-exposureprogram-3' => 'Tazwart i ulday',
+'exif-exposureprogram-4' => 'Tazwart i uqfel',
+'exif-exposureprogram-5' => 'Ahil n usnulfu (azullen i tadrut n urti)',
+'exif-exposureprogram-6' => 'Ahil n tigawt (azullen i urured n yqfel)',
+'exif-exposureprogram-7' => 'Askar tafrist (i tugniwin s tama dɣa s ugilal iluɣen)',
+'exif-exposureprogram-8' => 'Askar agama (i tugniwin n igmaten i llulcen)',
+
+'exif-subjectdistance-value' => '$1 lmitra',
+
+'exif-meteringmode-0' => 'Warisem',
+'exif-meteringmode-1' => 'Alemmas',
+'exif-meteringmode-2' => 'Allal amnekna deg agwans',
+'exif-meteringmode-3' => 'Asfaw',
+'exif-meteringmode-4' => 'Aget-asfaw',
+'exif-meteringmode-5' => 'Talɣa',
+'exif-meteringmode-6' => 'Aḥric',
'exif-meteringmode-255' => 'Nniḍen',
+'exif-lightsource-0' => 'Warisem',
+'exif-lightsource-1' => 'Tafukt n ass',
+'exif-lightsource-2' => 'Arafaw afliyuṛi',
+'exif-lightsource-3' => 'Tungsten (tafat tanesfawt)',
+'exif-lightsource-4' => 'Lebṛaq',
+'exif-lightsource-9' => 'Akud aceεlal',
+'exif-lightsource-10' => 'Akud isignew',
+'exif-lightsource-11' => 'Tili',
+'exif-lightsource-12' => 'Asfaw afliyuṛi « tafukt n ass » (D 5700 – 7100 K)',
+'exif-lightsource-13' => 'Asfaw afliyuṛi amellal « ass » (N 4600 – 5400 K)',
+'exif-lightsource-14' => 'Asfaw afliyuṛi amellal « asemmaḍ » (W 3900 – 4500 K)',
+'exif-lightsource-15' => 'Asfaw afliyuṛi amellal (WW 3200 – 3700 K)',
+'exif-lightsource-17' => 'Tafat talugent A',
+'exif-lightsource-18' => 'Tafat talugent B',
+'exif-lightsource-19' => 'Tafat talugent C',
+'exif-lightsource-24' => 'Tungsten ISU n usakwen',
+'exif-lightsource-255' => 'Aɣbalu nniḍen n tafat',
+
+# Flash modes
+'exif-flash-fired-0' => 'Lebṛaq ur yendeḥ ara',
+'exif-flash-fired-1' => 'Lebṛaq yendeḥ',
+'exif-flash-mode-1' => 'Tafat n lebaq yuwren',
+'exif-flash-mode-2' => 'Tukksa n lebaq yuwren',
+'exif-flash-mode-3' => 'askar awurman',
+'exif-flash-function-1' => 'Ulac tasɣent n lebṛaq',
+'exif-flash-redeye-1' => 'Tasɣent mgel-allen izeggwaɣen',
+
+'exif-focalplaneresolutionunit-2' => 'Idebbuzen',
+
+'exif-sensingmethod-1' => 'Aranbadu',
+'exif-sensingmethod-2' => 'Amaṭṭaf n ini s yiwen aceṛṛun',
+'exif-sensingmethod-3' => 'Amaṭṭaf n ini s sin iceṛṛunen',
+'exif-sensingmethod-4' => 'Amaṭṭaf n ini s kṛaḍ iceṛṛunen',
+'exif-sensingmethod-5' => 'Amaṭṭaf n ini ulkim',
+'exif-sensingmethod-7' => 'Amaṭṭaf kṛaḍimzireg',
+'exif-sensingmethod-8' => 'Amaṭṭaf n ini imzireg ulkim',
+
+'exif-filesource-3' => 'Taweṣṣaft tumḍint',
+
+'exif-scenetype-1' => 'Tugna n tafarut tusridt',
+
+'exif-customrendered-0' => 'Akala alugen',
+'exif-customrendered-1' => 'Akala yesagen',
+
+'exif-exposuremode-0' => 'Awurman',
+'exif-exposuremode-1' => 'Awfus',
+'exif-exposuremode-2' => 'Tazercet tawurmant',
+
+'exif-whitebalance-0' => 'Awurman',
+'exif-whitebalance-1' => 'Awfus',
+
+'exif-scenecapturetype-0' => 'Alugen',
+'exif-scenecapturetype-1' => 'Agama',
+'exif-scenecapturetype-2' => 'Tafrist',
+'exif-scenecapturetype-3' => 'Asayes n iḍ',
+
+'exif-gaincontrol-0' => 'Ulac',
+'exif-gaincontrol-1' => 'Rrbeḥ ufrir s ubedlez',
+'exif-gaincontrol-2' => 'Rrbeḥ ufrir s aṭas',
+'exif-gaincontrol-3' => 'Rrbeḥ uzdir s ubedlez',
+'exif-gaincontrol-4' => 'Rrbeḥ uzdir s aṭas',
+
+'exif-contrast-0' => 'Amagnu',
+'exif-contrast-1' => 'Abadlaz',
+'exif-contrast-2' => 'Aẓayan',
+
+'exif-saturation-0' => 'Amagnu',
+'exif-saturation-1' => 'Abadlaz',
+'exif-saturation-2' => 'Tameqqṛant',
+
+'exif-sharpness-0' => 'Amagnu',
+'exif-sharpness-1' => 'Alegɣan',
+'exif-sharpness-2' => 'Aẓayan',
+
+'exif-subjectdistancerange-0' => 'Warisem',
+'exif-subjectdistancerange-1' => 'Amɣer',
+
# Pseudotags used for GPSSpeedRef
'exif-gpsspeed-k' => 'Kilometr deg ssaɛa',
'exif-gpsspeed-m' => 'Miles deg usrag',
'exif-gpsdestdistance-m' => 'igimen',
'exif-gpsdestdistance-n' => 'Miles iwlalen',
+'exif-gpsdop-excellent' => 'Yufrar ($1)',
'exif-gpsdop-good' => 'Tamellayt ($1)',
'exif-gpsdop-moderate' => 'Tallalt ($1)',
+'exif-gpsdop-fair' => 'Attwadag ($1)',
+'exif-gpsdop-poor' => 'Yecmet ($1)',
'exif-objectcycle-a' => 'Tanzayt kan',
'exif-objectcycle-p' => 'Tameddit kan',
'exif-iimcategory-hth' => 'Tadawsa',
'exif-iimcategory-hum' => 'Aramsu alsi',
'exif-iimcategory-lab' => 'Amahil',
+'exif-iimcategory-lif' => 'Askar n tudert dɣa n imezlan',
+'exif-iimcategory-pol' => 'Tasertit',
+'exif-iimcategory-rel' => 'Ddin d tifelsin',
+'exif-iimcategory-sci' => 'Tussna d tatiknulujit',
+'exif-iimcategory-soi' => 'Tuttriwin timettiyin',
+'exif-iimcategory-spo' => 'Addalen',
+'exif-iimcategory-war' => 'Ṭrad, taẓit d tasmessit',
+'exif-iimcategory-wea' => 'Tasnignewt',
+
+'exif-urgency-normal' => 'Alugen ($1)',
+'exif-urgency-low' => 'Anammum ($1)',
+'exif-urgency-high' => 'Afella ($1)',
# External editor support
'edit-externally' => 'Beddel afaylu-yagi s usnas aberrani.',
'watchlistall2' => 'akk',
'namespacesall' => 'akk',
'monthsall' => 'akk',
+'limitall' => 'Akkw',
# E-mail address confirmation
'confirmemail' => 'Sentem tansa n e-mail',
'duplicate-defaultsort' => 'Ɣur-wet : tasarut n ufran m-ulac « $2 » atsefεej tasarut n uqbel « $1 ».',
# Special:Version
-'version' => 'Tasiwelt',
+'version' => 'Lqem',
'version-specialpages' => 'isebtar usligen',
# Special:SpecialPages
# Special:Tags
'tag-filter' => 'Astay n [[Special:Tags|ticraḍ]] :',
+# Feedback
+'feedback-subject' => 'Asentel :',
+'feedback-message' => 'Izen :',
+'feedback-cancel' => 'Semmewet',
+'feedback-submit' => 'Ceggaɛ iwenniten',
+'feedback-adding' => 'Rnud iwenniten inek/inem ar usebter...',
+
# Durations
'duration-seconds' => '$1 {{PLURAL:$1|tasint|tisinin}}',
'duration-minutes' => '$1 {{PLURAL:$1|tamrect|timercin}}',
'qbbrowse' => 'شولۋ',
'qbedit' => 'وڭدەۋ',
'qbpageoptions' => 'بۇل بەت',
-'qbpageinfo' => 'اينالا',
'qbmyoptions' => 'بەتتەرىم',
'qbspecialpages' => 'ارنايى بەتتەر',
'faq' => 'ٴجىيى قويىلعان ساۋالدار',
'qbbrowse' => 'Шолу',
'qbedit' => 'Өңдеу',
'qbpageoptions' => 'Бұл бет',
-'qbpageinfo' => 'Айнала',
'qbmyoptions' => 'Беттерім',
'qbspecialpages' => 'Арнайы беттер',
'faq' => 'Жиі қойылған сауалдар',
'qbbrowse' => 'Şolw',
'qbedit' => 'Öñdew',
'qbpageoptions' => 'Bul bet',
-'qbpageinfo' => 'Aýnala',
'qbmyoptions' => 'Betterim',
'qbspecialpages' => 'Arnaýı better',
'faq' => 'Jïi qoýılğan sawaldar',
'qbbrowse' => 'រាវរក',
'qbedit' => 'កែប្រែ',
'qbpageoptions' => 'ទំព័រនេះ',
-'qbpageinfo' => 'ព័ត៌មានទំព័រ',
'qbmyoptions' => 'ទំព័ររបស់ខ្ញុំ',
'qbspecialpages' => 'ទំព័រពិសេសៗ',
'faq' => 'សំណួរដែលសួរញឹកញាប់',
'qbbrowse' => '탐색',
'qbedit' => '편집',
'qbpageoptions' => '문서 기능',
-'qbpageinfo' => '문서 정보',
'qbmyoptions' => '내 사용자 문서',
'qbspecialpages' => '특수 문서',
'faq' => '자주 묻는 질문',
'searcharticle' => '가기',
'history' => '문서 역사',
'history_short' => '역사',
-'updatedmarker' => '마지막으로 읽은 뒤 바뀌었음',
+'updatedmarker' => '마지막으로 방문한 뒤 바뀜',
'printableversion' => '인쇄용 문서',
'permalink' => '고유링크',
'print' => '인쇄',
'edit-already-exists' => '새 문서를 만들 수 없습니다.
그 문서는 이미 존재합니다.',
'defaultmessagetext' => '기본 메세지 내용',
+'content-failed-to-parse' => '$1 모델에 대한 $2 내용을 구문 분석하는 데 실패했습니다: $3',
+'invalid-content-data' => '잘못된 내용 데이터입니다',
+'content-not-allowed-here' => '"$1" 내용은 [[$2]] 문서예 허용하지 않습니다',
+
+# Content models
+'content-model-wikitext' => '위키텍스트',
+'content-model-text' => '일반 텍스트',
+'content-model-javascript' => '자바스크립트',
+'content-model-css' => 'CSS',
# Parser/template warnings
'expensive-parserfunction-warning' => "'''경고:''' 이 문서는 너무 많은 파서 함수를 포함하고 있습니다.
'shared-repo' => '공용 저장소',
'shared-repo-name-wikimediacommons' => '위키미디어 공용',
'filepage.css' => '/* 이 CSS 설정은 파일 설명 문서에 포함되며, 또한 해외 클라이언트 위키에 포함됩니다 */',
-'upload-disallowed-here' => 'ì£\84ì\86¡í\95\98ì§\80ë§\8c ì\9d´ 그림ì\9d\84 ë\8d®ì\96´ 쓸 수 없습니다.',
+'upload-disallowed-here' => 'ì\9d´ í\8c\8cì\9d¼ì\9d\84 ë\8d®ì\96´쓸 수 없습니다.',
# File reversion
'filerevert' => '$1 되돌리기',
'undeletedrevisions' => '판 $1개를 복구했습니다',
'undeletedrevisions-files' => '판 $1개와 파일 $2개를 복구했습니다.',
'undeletedfiles' => '파일 $1개를 복구했습니다',
-'cannotundelete' => '복구에 실패했습니다.
-다른 사용자가 이미 복구했을 수도 있습니다.',
+'cannotundelete' => '복구하는 데 실패했습니다:
+$1',
'undeletedpage' => "'''$1 문서를 복구했습니다.'''
[[Special:Log/delete|삭제 기록]]에서 최근의 삭제와 복구 기록을 볼 수 있습니다.",
이미 복구되었을 수 있습니다.',
'undelete-error' => '문서 복구 중 오류',
'undelete-error-short' => '파일 복구 오류: $1',
-'undelete-error-long' => '파일을 복구하는 중 오류가 발생했습니다:
+'undelete-error-long' => '파일을 복구하는 동안 오류가 발생했습니다:
$1',
'undelete-show-file-confirm' => '정말 "<nowiki>$1</nowiki>" 파일의 삭제된 $2 $3 버전을 보시겠습니까?',
'immobile-target-namespace-iw' => '인터위키 링크를 넘어 문서를 이동할 수 없습니다.',
'immobile-source-page' => '이 문서는 이동할 수 없습니다.',
'immobile-target-page' => '새 이름으로 옮길 수 없습니다.',
+'bad-target-model' => '원하는 대상은 다른 내용 모델을 사용합니다. $1에서 $2로 변환할 수 없습니다.',
'imagenocrossnamespace' => '파일을 파일이 아닌 이름공간으로 옮길 수 없습니다.',
'nonfile-cannot-move-to-file' => '파일이 아닌 문서를 파일 이름공간으로 옮길 수 없습니다.',
'imagetypemismatch' => '새 파일의 확장자가 원래의 확장자와 일치하지 않습니다.',
# Info page
'pageinfo-title' => '"$1" 문서에 대한 정보',
+'pageinfo-not-current' => '정보는 현재 판만을 보여줄 수 있습니다.',
'pageinfo-header-basic' => '기본 정보',
'pageinfo-header-edits' => '편집 역사',
'pageinfo-header-restrictions' => '문서 보호',
'qbbrowse' => 'Къарау',
'qbedit' => 'Тюрлендир',
'qbpageoptions' => 'Бу бет',
-'qbpageinfo' => 'Бетни юсюнден',
'qbmyoptions' => 'Бетлерим',
'qbspecialpages' => 'Къуллукъчу бетле',
'faq' => 'FAQ',
'qbbrowse' => 'Aanluure',
'qbedit' => 'Ändere',
'qbpageoptions' => 'Sigge Enstellunge',
-'qbpageinfo' => 'Üvver de Sigg',
'qbmyoptions' => 'Ming Sigge',
'qbspecialpages' => 'Spezial Sigge',
'faq' => 'FAQ',
'whatlinkshere-prev' => '{{PLURAL:$1|yê|$1 yên}} berê',
'whatlinkshere-next' => '{{PLURAL:$1|yê|$1 yên}} din',
'whatlinkshere-links' => '← girêdan',
-'whatlinkshere-hideredirs' => '$1 beralîkirin',
-'whatlinkshere-hidelinks' => '$1 lînkan',
+'whatlinkshere-hideredirs' => 'Beralîkirinan $1',
+'whatlinkshere-hidetrans' => 'Naverokan $1',
+'whatlinkshere-hidelinks' => 'Lînkan $1',
'whatlinkshere-hideimages' => '$1 lînkên wêneyan',
'whatlinkshere-filters' => 'Parzûn',
'qbbrowse' => 'Perspicere',
'qbedit' => 'Recensere',
'qbpageoptions' => 'Optiones paginae',
-'qbpageinfo' => 'Contextus',
'qbmyoptions' => 'Paginae meae',
'qbspecialpages' => 'Paginae speciales',
'faq' => 'Quaestiones frequentes',
'qbbrowse' => 'Duerchsichen',
'qbedit' => 'Änneren',
'qbpageoptions' => 'Säitenoptiounen',
-'qbpageinfo' => 'Kontext',
'qbmyoptions' => 'Meng Säiten',
'qbspecialpages' => 'Spezialsäiten',
'faq' => 'FAQ',
'edit-no-change' => 'Är ännerung gouf ignoréiert, well Dir näischt um Text geännert hutt.',
'edit-already-exists' => 'Déi nei Säit konnt net ugeluecht ginn, well et se scho gëtt.',
'defaultmessagetext' => 'Standardtext',
+'content-not-allowed-here' => '"$1"-Inhalt ass op der Säit [[$2]] net erlaabt',
+
+# Content models
+'content-model-wikitext' => 'Wikitext',
+'content-model-text' => 'Kloertext',
+'content-model-javascript' => 'JavaScript',
+'content-model-css' => 'CSS',
# Parser/template warnings
'expensive-parserfunction-warning' => "'''Opgepasst:'' Dës Säit huet ze vill Ufroe vu komplexe Parserfunktiounen.
'undeletedrevisions' => '$1 {{PLURAL:$1|Versioun gouf|$1 Versioune goufe}} restauréiert',
'undeletedrevisions-files' => '{{PLURAL:$1|1 Versioun|$1 Versiounen}} a(n) {{PLURAL:$2|1 Fichier|$2 Fichiere}} goufe restauréiert',
'undeletedfiles' => '$1 {{PLURAL:$1|Fichier gouf|Fichiere goufe}} restauréiert',
-'cannotundelete' => "D'Restauratioun huet net fonktionéiert. Een anere Benotzer huet déi Säit warscheinlech scho virun iech restauréiert.",
+'cannotundelete' => "D'Restauratioun huet net fonctionnéiert:
+$1",
'undeletedpage' => "'''$1''' gouf restauréiert.
Am [[Special:Log/delete|Läsch-Logbuch]] fannt Dir déi geläscht a restauréiert Säiten.",
'qbbrowse' => 'Bladere',
'qbedit' => 'Bewirke',
'qbpageoptions' => 'Pagina-opties',
-'qbpageinfo' => 'Pagina-informatie',
'qbmyoptions' => 'mien opties',
'qbspecialpages' => "Speciaal pagina's",
'faq' => 'FAQ (väölgesjtèlde vraoge)',
'qbfind' => 'Attrêuva',
'qbedit' => 'Cangia',
'qbpageoptions' => "Opsioîn de 'sta paggina",
-'qbpageinfo' => 'Informassion inscia paggina',
'qbmyoptions' => 'E mæ paggine',
'qbspecialpages' => 'Pagine speçiä',
'faq' => 'Domande frequenti',
'qbbrowse' => 'Naršymas',
'qbedit' => 'Taisyti',
'qbpageoptions' => 'Šis puslapis',
-'qbpageinfo' => 'Kontekstas',
'qbmyoptions' => 'Mano puslapiai',
'qbspecialpages' => 'Specialieji puslapiai',
'faq' => 'DUK',
'qbbrowse' => 'Fangvêl rawh',
'qbedit' => 'Siamţhatna',
'qbpageoptions' => 'He phêk hi',
-'qbpageinfo' => 'Thukhawchang',
'qbmyoptions' => 'Ka phêkte',
'qbspecialpages' => 'Phêk vohbîkte',
'faq' => 'Zawhzin',
Ahnuaih hian {{PLURAL:$1|zawmtu hmasa ber|zawmtu hmasa $1-te}} kan rawn tlar chhuak e.
Zawmtu zawng zawng [[Special:WhatLinksHere/$2|tlarchhuahna hetah hian a awm]] e.',
'nolinkstoimage' => 'He taksa zawmtu/hmanna phêk pakhat mah a awm lo.',
-'morelinkstoimage' => 'Hemi taksa zawmpui dang [[Special:WhatLinksHere/$1|enna}}.',
+'morelinkstoimage' => 'Hemi taksa zawmpui dang [[Special:WhatLinksHere/$1|enna]].',
'linkstoimage-redirect' => '$1 (taksa hruailuhna) $2',
'duplicatesoffile' => 'A hnuaia taksa{{PLURAL:$1||te}} khu hë taksa nihpui{{PLURAL:$1||te}} a{{PLURAL:$1||n}} ni ([[Special:FileDuplicateSearch/$2|chanchin kimchang]]):',
'sharedupload' => 'Hë taksa hi $1-a mi a ni a, hna-hmachhawp dangin a hmang vè mai thei.',
'qbbrowse' => 'Navigācija',
'qbedit' => 'Izmainīšana',
'qbpageoptions' => 'Šī lapa',
-'qbpageinfo' => 'Konteksts',
'qbmyoptions' => 'Manas lapas',
'qbspecialpages' => 'Īpašās lapas',
'faq' => 'BUJ',
'qbbrowse' => '覽',
'qbedit' => '纂',
'qbpageoptions' => '此頁',
-'qbpageinfo' => '內文',
'qbmyoptions' => '吾好',
'qbspecialpages' => '非凡',
'faq' => '頻答問',
'qbbrowse' => 'गवेषण करू',
'qbedit' => 'सम्पादन करू',
'qbpageoptions' => 'ई पन्ना',
-'qbpageinfo' => 'विषय',
'qbmyoptions' => 'हमर पन्ना सभ',
'qbspecialpages' => 'विशेष पन्ना सभ',
'faq' => 'त्वरित प्रश्नोत्तरी',
'tog-underline' => 'Сюлмафкснень алга китькстамс:',
'tog-justify' => 'Тиемс сёрматфть фкакс ушедоматнень лопать кувалмова',
'tog-hideminor' => 'Од полафтоматнень эса кяшемс ёмланя видептематне',
+'tog-hidepatrolled' => 'Кяшемс лувонь кирдихнень видептемаснон мекольце полафнематнень эса',
+'tog-newpageshidepatrolled' => 'Кяшемс лувонь кирдихнень эса видептьф лопат од лопань лувса',
'tog-extendwatchlist' => 'Келептемс мельгеваномать сембе полафтоматнень няфтемга, аф аньцек мекольценнет',
-'tog-usenewrc' => 'Ð\9dолдак Ñ\82евÑ\81 Ñ\86ебÑ\8fÑ\80Ñ\8cгоÑ\84Ñ\82Ñ\84 од илÑ\8fкÑ\81Ñ\82опÑ\82омаÑ\82 (Ñ\8dÑ\80Ñ\8fви JavaScript)',
+'tog-usenewrc' => 'Ð\9fолгаÑ\8fÑ\84Ñ\82омÑ\81 илÑ\8fкÑ\81Ñ\82опÑ\82омаÑ\82ненÑ\8c лопанÑ\8c коÑ\80Ñ\8fÑ\81 меколÑ\8cÑ\86е полаÑ\84немаÑ\82ненÑ\8c Ñ\8dÑ\81а ди мелÑ\8cгеваномаÑ\81а (веÑ\88и JavaScript)',
'tog-numberheadings' => 'Сёрмадома коняксс лувомтяшксне эслек путовихть',
'tog-showtoolbar' => 'Кядьёнкс седяфксть няфтемс сёрмадомбачк (JavaScript)',
'tog-editondblclick' => 'Кафксть люпштазь сувамс сёрматфть петнема (JavaScript)',
'tog-editsection' => 'Няфтемс сюлмафксть [петемс] эрь пяльксонди',
'tog-editsectiononrightclick' => 'Петнемс пялькстне: люпштамс сёрмадомбяльксть лемонц лангс видешире пуняса (JavaScript)',
'tog-showtoc' => 'Няфтемс сёрматфть потмакс (лопатнень, конатнень эса 3 сёрмадома конякста лама)',
-'tog-rememberpassword' => 'Ð\92анÑ\84Ñ\82омÑ\81 монÑ\8c Ñ\81Ñ\83вама лемозе Ñ\82Ñ\8f Ñ\81одама маÑ\88инаÑ\81а (for a maximum of $1 {{PLURAL:$1|day|days}})',
-'tog-watchcreations' => 'СÑ\83ваÑ\84Ñ\82омÑ\81 монÑ\8c Ñ\82еÑ\84Ñ\82Ñ\8c лопаÑ\82не монÑ\8c мельгеваномазон',
-'tog-watchdefault' => 'СÑ\83ваÑ\84Ñ\82омÑ\81 монÑ\8c пеÑ\82нема лопане монÑ\8c мельгеваномазон',
-'tog-watchmoves' => 'Ð\9bопаÑ\82ненÑ\8c Ñ\88аÑ\88Ñ\84Ñ\82омÑ\81Ñ\82а Ñ\81Ñ\83ваÑ\84Ñ\82омÑ\81 Ñ\81инÑ\8c монÑ\8c мельгеваномазон',
-'tog-watchdeletion' => 'Ð\9bопаÑ\82ненÑ\8c наÑ\80дамÑ\81Ñ\82а Ñ\81Ñ\83ваÑ\84Ñ\82омÑ\81 Ñ\81инÑ\8c монÑ\8c мельгеваномазон',
+'tog-rememberpassword' => 'Ð\92анÑ\84Ñ\82омÑ\81 монÑ\8c Ñ\81Ñ\83вама лемозе Ñ\82Ñ\8f Ñ\81одаммаÑ\88инаÑ\81а (Ñ\81Ñ\8fда кÑ\83ваÑ\82Ñ\8c $1 {{PLURAL:$1|Ñ\88и|Ñ\88иÑ\82}})',
+'tog-watchcreations' => 'СÑ\83ваÑ\84Ñ\82омÑ\81 лопаÑ\82ненÑ\8c, конаÑ\82ненÑ\8c Ñ\82иине ди Ñ\84айлаÑ\82, конаÑ\82ненÑ\8c Ñ\82онгине мельгеваномазон',
+'tog-watchdefault' => 'СÑ\83ваÑ\84Ñ\82омÑ\81 лопаÑ\82ненÑ\8c ди Ñ\84айлаÑ\82ненÑ\8c, конаÑ\82ненÑ\8c пеÑ\82неÑ\81айне мельгеваномазон',
+'tog-watchmoves' => 'СÑ\83ваÑ\84Ñ\82омÑ\81 лопаÑ\82ненÑ\8c ди Ñ\84айлаÑ\82ненÑ\8c, конаÑ\82ненÑ\8c Ñ\88аÑ\88Ñ\84Ñ\82Ñ\8bне мельгеваномазон',
+'tog-watchdeletion' => 'СÑ\83ваÑ\84Ñ\82омÑ\81 лопаÑ\82ненÑ\8c ди Ñ\84айлаÑ\82ненÑ\8c, конаÑ\82ненÑ\8c наÑ\80дÑ\8bне мельгеваномазон',
'tog-minordefault' => 'Тяшксемс сембе петема анцяйнятне мъзярс илякс изь мярьгов',
'tog-previewontop' => 'Няфтемс сёрматфть васень няфтемать петемань седяфксть инголе',
'tog-previewonfirst' => 'Васень няфтема васенце петнемада меле',
-'tog-nocache' => 'Ð\90Ñ\84 мÑ\8fÑ\80Ñ\8cгома лопаÑ\82ненÑ\8c Ñ\8dÑ\81лек ванÑ\84неви Ñ\84айлÑ\81нон Ñ\82иемÑ\81',
-'tog-enotifwatchlistpages' => 'Кучемс электрононь сёрма монь ванома лопать илякстоптомада меле',
+'tog-nocache' => 'Ð\9aаÑ\80дамÑ\81 инÑ\82еÑ\80неÑ\82Ñ\81 вÑ\8fÑ\82иенди Ñ\8dÑ\81лек ванÑ\84неви Ñ\84айлÑ\85ненÑ\8c Ñ\82иема',
+'tog-enotifwatchlistpages' => 'Кучт тейне е-сёрма мзярда монь мельгеваномаста лопат илякстоптовихть',
'tog-enotifusertalkpages' => 'Кучемс электрононь сёрма монь тиить корхтама лопанц илякстоптомада меле',
-'tog-enotifminoredits' => 'Кучемс электрононь сёрма нъльне петема анцяйняда меле',
+'tog-enotifminoredits' => 'Кучт тейне е-сёрма нъльне мъзярда лопат эди файлхт аф ламне видептевихть',
'tog-enotifrevealaddr' => 'Штафтомс монь электрононь адресозе пачфтема сёрмаса',
'tog-shownumberswatching' => 'Няфтемс мъзяра сувсида конат арафтозь лопать эсь мельгеваномазост',
+'tog-oldsig' => 'Афкуксонь кядьтяшкс',
'tog-fancysig' => 'Кядьтяшкст улихть викитекстокс (эслек тиеви сюлмафксфтома)',
-'tog-externaleditor' => 'Нолдамс тевс ушеширень петнить мъзярс илякс изь мярьгов (аньцек тевонь содайхненди, сяс мес эрявихть башка кядьёнкст-арафнемат содама машинаса)',
-'tog-externaldiff' => 'Нолдамс тевс ушеширень програм верзиень ваксс путоманкса мъзярс илякс изь мярьгов (аньцек тевонь содайхненди, сяс мес эрявихть башка кядьёнкст-арафнемат содама машинаса)',
+'tog-externaleditor' => 'Нолдамс тевс ушеширень петнить мъзярс илякс изь мярьгов (аньцек тевонь содайхненди, сяс мес эрявихть башка кядьёнкст-арафнемат содама машинаса [//www.mediawiki.org/wiki/Manual:External_editors сяда тов.])',
+'tog-externaldiff' => 'Нолдамс тевс ушеширень програм верзиень ваксс путоманкса мъзярс илякс изь мярьгов (аньцек тевонь содайхненди, сяс мес эрявихть башка кядьёнкст-арафнемат содама машинаса[//www.mediawiki.org/wiki/Manual:External_editors сяда тов.])',
'tog-showjumplinks' => 'Мярьгомс "юпадемс" сатовома сюлмафкстненди',
'tog-uselivepreview' => 'Максомс эряй васень няфтемась (JavaScript) (Варжамань)',
'tog-forceeditsummary' => 'Няфтемс мондине мезе сёрмадомс шава петнема вальмас сувамста',
'tog-watchlisthideminor' => 'Кяшемс петнема анцяйнятне ванома лопаста',
'tog-watchlisthideliu' => 'Кяшемс сёрматфтф тиихнень петнемаснон мельгеваномаса',
'tog-watchlisthideanons' => 'Кяшемс лемфтома тиихнень петнемаснон мельгеваномаса',
+'tog-watchlisthidepatrolled' => 'Кяшемс лувонь кирдихнень видептемаснон мельгеваномаса',
'tog-ccmeonemails' => 'Кучт тейне копия электрононь сермане конатнень кучсайне иля тиихненди.',
'tog-diffonly' => 'Тят няфте лопань потмоц кафта верзиятнень ваксс путомать ала',
'tog-showhiddencats' => 'Няфтемс кяшф категориет',
'underline-never' => 'Мъзярдонга',
'underline-default' => 'Интернет полатксть кадомс апак полафтт',
+# Font style option in Special:Preferences
+'editfont-style' => 'Полафтомс тя паксянь сёрмадома стиленц',
+'editfont-default' => 'Интернетс вятись апак полафтт',
+'editfont-monospace' => 'Фкя келеса сёрмадома',
+'editfont-sansserif' => 'Сёрмадома Sans-serif',
+'editfont-serif' => 'Serif сёрмадома',
+
# Dates
'sunday' => 'Таргоши (Недляши)',
'monday' => 'Одговши (Панедельник)',
'category-file-count' => '{{PLURAL:$2|Тя категориеса аньцек фкя файл.|Вага {{PLURAL:$1|файл|$1 файлхт}} тя категориеса $2-нь эста.}}',
'category-file-count-limited' => 'Вага {{PLURAL:$1|файл|$1 файлхт}} тя категориеса.',
'listingcontinuesabbrev' => 'полатксоц',
+'index-category' => 'Индексыяф лопат',
'noindex-category' => '↓Индексфтома лопатне',
+'broken-file-category' => 'Лопат колаф сюлмафкснень мархта',
'about' => 'Колганза',
'article' => 'Сёрматфть потмонц лопац',
'qbbrowse' => 'Ванондома',
'qbedit' => 'Петнема',
'qbpageoptions' => 'Тя лопась',
-'qbpageinfo' => 'Контекстсь',
'qbmyoptions' => 'Монь лопане',
'qbspecialpages' => 'Башка тевонь лопат',
'faq' => 'Сидеста Кеподеви Кизефксне',
'faqpage' => 'Project:Сидеста Кеподеви Кизефксне',
# Vector skin
+'vector-action-addsection' => 'Поладомс мезень колга корхтамс',
'vector-action-delete' => 'Нардамс',
'vector-action-move' => 'Шашфтомс',
'vector-action-protect' => 'Араламс',
'vector-action-undelete' => 'Мърдафтомс',
-'vector-action-unprotect' => 'Аралама лоткамс',
+'vector-action-unprotect' => 'Араламать полафтомс',
+'vector-simplesearch-preference' => 'Нодамс тевс тёждялгтотф кядьёнксонь седяфксть (аньцек векторонь лангакс)',
'vector-view-create' => 'Тиемс',
'vector-view-edit' => 'Петнемс',
'vector-view-history' => 'История няфтемс',
'printableversion' => 'Лихтеви верзие',
'permalink' => 'Ялань сюлмафкс',
'print' => 'Нолдамс',
+'view' => 'Ваномс',
'edit' => 'Петнеме',
'create' => 'Тиемс',
'editthispage' => 'Петнемс тя лопать',
'delete' => 'Нардамс',
'deletethispage' => 'Нардамс тя лопать',
'undelete_short' => 'Мърдафтомс {{PLURAL:$1|петнема|$1 петнемат}}',
+'viewdeleted_short' => 'Ваномс {{PLURAL:$1|фкя нардаф видептема|$1 нардаф видептемат}}',
'protect' => 'Араламс',
'protect_change' => 'полафтомс прянь араламать',
'protectthispage' => 'Араламс тя лопать',
-'unprotect' => 'Ð\92алÑ\85Ñ\82омÑ\81 аÑ\80аламаÑ\82Ñ\8c',
-'unprotectthispage' => 'Ð\92алÑ\85Ñ\82омÑ\81 Ñ\82Ñ\8f лопаÑ\82Ñ\8c аÑ\80аламац',
+'unprotect' => 'Ð\90Ñ\80аламаÑ\82Ñ\8c полаÑ\84Ñ\82омÑ\81',
+'unprotectthispage' => 'Ð\9fолаÑ\84Ñ\82омÑ\81 Ñ\82Ñ\8f лопаÑ\82Ñ\8c аÑ\80аламанц',
'newpage' => 'Од лопа',
'talkpage' => 'Корхтамс тя лопать колга',
'talkpagelinktext' => 'Корхтама',
'jumpto' => 'Юпадемс тязк:',
'jumptonavigation' => 'навигацие',
'jumptosearch' => 'вешендема',
+'view-pool-error' => 'Ужяль, тя пингть серверхнень вийсна аф сатовихть.
+Вельф лама тиихть тяряфнихть ваномс тя лопать.
+Эняльттяма учт аф ламос тя лопанди одукс сама инголе.
+$1',
+'pool-timeout' => 'Пигонь кирдемась учи пякстаманц',
+'pool-queuefull' => 'Тяряфнемада вельф лама',
+'pool-errorunknown' => 'Аф содаф эльбятькс',
# All link text and link target definitions of links into project namespace that get used by other message strings, with the exception of user group pages (see grouppage) and the disambiguation template definition (see disambiguations).
'aboutsite' => '{{SITENAME}} колга',
'youhavenewmessages' => 'Тонь ули $1 ($2).',
'newmessageslink' => 'Од сёрмат',
'newmessagesdifflink' => 'мекольце полафтома',
+'youhavenewmessagesfromusers' => 'Тонь $1 {{PLURAL:$3|тага фкя тиить эзда|$3 тиихнень эзда}} ($2).',
+'youhavenewmessagesmanyusers' => 'Тонь $1 лама тиихнень эзда ($2).',
+'newmessageslinkplural' => '{{PLURAL:$1|од сёрма|од сёрмат}}',
+'newmessagesdifflinkplural' => 'мекольце {{PLURAL:$1|полафнема|полафнемат}}',
'youhavenewmessagesmulti' => 'Тонь улихть од сёрмат $1-са',
'editsection' => 'петнемс',
'editold' => 'петнемс',
'toc' => 'Лопань потмоц',
'showtoc' => 'няфтемс',
'hidetoc' => 'кяшемс',
+'collapsible-collapse' => 'Ёмлалгофтомс',
+'collapsible-expand' => 'Келептемс',
'thisisdeleted' => 'Ваномс эли мърдафтомс $1?',
'viewdeleted' => 'Ваномс $1?',
'restorelink' => '{{PLURAL:$1|нардаф петнема|$1 нардаф петнемат}}',
'page-rss-feed' => '"$1" RSS линия',
'page-atom-feed' => '"$1" Atom линия',
'red-link-title' => '$1 (стама лопась аш)',
+'sort-descending' => 'Арафтомс алу',
+'sort-ascending' => 'Арафтомс вяри',
# Short words for each namespace, by default used in the namespace tab in monobook
'nstab-main' => 'Лопа',
# General errors
'error' => 'Эльбятькс',
'databaseerror' => 'Датабаза эльбятькс',
-'dberrortext' => 'Ð\94аÑ\82абазанÑ\8c веÑ\88ендембаÑ\87к лиÑ\81Ñ\81Ñ\8c Ñ\81инÑ\82акÑ\81 эльбятькс.
-ТÑ\8f, Ñ\83лема, пÑ\80огÑ\80амонÑ\8c Ñ\8dлÑ\8cбÑ\8fÑ\82Ñ\8cкÑ\81.
-Мекольце датабазонь вешендема ульсь:
-<blockquote><tt>$1</tt></blockquote>
-функциеста "<tt>$2</tt>".
-Ð\94аÑ\82абазаÑ\81Ñ\8c мÑ\8aÑ\80даÑ\84Ñ\82озе Ñ\8dлÑ\8cбÑ\8fÑ\82Ñ\8cкÑ\81Ñ\82Ñ\8c "<tt>$3: $4</tt>".',
+'dberrortext' => 'СодамоÑ\88инÑ\8c паÑ\80ганÑ\8c веÑ\88ендембаÑ\87к лиÑ\81Ñ\81Ñ\8c Ñ\81инÑ\82акÑ\81онÑ\8c эльбятькс.
+ТÑ\8f, Ñ\83лема, пÑ\80огÑ\80амгÑ\8fÑ\80Ñ\8cкÑ\81онÑ\8c Ñ\81и.
+Мекольце содамошинь паргань вешема:
+<blockquote><code>$1</code></blockquote>
+функциеста "<code>$2</code>".
+СодамоÑ\88инÑ\8c паÑ\80гаÑ\81Ñ\8c паÑ\87Ñ\84Ñ\82еÑ\81Ñ\8c Ñ\8dлÑ\8cбÑ\8fÑ\82Ñ\8cкÑ\81 "<samp>$3: $4</samp>".',
'dberrortextcl' => 'Датабазонь вешендембачк лиссь синтакс эльбятькс.
Мекольце датабазонь вешендема ульсь:
"$1"
'readonlytext' => 'Датабазась тяни пякстаф од сёрмадоматненди эли полафнематненди, шятьта нежедематненди, меле сон мърдай эрьшинь покаманцты.
Оцюнясь кона сонь пякстазе арьсезе сонь шарьхкотьфтемац: $1',
-'missing-article' => 'Ð\94аÑ\82абазаÑ\81а аÑ\84 мÑ\83ви Ñ\82екÑ\81Ñ\82 конань эряви мумс, сонь лемоц "$1" $2.
+'missing-article' => 'СодамоÑ\88инÑ\8c паÑ\80гÑ\81а аÑ\84 мÑ\83ви Ñ\82екÑ\81Ñ\82Ñ\81Ñ\8c конань эряви мумс, сонь лемоц "$1" $2.
Тя сидеста лиси мъзярда молят сирелготф верзиева эли историянь сюлмафксова, кона вяти нардаф лопас.
'readonly_lag' => 'Датабазась эслек пякстась мъзярс кядяла датабаза серверхт сотни прясерверть мархта',
'internalerror' => 'Потмонь эльбятькс',
'internalerror_info' => 'Потмонь эльбятькс: $1',
+'fileappenderrorread' => '"$1" файлась аф лувови поладома пингста.',
+'fileappenderror' => '"$1" файлась изь поладов "$2" файлти.',
'filecopyerror' => 'Аш кода копиямс файл "$1" файл "$2"с.',
'filerenameerror' => 'Аш кода "$1" файлти максомс од лем "$2".',
'filedeleteerror' => 'Файл "$1" аф нардави.',
'badarticleerror' => 'Тя лопаса тя аф тиеви.',
'cannotdelete' => 'Лопась эли кочкаф "$1" файлсь аф нардави.
Сонь, улема, кинге нардазе ни.',
+'cannotdelete-title' => '"$1" лопась аф нардави',
+'delete-hook-aborted' => 'Туворкс програм петнемать лоткафтозе.
+Пачфтемат тянь коряс аш.',
'badtitle' => 'Аф кондясти лем',
'badtitletext' => 'Вешф лопань лемоц аф тяфтама эли шава, шятьта кялень-ётка эли викинь-ётка лемсна аф лац сюлмафт. Сонь эса, улема ащи фкя эли сяда лама башка тяштькстт конат коняксонди аф кондястихть.',
-'perfcached' => 'Вешф програмонь информациесь сёрматфоль эслек ванфневи файлхнень эса ди, улема, сирелгодсь. A maximum of {{PLURAL:$1|one result is|$1 results are}} available in the cache.',
-'perfcachedts' => 'Тя програмонь информациесь сёрматфоль эслек ванфневи файлхнень эса ди мекольцеда одонзаф $1. A maximum of {{PLURAL:$4|one result is|$4 results are}} available in the cache.',
+'perfcached' => 'Вешф програмонь информациесь сёрматфоль эслек ванфневи файлхнень эса ди, улема, сирелгодсь. Сяда {{PLURAL:$1|фкя муфкс|$1 муфкст}} эслек ванфневи файлань кярьксса.',
+'perfcachedts' => 'Тя програмонь информациесь сёрматфоль эслек ванфневи файлхнень эса ди мекольцеда одонзаф $1. Сяда {{PLURAL:$4|фкя муфкс|$4 муфкст}} эслек ванфневи файлонь кярьксса.',
'querypage-no-updates' => 'Тя лопать одонзапне тяни аф тиевихть. Информациесь тяса тяни аф одонзави.',
'wrong_wfQuery_params' => 'Аф кондясти параметратне функцияса wfQuery()<br />
Функцие: $1<br />
Вешфкс: $2',
'viewsource' => 'Ваномс лисьмоть',
+'viewsource-title' => 'Ванк $1 лисьмаста',
'actionthrottled' => 'Куроксшись кирьфтаф',
'actionthrottledtext' => 'Лудна мархта тюрема туфталонкса тя тевть ламоксть тиемась нюрьхконя ётка пингста кардаф. Эняльттяма мърдамс тя тевти мъзярошка минутода меле.',
'protectedpagetext' => 'Тя лопас сувама пякстаф лопань петнема кардамать сюнеда.',
'viewsourcetext' => 'Тейть ули кода ваномс эди копиямс тя лопать лисьмоц:',
-'protectedinterface' => 'Тя лопаса ащи лопать ванфонц програмонь текстоц, сон пякстаф кальдяв тевда араламать сюнеда.',
-'editinginterface' => "'''Инголе кардама:''' Тон петнесак лопать конань эса ащи лопать ванфонц програмонь текстоц. Петнематне полафтсазь сонь ванфоц кода сон няеви иля тиихненди. Ётафтома тиеманкса эняльттяма ваномс [//translatewiki.net/wiki/Main_Page?setlang=mdf translatewiki.net] МедиаВикить локализациеть проектть.",
+'viewyourtext' => "Тондейть ули кода тя лопань '''петнематнень''' ваномс ди тиемс копиянснон:",
+'protectedinterface' => 'Тя лопать эса интерфейс текстсь тя викить програмгярксти, сон аралаф кальдяв тиемада.
+Вики ётафтоматнень поладоманди полафнемандивок сувак [//translatewiki.net/ translatewiki.net], MediaWiki локализациень проектти.',
+'editinginterface' => "'''Инголи кардама:''' Тон петнесак лопать конань эса ащи интерфейс текст програмкярьксонди. Петнематне полафтсазь сонь ванфоц, кода сон няеви иля тиихненди. Вики ётафтоматнень поладоманди, полафтомандивок сувак [//translatewiki.net/ translatewiki.net] MediaWiki локализациень проектти.",
'sqlhidden' => '(SQL вешфкс кяшф)',
'cascadeprotected' => 'Тя лопать аралазь петнемада сяс мес сон сувафни {{PLURAL:$1|сай лопас, кона путфоль|сай лопас, конат путфольхть}} каскад араламас:
$2',
'namespaceprotected' => "Тондейть аф мярьгови петнемс лопатне '''$1''' лепнень мархта.",
+'customcssprotected' => 'Тейть аф мярьгови петнемс CSS лопать, сяс мес потмосонза иля тиить латцеманза.',
+'customjsprotected' => 'Тейть аф мярьгови петнемс JavaScript лопать, сяс мес потмосонза иля тиить латцеманза.',
'ns-specialprotected' => '{{ns:special}} лепнень мархта лопатне аф петневихть.',
'titleprotected' => "[[User:$1|$1]] кардазь тя лемсь мархта лопа тиемать.
Туфталсь ''$2''.",
+'filereadonlyerror' => '"$1" файлась аф полафтови сяс мес "$2" файлонь пърдафкссь аньцек морафтови форматса.
+
+Админць конась сёлгозе кадсь пачфтема: "$3".',
+'invalidtitle-knownnamespace' => 'Аф кондясти лем "$2" лемботмоса ди "$3" текстть эса',
+'invalidtitle-unknownnamespace' => 'Аф кондясти лем $1 лемботмоса ди "$2" текстть эса',
+'exception-nologin' => 'Апак сувак',
+'exception-nologin-text' => 'Тя лопать эли тиемать сатоманди васенда эряви сувамс викис.',
# Virus scanner
'virus-badscanner' => "Аф кондясти конфигурациесь: аф содаф вирусонь вешендема програмсь: ''$1''",
'yourpassword' => 'Сувама валце:',
'yourpasswordagain' => 'Сёрматк сувама валце омбоцекс:',
'remembermypassword' => 'Ванфтомс монь сувама лемозе тя содам машинаса (максимум $1 {{PLURAL:$1|шис|шис}})',
+'securelogin-stick-https' => 'Кадовомс сотфокс HTTPS вельде сувамада меле',
'yourdomainname' => 'Тонь доменце:',
+'password-change-forbidden' => 'Сувама валхне тя викить эса аф полафтовихть',
'externaldberror' => 'Лиссь эльбятькс ушеширень датабазонь вельде кемокстакшнембачк эли тондейть аф мярьгови полафнемс тонь ушеширень сёрматфтомацень.',
'login' => 'Сувама',
'nav-login-createaccount' => 'Сувама / сёрматфтома',
'loginprompt' => 'Тондейть эряви нолдамс тевс cookies {{SITENAME}}с суваманди.',
'userlogin' => 'Сувама / сёрматфтома',
+'userloginnocreate' => 'Сувамс',
'logout' => 'Лисема',
'userlogout' => 'Лисема',
'notloggedin' => 'Апак сувак',
'createaccount' => 'Тиемс од сёрматфтомась',
'gotaccount' => "Сёрматфтыть ни? '''$1'''.",
'gotaccountlink' => 'Сувамс',
+'userlogin-resetlink' => 'Сувама эрявикснень юкстайть?',
'createaccountmail' => 'электрононь сёрма вельде',
+'createaccountreason' => 'Туфтал:',
'badretype' => 'Сувама валхне тон путыть аф фкат.',
-'userexists' => 'Тя лемсь кой-кие сявозь ни. Эняльттяма, арьсек иля лемсь.',
+'userexists' => 'Тя лемть сявозь ни.
+Эняльттяма, арьсек эстейть иля.',
'loginerror' => 'Сувама эльбятькс',
+'createaccounterror' => 'Сёрматфтомась аф тиеви: $1',
'nocookiesnew' => 'Тиить сёрматфтомаце анок, аньцек тон изеть сува. {{SITENAME}}-са тиихнень содафтоманкса функцие cookies эряви. Тяни тонь содама машинаса функцие cookies кардаф. Эняльттяма нолдамс тевс cookies, меле сувак од эсь тиить лемцень эди сувама валцень мархта.',
'nocookieslogin' => '{{SITENAME}} лопаса тиихнень содафтоманкса функцие cookies эряви. Тяни тонь содама машинаса функцие cookies кардаф. Эняльттяма нолдамс тевс cookies, меле сувак тага весть.',
+'nocookiesfornew' => 'Тиить сёрматфтомась апак тик сяс мес лисьмонц аф кемокстави.
+Варжак cookies нолдафт эли аф, одонзафтк лопать ди тяряфтт одукс.',
'noname' => 'Тон изеть пута кемокстаф тиить лемоц.',
'loginsuccesstitle' => 'Сувамась ётась лац',
'loginsuccess' => "'''Тон сувать {{SITENAME}}-с кода \"\$1\".'''",
Илякс тондейть эряви [[Special:UserLogin/signup|сёрматфтомс одукс]].',
'nosuchusershort' => 'Тиись "$1" лемса аш. Ванк, улема, тон сёрмадыть лемть аф лац.',
'nouserspecified' => 'Тиить лемсь эряви.',
+'login-userblocked' => 'Тиись перяф. Сувама кардаф.',
'wrongpassword' => 'Сувама валсь сёрматф аф лац. Варжак тага весть.',
'wrongpasswordempty' => 'Сувама валсь кадовсь апак сёрматк. Сёрматк одукс.',
'passwordtooshort' => 'Тонь сувама валценди эряви улемс аф {{PLURAL:$1|1 тяшкста|$1 тяшкста}} кържа',
+'password-name-match' => 'Сувама лемце ди сувама сувама валце улемат аф фкат.',
+'password-login-forbidden' => 'Тя сувама лемсь эди сувама валсь кардафт.',
'mailmypassword' => 'Кучт од сувама вал',
'passwordremindertitle' => 'Од ёткопингонь сувама валсь {{SITENAME}}с суваманди',
'passwordremindertext' => 'Кивок (улема, тон IP адресста $1) вешсь од сувама валсь {{SITENAME}} ($4)с суваманди.
Улендяряй киге иля кучсь тя вешфксть эли тон мяляфтсак тонь сувама валцень эди тонь тяни аш мяльце сонь полафтома, тят тие мезеге тя пачфтемась самда меле ди киртть тонь ингольдень сувама валцень.',
'noemail' => '"$1" тиить электрононь адресоц аш.',
+'noemailcreate' => 'Эряви тяштемс афкукс е-парга',
'passwordsent' => 'Од сувама валсь кучфоль "$1" тиить электрононь адресонцты.
Сувак сонь кундамда меле.',
'blocked-mailpassword' => 'Петнемат тиемась тонь IP адрестот кардаф. Сувама валть кемокстама функциес кундама аф мярьгови кальдяв тиемада аралама туфталонкса.',
'noemailprefs' => 'Мъзярс тон ашеть пута тонь электрононь адресце Викить сёрматнень коряс програмсь кодамога сёрмат аф кучсыне.',
'emailconfirmlink' => 'Кемокстак тонь электрононь адресце',
'invalidemailaddress' => 'Электрононь адресть аф пьрьняндави сяс сонь аф кондясти электрононь адресоц. Путт кондясти электрононь адресонц эли катк тя паксянять шавакс.',
+'cannotchangeemail' => 'Сёрматфтомать е-паргоц аф полафтови тя викить эса',
+'emaildisabled' => 'Тя лопанди аш кода кучемс е-сёрмат.',
'accountcreated' => 'Сёрматфтомась тиф',
'accountcreatedtext' => '$1 тиить сёрматфтомась тиф.',
'createaccount-title' => 'Сёрматфнемась {{SITENAME}}-с',
'createaccount-text' => 'Кати-кие тизе сёрматфтомась $2 {{SITENAME}} ($4)-са. "$2" -ть сувама валсь "$3". Тондейть эряви сувамс тозк эди арафтомс од сувама валть.
Улендяряль тя сёрматфтомась эльбятьксокс мезеге тят тие.',
+'usernamehasherror' => 'Тиить лемозонза тяфтама тяшкст аф мярьговихть',
'login-throttled' => 'Тон улхкомба вельф ламос тяряфнеть сувамс тя сувама валть вельде.
Эняльттяма, учт аф ламос тага весть тяряфтома инголе.',
+'login-abort-generic' => 'Сувамацень апак тиевсь лац - Валхтф',
'loginlanguagelabel' => 'Кяль: $1',
+'suspicious-userlogout' => 'Вешфксце лисемс кардафоль сяс мес няеви тянь кучезь колаф интернетс вятиень эли ётка ёкамань сервер вельде.',
+
+# E-mail sending
+'php-mail-error-unknown' => 'Аф содаф эльбятькс PHP сёрмавятемань функциеса.',
+'user-mail-no-addy' => 'Тяряфтыхть кучемс е-сёрма е-паргафтома',
# Change password dialog
'resetpass' => 'Полафтомс сувама валцень',
'resetpass_forbidden' => 'Сувама валхнень полафтомс аш кода',
'resetpass-no-info' => 'Тондейть эряви сёрматфтомс тя лопас видеста суваманди.',
'resetpass-submit-loggedin' => 'Полафтомс сувама валцень',
+'resetpass-submit-cancel' => 'Валхтомс',
'resetpass-wrong-oldpass' => 'Аф виде ёткопингонь эли тяниень сувама валсь.
Улема тон полафтыть сувама валце ни эли кучеть вешфкс од ёткопингонь сувама вал кундаманкса.',
'resetpass-temp-password' => 'Пингонь сувама валсь:',
+# Special:PasswordReset
+'passwordreset' => 'Полафтомс сувама валцень',
+'passwordreset-text' => 'Эряви пяшкодемс тя формать е-сёрма сёрматфтомацень колга сявоманди.',
+'passwordreset-legend' => 'Полафтомс сувама валцень',
+'passwordreset-disabled' => 'Сувама валсь аф полафтови тя викить эса.',
+'passwordreset-pretext' => '{{PLURAL:$1||Тяштьк содама пялькснень эзда фкя алу}}',
+'passwordreset-username' => 'Тиить лемоц',
+'passwordreset-domain' => 'Домен:',
+'passwordreset-capture' => 'Ваномс мекольце е-сёрма?',
+'passwordreset-capture-help' => 'Путондярят тяшкс тя паксять эса е-сёрма (пингонь сувама вал мархта) кармай няфтевома кодак кучф тиенди.',
+'passwordreset-emailtitle' => 'Серматфтомать колга {{SITENAME}}са',
+
# Edit page toolbar
'bold_sample' => 'Эчке сёрмадома',
'bold_tip' => 'Эчке сёрмадома',
Тондейть ули кода [[Special:Search/{{PAGENAME}}|вешендемс тя лопать коняксонц]] иля лопава,
<span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} вешендемс малады лувомава],
эли [{{fullurl:{{FULLPAGENAME}}|action=edit}} петнемс тя лопать]</span>.',
+'noarticletext-nopermission' => 'Тяни аш текст тя лопаса.
+Тондейть ули кода [[Special:Search/{{PAGENAME}}|вешендемс тя лопать коняксонц]] иля лопава,
+<span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} вешендемс малады лувомава]</span>, аньцек тонь аш мярьговомаце тя лопать ушедомс.',
'userpage-userdoesnotexist' => 'Сёрматфтомась «<nowiki>$1</nowiki>» лемса аш. Арьсек лацкаста, афкукс тонь улендяряй мяльце тиемс эли полафтомс тя лопать.',
'clearyourcache' => "'''Шарфтк мяльце:''' Ванфтомада меле од полафнематнень ваноманкса тондейть эряви нардамс эслек ванфневи файлхнень тонь интернет полатксонь вальмастонза. '''Mozilla / Firefox / Safari:''' ''Shift'' кирдезь, люпштак ''Reload'', эли люпштак ''Ctrl-Shift-F5'' эли ''Ctrl-R'' (''Command-Shift-R'' Mac машинаса); '''Konqueror: '''люпштак ''Reload'' эли люпштак ''F5;'' '''Opera:''' програмса тондейть эрявксты нардамс сембе эслек ванфневи файлхт ''Tools→Preferences'' вельде; '' '''Internet Explorer:''' ''Ctrl'' кирдезь люпштакшнек ''Refresh'' эли люпштак ''Ctrl-F5.''",
'usercssyoucanpreview' => "'''Мялень максома:''' Ванфтомада инголе нолдак тевс 'Васень няфтема' пунять тонь од CSS эли JS файлть варжаманкса.",
'userinvalidcssjstitle' => "'''Инголе мярьгома:''' Аш тема файл \"\$1\" мазопнеманкса. Киртть мяльсот .css эди .js лопас путови аньцек ёмла тяшкса коняксне, кепотьксонди {{ns:user}}:Foo/лем.css афи {{ns:user}}:Foo/Лем.css.",
'updated' => '(Одонзаф)',
'note' => "'''Шарфтк мяльце:'''",
-'previewnote' => "'''ТÑ\8f анÑ\8cÑ\86ек ваÑ\81енÑ\8c нÑ\8fÑ\84Ñ\82емаÑ\81Ñ\8c; полаÑ\84немаÑ\82не нинге иÑ\81Ñ\82Ñ\8c ванÑ\84Ñ\82ов!'''",
+'previewnote' => "'''Ð\9aиÑ\80Ñ\82Ñ\82Ñ\8c мÑ\8fлÑ\8cÑ\81оÑ\82, Ñ\82Ñ\8f анÑ\8cÑ\86ек ваÑ\81енÑ\8c нÑ\8fÑ\84Ñ\82емаÑ\81Ñ\8c.''' ТонÑ\8c полаÑ\84Ñ\82омаÑ\82не нинге иÑ\81Ñ\82Ñ\8c ванÑ\84Ñ\82ов!",
'previewconflict' => 'Текстсь тя васень няфтемаса няфтеви вярдень петнема паксяса стамкс кодамкс сон няеволь ванфтомада меле.',
'session_fail_preview' => "'''Аш кода тонь петнемаце сувафтомс мекольце информациень юмафтомать сюнеда.
Тик одукс.
'edit-no-change' => 'Тонь петнемацень тевс изь нолда, сяс мес тон текстть ашеть полафта.',
'edit-already-exists' => 'Аш кода од лопа ушедомс.
Тя лопась ульсь ни.',
+'content-failed-to-parse' => 'Аш кода $2 сёрматфть нолдамс тевс $1 моделень коряс: $3',
# Parser/template warnings
'expensive-parserfunction-warning' => 'Инголе кардама: Тя лопаса пяк лама питни синтаксонь анализаторхнень тяшкста.
Шарьхкотьфтема: (тян.) = тяниень верзиеда явомась,
(сяд.) = сядынгольдень верзияда явомась, Ё = ёмла петнема.',
'history-fieldset-title' => 'Вешентть история',
+'history-show-deleted' => 'Аньцек нардаф',
'histfirst' => 'Кунардонь',
'histlast' => 'Мекольце',
'historysize' => '({{PLURAL:$1|1 байт|$1 байтт}})',
'revdelete-success' => "'''Верзиеть няевоманц одонзафозь лац.'''",
'logdelete-success' => "'''Сёрматфть няевомац арафтовсь лац.'''",
'revdel-restore' => 'Полафтомс няевомац',
+'revdel-restore-deleted' => 'нардаф верзиет',
+'revdel-restore-visible' => 'няеви верзиет',
'pagehist' => 'Лопать историяц',
'deletedhist' => 'Нардаф историяц',
'revdelete-edit-reasonlist' => 'Петнемс нардамань туфталхне',
'mergelogpagetext' => 'Ванк ала сяда мекольдень лопатнень фкя фкянь мархта шоворемаснон историясна.',
# Diffs
-'history-title' => '"$1"-нь верзиетнень историясна',
+'history-title' => '"$1"нь полафнематнень историясна',
'difference-multipage' => 'Явомась лопаланготнень ёткова',
'lineno' => 'Кикссь $1:',
'compareselectedversions' => 'Путомс кочкаф верзиетнень ваксс',
'editundo' => 'валхтомс',
-'diff-multi' => '({{PLURAL:$1|$1-нь ётконь верзиец изь няфтев|$1-нь ётконь верзиенза исть няфтев}}.)',
+'diff-multi' => '({{PLURAL:$1|ёткопингонь верзие, конась|$1 ёткопингонь верзиет, конатне}} {{PLURAL:$2|тии тизе|$2 тиихть тизь}} апак няфтек)',
# Search results
'searchresults' => 'Мезе мувсь',
'notextmatches' => 'Лопаса сёрматфсь изь мув',
'prevn' => 'сядынголень {{PLURAL:$1|$1}}',
'nextn' => 'сай {{PLURAL:$1|$1}}',
+'prevn-title' => 'Сядынгольдень $1 {{PLURAL:$1|сафкс|сафкст}}',
+'nextn-title' => 'Сядомелень $1 {{PLURAL:$1|сафкс|сафкст}}',
+'shown-title' => 'Няфтемс лопасонза $1 {{PLURAL:$1|сафкс|сафкст}}',
'viewprevnext' => 'Ваномс ($1 {{int:pipe-separator}} $2) ($3)',
'searchmenu-legend' => 'Вешендема арафнематне',
'searchmenu-exists' => "'''Тя Викиса ули лопась \"[[:\$1]]\" лем мархта'''",
'searchprofile-everything-tooltip' => 'Вешендемс сембе лопаса (корхнема лопат сявомок)',
'searchprofile-advanced-tooltip' => 'Вешендемс кърдань лемботмова',
'search-result-size' => '$1 ({{PLURAL:$2|1 вал|$2 валхт}})',
+'search-result-category-size' => '{{PLURAL:$1|1 якай|$1 якайхть}} ({{PLURAL:$2|1 субкатегорие|$2 субкатегориет}}, {{PLURAL:$3|1 файла|$3 файлат}})',
'search-result-score' => 'Малавиксши: $1%',
'search-redirect' => '(шашфтт $1с)',
'search-section' => '(пялькс $1)',
'searchall' => 'сембе',
'showingresults' => "Ала няфтеви {{PLURAL:$1|мувсь '''1'''|мувсть '''$1'''}} '''$2'''-ста ушедомс.",
'showingresultsnum' => "Ала няфтеви {{PLURAL:$3|мувсь '''1'''|мувсть '''$3'''}} '''$2'''-ста ушедомс.",
+'showingresultsheader' => "{{PLURAL:$5|'''$1''' сафкс '''$3'''-ста|'''$1 - $2''' сафкст '''$3'''-ста}} '''$4'''нди",
'nonefound' => "'''Шарфтк мяльце''': Аньцек мъзярошка лемботмат вешендевихть инголе апак полафтт. Тяряфтт вешендема валда инголе путомс ''all:'' сембе потмонь вешендеманди (корхнема лопат ди шаблотт сявомок, ди с. т) эли кундак эрявикс лемботмос кода валынгольксс.",
'search-nonefound' => 'Аш вешфксонди малады муфкст.',
'powersearch' => 'Сядонга вешендемс',
'email' => 'Электрононь адресце',
'prefs-help-realname' => 'Афкуксонь лемце путомась аф лувови эрявикс. Афкуксонь лемцень тязк путомада меле тонь лемце кармай эвондама лопаса тонь петнемацень ала.',
'prefs-help-email' => 'Электрононь адресце тяса аф лувови эрявикс, интай юкстандярят сувама валце адресце путомась лезды сонь полафтоманди.',
+'prefs-help-email-others' => 'Тондейть ули кода путомс корхнема лопазот е-паргцень, конань вельде иля ломатне сёрмадовихть тейть. Е-паргце аф кармай няеви мзярда иля тиихне тяшнелихть тейть.',
'prefs-help-email-required' => 'Эряви электрононь адресце.',
# User rights
'recentchanges-legend' => 'Мекольце полафнематнень арафнемасна',
'recentchanges-summary' => 'Ваномс сяда мекольце Викиса полафнематнень мельге тя лопаса.',
'recentchanges-feed-description' => 'Ваномс сяда мекольце Викиса полафнематнень мельге тя шудемаса.',
+'recentchanges-label-newpage' => 'Тя видептемась од лопа тись',
+'recentchanges-label-minor' => 'Тя ёмланя видептема',
+'recentchanges-label-bot' => 'Тя видептемась тизе кона-бди робот програм',
+'recentchanges-label-unpatrolled' => 'Тя видептемась ашесь пова патруль ала ни',
'rcnote' => "Ала {{PLURAL:$1|мекольце '''1''' полафнема|мекольце '''$1''' полафнемат}} '''$2''' ётай {{PLURAL:$2|шис|шис}}, $5, $4ста.",
'rcnotefrom' => "Ала няфтезь полафнематне '''$2'''-ста ('''$1'''-с).",
'rclistfrom' => 'Няфтемс од полафнематне $1-ста ушедомс',
'filehist-dimensions' => 'Кувалма',
'filehist-filesize' => 'Файлонь кувалмоц',
'filehist-comment' => 'Мяльполаткс',
-'imagelinks' => 'Файл сюлмафкст',
+'imagelinks' => 'Файлань тевс нолнема',
'linkstoimage' => 'Сай {{PLURAL:$1|лопась сюлмаф|$1 лопатне сюлмафт}} вага тя файлть мархта:',
'linkstoimage-more' => '$1-да лама {{PLURAL:$1|лопа сюлмаф|лопат сюлмафт}} тя файлть мархта.
Тя лувомаса няфневихть {{PLURAL:$1|васенце лопань сюлмафксоц|васенце $1 лопань сюлмафкссна}} аньцек тя файлть мархта.
'morelinkstoimage' => 'Ванк [[Special:WhatLinksHere/$1|сяда лама сюлмафкст]] тя файлонди.',
'duplicatesoffile' => 'Сай {{PLURAL:$1|файлсь ащи кафонзафксокс|$1 файлхне ащихть кафонзафксокс}} тя файлонди ([[Special:FileDuplicateSearch/$2|сяда лама информацие]]):',
'sharedupload' => 'Тя файлсь $1ста ди сонь ули кода сувафтомс иля проектс.',
+'sharedupload-desc-here' => 'Тя файлась $1ста ди сонь ули кода сувафтомс иля проектс.
+Колганза тяштьф [$2 файлать азондома лопазонза] конась няфтеви ала.',
'uploadnewversion-linktext' => 'Тонгодемс тя файлонь од верзиенц',
# File reversion
'listusers' => 'Тиихне',
'listusers-editsonly' => 'Няфтемс аньцек петнематнень мархта тиихнень',
'usereditcount' => '$1 {{PLURAL:$1|петнема|петнемат}}',
+'usercreated' => '{{GENDER:$3|Шкаф}} $1 шиста $2 пингста',
'newpages' => 'Од лопат',
'newpages-username' => 'Тиить лемоц:',
'ancientpages' => 'Сембода сире лопат',
# Watchlist
'watchlist' => 'Монь мельгеваномазе',
'mywatchlist' => 'Монь мельгеваномазе',
+'watchlistfor2' => '$1 $2-нди',
'nowatchlist' => 'Мезеге аш тонь мельгеваномасот.',
'watchlistanontext' => '$1 тонь ванома мельгеваномаста лопат ваноманкса эли петнеманкса.',
'watchnologin' => 'Апак сувак',
'confirmdeletetext' => 'Тон сърхкать нардамс лопать сембе сонь историянц мархта.
Эняльттяма, кемокстак тон афкукс ёрат тянь тиемс, эди тон шарьхкодьсак мезе лиси тяда меле, ди тон сембе тянь тисак [[{{MediaWiki:Policy-url}}|политик]] коряс.',
'actioncomplete' => 'Тевонь тиемась анок',
+'actionfailed' => 'Тиемась изь лисе',
'deletedtext' => 'Лопась "$1" нардафоль. Ванк $2 мекольце нардаматнень няфтеманкса.',
'dellogpage' => 'Нардамань лувома',
'dellogpagetext' => 'Ватт сяда мекольце нардаматнень лувомась ала.',
'undelete-nodiff' => 'Сядынгольдень верзиет исть мув.',
'undeletebtn' => 'Мърдафтомс',
'undeletelink' => 'ваномс/мърдафтомс',
+'undeleteviewlink' => 'ваномс',
'undeletereset' => 'Валхтомс',
'undeleteinvert' => 'Валхтомс кочкама',
'undeletecomment' => 'Мяльполаткс:',
'sp-contributions-newbies-title' => 'Тиить путксонза од сёрматфтоматненди',
'sp-contributions-blocklog' => 'Сёлгомань лувомась',
'sp-contributions-deleted' => 'нардаф тиинь путксонза',
+'sp-contributions-uploads' => 'Тонгодемат',
+'sp-contributions-logs' => 'Сувама лувомат',
'sp-contributions-talk' => 'корхтама',
'sp-contributions-userrights' => 'тиинь видекснень вятема',
'sp-contributions-search' => 'Вешендемс путкст',
'sp-contributions-username' => 'IP адрес эли тиить лемоц:',
+'sp-contributions-toponly' => 'Няфтемс аньцек мекольце верзиетнень ёткса видептематне',
'sp-contributions-submit' => 'Вешендема',
# What links here
'allmessagestext' => 'Тя MediaWiki-са васьфневи системонь пачфтематнень лувомась.
Эняльттяма, сувак [//www.mediawiki.org/wiki/Localisation MediaWiki Локализациес] ди [//translatewiki.net translatewiki.net-с] кда тонь мяльце тиемс эсь путксце марстонь MediaWiki локализациес.',
'allmessagesnotsupportedDB' => "Тя лопас аш кода кунцемс сяс мес '''\$wgUseDatabaseMessages'''лоткафоль.",
+'allmessages-language' => 'Кяль:',
+'allmessages-filter-submit' => 'Ётамс',
# Thumbnails
'thumbnail-more' => 'Оцюлгофтомс',
'thumbnail_error' => 'Миниатюр тиема эльбятькс: $1',
'djvu_page_error' => 'DjVu лопась аф сатови',
'djvu_no_xml' => 'Аш кода латцемс XML DjVu файлти',
+'thumbnail-temp-create' => 'Пингонь миниатюрац аф тиеви',
+'thumbnail-dest-create' => 'Миниатюрась аф ванфтови коза эряви',
'thumbnail_invalid_params' => 'Аф кондясти миниатюронь арафнеманза',
'thumbnail_dest_directory' => 'Аш кода ушедомс од вастонь директориесь',
+'thumbnail_image-type' => 'Тя няйфкс форматсь аф нежедеви',
# Special:Import
'import' => 'Таргамс лопат',
Сембе ётковикинь таргама тефне тяшневихть [[Special:Log/import|таргамань лувомас]].',
'import-interwiki-source' => 'Вики лисьма/лопа:',
'import-interwiki-history' => 'Копияфтомс тя лопать сембе историянь верзиенза',
+'import-interwiki-templates' => 'Сувафтомс сембе шаблотт',
'import-interwiki-submit' => 'Таргамс',
'import-interwiki-namespace' => 'Пачфтема лемботма:',
+'import-interwiki-rootpage' => 'Ёнонь юрлопась (кочкамать коряс):',
'import-upload-filename' => 'Файллем:',
'import-comment' => 'Мяльполаткс:',
-'importtext' => 'Эняльттяма таргак файлть Вики лисьмостонза [[Special:Export|вима лезкссь]] тевс нолдазь, ванфтт тянь тонь содама машинаса ди тонк тязк.',
+'importtext' => 'Эняльттяма таргак файлать Вики лисьмостонза [[Special:Export|вима лезкссь]]. Ванфтк содама машиназот ди тонк тяза.',
'importstart' => 'Лопатне тарксевихть...',
'import-revision-count' => '$1 {{PLURAL:$1|илякстоптома|илякстоптомат}}',
'importnopages' => 'Ашет лопат таргаманди.',
+'imported-log-entries' => 'Таргак $1 {{PLURAL:$1|лувонь тяштема|лувонь тяштемат}}.',
'importfailed' => 'Таргамась колавсь: <nowiki>$1</nowiki>',
'importunknownsource' => 'Аф содаф таргама лисьмоть сортоц',
'importcantopen' => 'Аш кода панжемс таргама файлть',
'import-upload' => 'Тонгомс XML информациесь',
'import-token-mismatch' => 'Сессиянь нформациесь юмась. Тяряфтт тага весть.',
'import-invalid-interwiki' => 'Аш кода таргамс кочкаф Викить.',
+'import-error-edit' => '"$1" лопась апак тонк сяс мес тейть аф мярьгови петеманза.',
+'import-error-create' => '"$1" лопась апак тонк сяс мес тейть аф мярьгови тиеманза.',
+'import-error-interwiki' => '"$1" лопась апак тонк сяс мес лемоц ванфтфоль ушеширень сюлмаманди (interwiki).',
+'import-error-special' => '"$1" лопась апак тонк сяс мес сон башка лемботмоса, конаса лопат кардафт.',
+'import-error-invalid' => '"$1" лопась апак тонк сяс мес лемоц аф кондясти.',
+'import-options-wrong' => 'Аф виде {{PLURAL:$2|кочкама|кочкамат}}: <nowiki>$1</nowiki>',
# Import log
'importlogpage' => 'Таргамань лувома',
'tooltip-rollback' => '"Потафтфкс" мърдафтсыне петнематне мекольце тиинь путксонц лопазонза фкя люпштамас.',
'tooltip-undo' => '"Каряньфтема" мърдафтсыне тя петнемать эди панжесы петнема форм васень няфтемаса.
Лезни поладомс туфталхт лихтемать эс.',
+'tooltip-summary' => 'Тяштьк нюрьхкяняста сувафтфть колга',
# Metadata
'notacceptable' => 'Вики серверонди аш кода максомс информациесь стама форматса конань эса тонь клиентти ули кода сонь морафтомс.',
#Путт сембе васу валзюлмафксонь пакшензон тя луфть (строкать) вельфке. Катт тя луфть (строкать) стамкс кодамкс сон ульсь</pre>',
+# Special:Tags
+'tag-filter' => '[[Special:Tags|Tag]] педямась:',
+
# New logging system
'revdelete-restricted' => 'нолдаф тевс кардафксне системонь вятиксненди',
'revdelete-unrestricted' => 'системонь вятиксненди кардафксне валхтфт',
'qbbrowse' => 'Tadiavina',
'qbedit' => 'Hanova',
'qbpageoptions' => 'Ity pejy ity',
-'qbpageinfo' => 'Pejy fanoroana',
'qbmyoptions' => 'Ny pejiko',
'qbspecialpages' => 'Pejy manokana',
'faq' => 'FMM',
'ns-specialprotected' => "Tsy afaka ovaina ny pejy anatin'ny toeran'anarana « {{ns:special}} » .",
'titleprotected' => "Voaaron'i [[User:$1|$1]] ity lohateny ity mba tsy hamorona pejy mitondra ity anarana ity.
Ny antony napetraka dia : « ''$2'' ».",
+'invalidtitle-knownnamespace' => 'Lohateny tsy miady amin\'ny fepetra miaraka amin\'ny anaram-balam-pejy "$2" ary soratra "$3"',
'exception-nologin' => 'Tsy tafiditra',
# Virus scanner
'invalidemailaddress' => 'Tsy mety io imailaka nalefanao io satria tsy manaraka ny firafitra tokony ho izy.
Azafady manomeza adiresy voasoratra tsara na avelao ho banga io toerana io.',
'cannotchangeemail' => "Tsy afaka ovaina eto amin'ity wiki ity ny adiresy imailaky ny kaonty.",
+'emaildisabled' => 'Tsy afaka mandefa imailaka ity tranonkala ity.',
'accountcreated' => 'Kaonty voaforona',
'accountcreatedtext' => "Voasokatra ilay kaonty hoan'i $1.",
'createaccount-title' => "Fanokafana kaonty ho an'ny/i {{SITENAME}}",
* '''[{{SERVER}}{{localurl:{{NAMESPACE}}:{{PAGENAME}}|action=edit}} Na forony eto ny lahatsoratra momba ny {{PAGENAME}}]'''.",
'noarticletext-nopermission' => "Mbola tsy misy lahatsoratra ao amin'io pejy io.
-Azonao atao ny [[Special:Search/{{PAGENAME}}|Mikaroka momba ny lohatenin'io pejy io]] ao amin'ny pejy hafa, mitady <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} anatin'ny laogy mikasika azy]</span>",
+Azonao atao ny [[Special:Search/{{PAGENAME}}|mikaroka ity lohateny ity]] eny amin'ny pejy hafa na <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} mitady ao amin'ny laogy misy fifandraisana]</span>, fa tsy azonao atao ny mamorona ity pejy ity.",
'userpage-userdoesnotexist' => 'Mbola tsy nisoratra anarana ato i « <nowiki>$1</nowiki> ». Marino raha tena hamorona ity pejy ity ianao.',
'userpage-userdoesnotexist-view' => 'Tsy nisoratra anarana ato i « $1 ».',
'blocked-notice-logextract' => "Ankehitriny ity mpikambana ity dia voasakana.
'edit-already-exists' => 'Tsy afaka amboarina ilay pejy vaovao.
Efa misy izy.',
'defaultmessagetext' => 'Hafatra raha tsy misy',
+'invalid-content-data' => "Data anaty votoatiny tsy miady amin'ny fepetra",
# Parser/template warnings
'expensive-parserfunction-warning' => 'Tandremo : Betsaka loatra ny fanantsoana ny tao parser.
'upload-too-many-redirects' => "Be loatra ny fihodinan'ny URL.",
'upload-unknown-size' => 'tsy fantatra ny habe',
'upload-http-error' => 'Nisy tsy fetezana HTTP nitranga : $1',
+'upload-copy-upload-invalid-domain' => "Tsy misy eto amin'ity dômenina ity ny tahaky ny upload.",
# File backend
'backend-fail-stream' => 'Tsy afaka mamaky ilay rakitra $1.',
'backend-fail-backup' => 'Tsy afaka mitahiry ilay rakitra $1.',
+'backend-fail-notexists' => 'Tsy misy ilay rakitra $1.',
'backend-fail-hashes' => "Tsy azo ilay hash an-drakitra ho an'ny fampitahana.",
'backend-fail-notsame' => "Efa misy rakitra samihafa ho an'i $1",
'backend-fail-invalidpath' => '$1 dia lalam-pitahirizana tsy azo raisina.',
'mostlinkedtemplates' => "Misy firohizana betsaka amin'ny endrika",
'mostcategories' => 'Lahatsoratra misy sokajy betsaka indrindra',
'mostimages' => "Misy firohizana betsaka amin'ny sary",
+'mostinterwikis' => 'Pejy be interwiki indrindra',
'mostrevisions' => 'Lahatsoratra niova im-betsaka indrindra',
'prefixindex' => "Pejy manomboka amin'ny...",
'prefixindex-namespace' => 'Ny pejy rehetra mitondra ny tovona (anaran-tsehatra $1)',
Azonao ferana ny fahitana ny tao amin'ny fisafidianana karazana laogy iray, anaram-pikambana iray na pejy iray (samihafa ny sorabaventy sy soramadinika).",
'logempty' => 'Tsy nahitana.',
'log-title-wildcard' => "Hitady amin'ny lohateny manomboka amin'io soratra io",
+'showhideselectedlogentries' => 'Haneho/Hanafina ny iditry ny laogy nofidiana',
# Special:AllPages
'allpages' => 'Pejy rehetra',
'allpagesprefix' => "Asehoy ny pejy miantomboka amin'ny:",
'allpagesbadtitle' => 'Tsy mety ny anaram-pejy : misy tovona iraisam-piteny na interwiki natokana, na misy soratra iray na maro tsy azo ampiasaina anaty anaram-pejy.',
'allpages-bad-ns' => '{{SITENAME}} dia tsy manana anaran-tsehatra mitondra anarana « $1 ».',
+'allpages-hide-redirects' => 'Haneho ny fihodinana',
+
+# SpecialCachedPage
+'cachedspecial-refresh-now' => 'Hijery ny farany indrindra',
# Special:Categories
'categories' => 'Sokajy',
'mailnologin' => 'Tsy misy adiresy handefasana ny tenimiafina',
'mailnologintext' => "Mila [[Special:UserLogin|miditra]] ianao sady manana imailaka mandeha sy voamarina ao amin'ny [[Special:Preferences|mombamomba anao]] vao afaka mandefa imailaka amin'ny mpikambana hafa.",
'emailuser' => 'Andefaso imailaka io mpikambana io',
+'emailuser-title-notarget' => "Handefa imailaka an'ilay mpikambana",
'emailpage' => 'Andefaso imailaka io mpikambana io',
'emailpagetext' => "Raha nametraka adiresy tena miasa tao amin'ny [[Special:Preferences|mombamomba azy io mpikambana io]],
dia ahafahana mandefa hafatra tokana ho any aminy ity fisy eto ambany ity.
'qbbrowse' => 'Прелистај',
'qbedit' => 'Уреди',
'qbpageoptions' => 'Оваа страница',
-'qbpageinfo' => 'Содржина на страница',
'qbmyoptions' => 'Мои страници',
'qbspecialpages' => 'Специјални страници',
'faq' => 'ЧПП',
'lastmodifiedat' => 'Оваа страница последен пат е изменета на $1 во $2 ч.',
'viewcount' => 'Оваа страница била посетена {{PLURAL:$1|еднаш|$1 пати}}.',
'protectedpage' => 'Заштитена страница',
-'jumpto' => 'Скокни на:',
+'jumpto' => 'Ð\9fÑ\80еÑ\98ди на:',
'jumptonavigation' => 'содржини',
'jumptosearch' => 'барај',
'view-pool-error' => 'За жал во моментов опслужувачите се преоптоварени.
'edit-already-exists' => 'Не може да се создаде нова страница.
Истата веќе постои.',
'defaultmessagetext' => 'Текст на пораката по основно',
+'content-failed-to-parse' => 'Не успеав да ја предадам содржината од типот $2 за моделот $1: $3',
+'invalid-content-data' => 'Неважечки податоци од содржината',
+'content-not-allowed-here' => 'Содржините од моделот „$1“ не се допуштени на страницата [[$2]]',
+
+# Content models
+'content-model-wikitext' => 'викитекст',
+'content-model-text' => 'прост текст',
+'content-model-javascript' => 'JavaScript',
+'content-model-css' => 'CSS',
# Parser/template warnings
'expensive-parserfunction-warning' => 'Предупредување: Оваа страница користи премногу повикувања на parser функции.
'shared-repo' => 'заедничко складиште',
'shared-repo-name-wikimediacommons' => 'Заедничката Ризница',
'filepage.css' => '/* Тука поставените каскадни стилски страници (CSS) се вклучени во страницата за опис на податотеката, како и на клиентските викија */',
-'upload-disallowed-here' => 'Нажалост, не можете да ја замените сликава со нова.',
+'upload-disallowed-here' => 'Нажалост, не можете да презапишете врз сликава.',
# File reversion
'filerevert' => 'Врати $1',
'undeletedrevisions' => '{{PLURAL:$1|1 измена е обновена|$1 измени се обновени}}',
'undeletedrevisions-files' => '{{PLURAL:$1|1 измена|$1 измени}} и {{PLURAL:$2|1 податотека|$2 податотеки}} се вратени',
'undeletedfiles' => '{{PLURAL:$1|1 податотека е вратена|$1 податотеки се вратени}}',
-'cannotundelete' => 'Враќањето не успеа. Можеби некој друг веќе ја вратил страницата.',
+'cannotundelete' => 'Враќањето не успеа:
+$1',
'undeletedpage' => "'''$1 беше обновена'''
Погледнете го [[Special:Log/delete|дневникот на бришења]] за попис на претходни бришења и обновувања.",
'immobile-target-namespace-iw' => 'Меѓувики-врска не може да се користи за преименување на страници.',
'immobile-source-page' => 'Оваа страница не може да се преместува.',
'immobile-target-page' => 'Не може да се премести под бараниот наслов.',
+'bad-target-model' => 'Саканата одредница користи друг содржински модел. Не можам да претворам од $1 во $2.',
'imagenocrossnamespace' => 'Не може да се премести податотека во неподатотечен именски простор',
'nonfile-cannot-move-to-file' => 'Не можам да преместам неподатотека во податотечен именски простор',
'imagetypemismatch' => 'Новата наставка на податотеката не соодветствува на нејзиниот тип',
# Info page
'pageinfo-title' => 'Информации за „$1“',
+'pageinfo-not-current' => 'Информациите може да се прикажат само за тековната ревизија.',
'pageinfo-header-basic' => 'Основни информации',
'pageinfo-header-edits' => 'Историја на уредувања',
'pageinfo-header-restrictions' => 'Заштита на страницата',
'dberr-cachederror' => 'Следнава содржина е кеширана копија на бараната страница, која може да е застарена.',
# HTML forms
-'htmlform-invalid-input' => 'Ð\98ма пÑ\80облеми Ñ\81о дел од ваÑ\88иоÑ\82 внес',
-'htmlform-select-badoption' => 'Ð\92Ñ\80едноÑ\81Ñ\82а коÑ\98а Ñ\98а наведовÑ\82е не е важеÑ\87ка.',
+'htmlform-invalid-input' => 'Ð\98ма пÑ\80облеми Ñ\81о дел од ваÑ\88иоÑ\82 внос',
+'htmlform-select-badoption' => 'УкажанаÑ\82а вÑ\80едноÑ\81Ñ\82 е неважеÑ\87ка како можноÑ\81Ñ\82.',
'htmlform-int-invalid' => 'Вредноста која ја наведовте не е цел број.',
'htmlform-float-invalid' => 'Вредноста која ја наведовте не е број.',
'htmlform-int-toolow' => 'Вредноста која ја наведовте е под минимумот од $1',
'qbbrowse' => 'ബ്രൗസ്',
'qbedit' => 'തിരുത്തുക',
'qbpageoptions' => 'ഈ താൾ',
-'qbpageinfo' => 'സന്ദർഭം',
'qbmyoptions' => 'എന്റെ താളുകൾ',
'qbspecialpages' => 'പ്രത്യേക താളുകൾ',
'faq' => 'പതിവുചോദ്യങ്ങൾ',
'edit-already-exists' => 'പുതിയ താൾ സൃഷ്ടിക്കാൻ കഴിഞ്ഞില്ല.
താൾ ഇപ്പോൾ തന്നെ നിലവിലുണ്ട്.',
'defaultmessagetext' => 'സ്വതേയുള്ള സന്ദേശ എഴുത്ത്',
+'content-failed-to-parse' => '$2 ഉള്ളടക്കം $1 മാതൃകയിൽ പാഴ്സ് ചെയ്യൽ പരാജയപ്പെട്ടു: $3',
+'invalid-content-data' => 'അസാധുവായ ഉള്ളടക്ക ഡേറ്റ',
+'content-not-allowed-here' => '"$1" ഉള്ളടക്കം [[$2]] താളിൽ അനുവദിക്കുന്നില്ല',
+
+# Content models
+'content-model-wikitext' => 'വിക്കിഎഴുത്ത്',
+'content-model-text' => 'ശുദ്ധ എഴുത്ത്',
+'content-model-javascript' => 'ജാവാസ്ക്രിപ്റ്റ്',
+'content-model-css' => 'സി.എസ്.എസ്.',
# Parser/template warnings
'expensive-parserfunction-warning' => "'''മുന്നറിയിപ്പ്:''' ഈ താളിൽ വളരെക്കൂടുതൽ പാഴ്സർ ഫങ്ഷനുകൾ വിളിച്ചിരിക്കുന്നു.
'shared-repo' => 'ഒരു പങ്കുവെക്കപ്പെട്ട സംഭരണി',
'shared-repo-name-wikimediacommons' => 'വിക്കിമീഡിയ കോമൺസ്',
'filepage.css' => '/* ഇവിടെ നൽകുന്ന സി.എസ്.എസ്. പ്രമാണ വിവരണ താളുകളിൽ ഉൾപ്പെടുത്തപ്പെടുന്നതായിരിക്കും, ബാഹ്യ ക്ലൈന്റ് വിക്കികളിലും അത് ലഭ്യമായിരിക്കും */',
-'upload-disallowed-here' => 'നിർà´à´¾à´\97àµ\8dയവശാൽ à´\88 à´\9aà´¿à´¤àµ\8dà´°à´¤àµ\8dതിനàµ\81 à´®àµ\81à´\95ളിൽ മറàµ\8dà´±àµ\8aà´°àµ\81 à´\9aà´¿à´¤àµ\8dà´°ം ചേർക്കാൻ താങ്കൾക്ക് കഴിയില്ല.',
+'upload-disallowed-here' => 'à´\88 à´ªàµ\8dരമാണതàµ\8dതിനàµ\81 à´®àµ\81à´\95ളിൽ മറàµ\8dà´±àµ\8aà´°àµ\81 à´ªàµ\8dരമാണം ചേർക്കാൻ താങ്കൾക്ക് കഴിയില്ല.',
# File reversion
'filerevert' => '$1 തിരസ്ക്കരിക്കുക',
'undeletedrevisions' => '{{PLURAL:$1|ഒരു പതിപ്പ്|$1 പതിപ്പുകൾ}} പുനഃസ്ഥാപിച്ചിരിക്കുന്നു',
'undeletedrevisions-files' => '{{PLURAL:$1|ഒരു പതിപ്പും|$1 പതിപ്പുകളും}} {{PLURAL:$2|ഒരു പ്രമാണവും|$2 പ്രമാണങ്ങളും}} പുനഃസ്ഥാപിച്ചിരിക്കുന്നു',
'undeletedfiles' => '{{PLURAL:$1|ഒരു പ്രമാണം|$1 പ്രമാണങ്ങൾ}} പുനഃസ്ഥാപിച്ചു',
-'cannotundelete' => 'മായ്ക്കൽ തിരസ്ക്കരിക്കാനുള്ള ശ്രമം പരാജയപ്പെട്ടു. മറ്റാരെങ്കിലും ഇതിനു മുൻപ് മായ്ക്കൽ തിരസ്ക്കരിച്ചിരിക്കാം.',
+'cannotundelete' => 'മായ്ക്കൽ തിരസ്കരണം പരാജയപ്പെട്ടു:
+$1',
'undeletedpage' => "'''$1 പുനഃസ്ഥാപിച്ചിരിക്കുന്നു'''
പുതിയതായി നടന്ന ഒഴിവാക്കലുകളുടേയും പുനഃസ്ഥാപനങ്ങളുടേയും വിവരങ്ങൾ കാണാൻ [[Special:Log/delete|മായ്ക്കൽ ലോഗ്]] കാണുക.",
'immobile-target-namespace-iw' => 'അന്തർവിക്കി കണ്ണി താൾ മാറ്റാനുള്ള സാധുവായ ലക്ഷ്യമല്ല.',
'immobile-source-page' => 'ഈ താൾ മാറ്റാൻ സാദ്ധ്യമല്ല',
'immobile-target-page' => 'ലക്ഷ്യമാക്കിയ തലക്കെട്ടിലേക്ക് മാറ്റാൻ സാധിക്കില്ല.',
+'bad-target-model' => 'ആഗ്രഹിക്കുന്ന ലക്ഷ്യം മറ്റൊരു ഉള്ളടക്ക മാതൃകയാണ് ഉപയോഗിക്കുന്നത്. $1 എന്നതിനെ $2 ആക്കി മാറ്റാൻ കഴിയില്ല.',
'imagenocrossnamespace' => 'പ്രമാണം അതിനായി അല്ലാത്ത നാമമേഖലയിലേയ്ക്ക് മാറ്റാൻ കഴിയില്ല',
'nonfile-cannot-move-to-file' => 'പ്രമാണമല്ലാത്തവ പ്രമാണം നാമമേഖലയിലേയ്ക്ക് മാറ്റാൻ കഴിയില്ല.',
'imagetypemismatch' => 'പുതിയ പ്രമാണത്തിന്റെ എക്സ്റ്റെൻഷൻ അതിന്റെ തരവുമായി ഒത്തുപോകുന്നില്ല.',
# Info page
'pageinfo-title' => '"$1" എന്ന താളിന്റെ വിവരങ്ങൾ',
+'pageinfo-not-current' => 'ഇപ്പോഴത്തെ നാൾപ്പതിപ്പിൽ മാത്രമേ വിവരങ്ങൾ പ്രദർശിപ്പിക്കപ്പെടാനിടയുള്ളു.',
'pageinfo-header-basic' => 'അടിസ്ഥാനവിവരങ്ങൾ',
'pageinfo-header-edits' => 'തിരുത്തൽചരിത്രം',
'pageinfo-header-restrictions' => 'സംരക്ഷണം',
'pageinfo-magic-words' => 'മാന്ത്രിക{{PLURAL:$1|വാക്ക്|വാക്കുകൾ}} ($1)',
'pageinfo-hidden-categories' => 'മറഞ്ഞിരിക്കുന്ന {{PLURAL:$1|വർഗ്ഗം|വർഗ്ഗങ്ങൾ}} ($1)',
'pageinfo-templates' => 'ഉൾപ്പെടുത്തിയിട്ടുള്ള {{PLURAL:$1|ഫലകം|ഫലകങ്ങൾ}} ($1)',
+'pageinfo-toolboxlink' => 'താളിന്റെ വിവരങ്ങൾ',
# Skin names
'skinname-standard' => 'സാർവത്രികം',
# Scary transclusion
'scarytranscludedisabled' => '[അന്തർവിക്കി ഉൾപ്പെടുത്തൽ സജ്ജമല്ല]',
'scarytranscludefailed' => '[$1-നു ഫലകം കണ്ടുപിടിക്കാൻ പറ്റിയില്ല]',
+'scarytranscludefailed-httpstatus' => '[$1-നു ഫലകം എടുക്കാൻ കഴിഞ്ഞില്ല: എച്ച്.റ്റി.റ്റി.പി. $2]',
'scarytranscludetoolong' => '[വളരെ നീളക്കൂടുതലുള്ള യൂ.ആർ.എൽ.]',
# Delete conflict
'qbbrowse' => 'Дэлгэх',
'qbedit' => 'Засварлах',
'qbpageoptions' => 'Энэ хуудас',
-'qbpageinfo' => 'Агуулга',
'qbmyoptions' => 'Миний хуудсууд',
'qbspecialpages' => 'Тусгай хуудсууд',
'faq' => 'Тогтмол тавигддаг асуултууд',
'qbbrowse' => 'न्याहाळा',
'qbedit' => 'संपादन',
'qbpageoptions' => 'हे पान',
-'qbpageinfo' => 'सामग्री',
'qbmyoptions' => 'माझी पाने',
'qbspecialpages' => 'विशेष पाने',
'faq' => 'नेहमीची प्रश्नावली',
'qbbrowse' => 'Semak imbas',
'qbedit' => 'Sunting',
'qbpageoptions' => 'Laman ini',
-'qbpageinfo' => 'Konteks',
'qbmyoptions' => 'Laman-laman saya',
'qbspecialpages' => 'Laman khas',
'faq' => 'Soalan Lazim',
'edit-no-change' => 'Suntingan anda diabaikan kerana tiada perubahan dibuat pada teks tersebut.',
'edit-already-exists' => 'Tidak dapat mencipta laman baru kerana ia telah wujud.',
'defaultmessagetext' => 'Teks mesej asal',
+'content-failed-to-parse' => 'Kandungan $2 tidak dapat dihuraikan untuk model $1: $3',
+'invalid-content-data' => 'Data kandungan tidak sah',
+'content-not-allowed-here' => 'Kandungan "$1" tidak dibenarkan di halaman [[$2]]',
+
+# Content models
+'content-model-wikitext' => 'wikiteks',
+'content-model-text' => 'teks biasa',
+'content-model-javascript' => 'JavaScript',
+'content-model-css' => 'CSS',
# Parser/template warnings
'expensive-parserfunction-warning' => 'Amaran: Laman ini mengandungi terlalu banyak panggilan fungsi penghurai yang intensif.
'undeletedrevisions' => '$1 semakan dipulihkan',
'undeletedrevisions-files' => '$1 semakan dan $2 fail dipulihkan',
'undeletedfiles' => '$1 fail dipulihkan',
-'cannotundelete' => 'Penyahhapusan gagal; mungkin orang lain telah pun mengnyahhapuskannya.',
+'cannotundelete' => 'Penyahhapusan gagal: $1',
'undeletedpage' => "'''$1 telah dipulihkan'''
Sila rujuk [[Special:Log/delete|log penghapusan]] untuk rekod penghapusan terkini.",
'immobile-target-namespace-iw' => 'Pautan interwiki tidak boleh dijadikan sasaran untuk pemindahan laman.',
'immobile-source-page' => 'Anda tidak boleh memindahkan laman ini.',
'immobile-target-page' => 'Anda tidak boleh memindahkan laman ke tajuk itu.',
+'bad-target-model' => 'Destinasi yang dikehendaki menggunakan model kandungan yang berlainan. $1 tidak dapat ditukar kepada $2.',
'imagenocrossnamespace' => 'Anda tidak boleh memindahkan fail ke ruang nama bukan fail',
'nonfile-cannot-move-to-file' => 'Laman bukan fail tidak boleh dipindahkan ke ruang nama fail',
'imagetypemismatch' => 'Sambungan baru fail tersebut tidak sepadan dengan jenisnya',
'pageinfo-magic-words' => 'Kata sakti ($1)',
'pageinfo-hidden-categories' => 'Kategori tersembunyi ($1)',
'pageinfo-templates' => 'Templat tertransklusi ($1)',
+'pageinfo-toolboxlink' => 'Maklumat halaman',
# Skin names
'skinname-standard' => 'Klasik',
# Scary transclusion
'scarytranscludedisabled' => '[Penyertaan pautan interwiki dilumpuhkan]',
'scarytranscludefailed' => '[Gagal mendapatkan templat $1]',
+'scarytranscludefailed-httpstatus' => '[Ambilan templat gagal untuk $1: HTTP $2]',
'scarytranscludetoolong' => '[URL terlalu panjang]',
# Delete conflict
'qbbrowse' => 'Qalleb',
'qbedit' => 'Immodifika',
'qbpageoptions' => 'Din il-paġna',
-'qbpageinfo' => 'Kuntest',
'qbmyoptions' => 'Il-paġni tiegħi',
'qbspecialpages' => 'Paġni speċjali',
'faq' => 'Mistoqsijiet komuni',
'qbbrowse' => 'Bla gjennom',
'qbedit' => 'Rediger',
'qbpageoptions' => 'Sideinnstillinger',
-'qbpageinfo' => 'Sideinformasjon',
'qbmyoptions' => 'Egne innstillinger',
'qbspecialpages' => 'Spesialsider',
'faq' => 'Ofte stilte spørsmål',
'vector-action-protect' => 'Beskytt',
'vector-action-undelete' => 'Gjenopprett',
'vector-action-unprotect' => 'Endre beskyttelse',
-'vector-simplesearch-preference' => 'Aktiver forbedrede søkeforslag (kun for drakten Vector)',
+'vector-simplesearch-preference' => 'Aktiver forenklet søkefelt (kun for drakten Vector)',
'vector-view-create' => 'Opprett',
'vector-view-edit' => 'Rediger',
'vector-view-history' => 'Vis historikk',
'edit-no-change' => 'Redigeringen din ble ignorert fordi det ikke var noen endringer.',
'edit-already-exists' => 'Kunne ikke opprette ny side fordi den finnes fra før.',
'defaultmessagetext' => 'Standard meldingstekst',
+'content-failed-to-parse' => 'Klarte ikke å tolke innholdet $2 for innholdsmodellen $1: $3',
+'invalid-content-data' => 'Ugyldig innhold',
+'content-not-allowed-here' => 'Innholdsmodellen «$1» er ikke tillatt på siden [[$2]]',
+
+# Content models
+'content-model-wikitext' => 'WikiTekst',
+'content-model-text' => 'Ren tekst',
+'content-model-javascript' => 'JavaScript',
+'content-model-css' => 'CSS',
# Parser/template warnings
'expensive-parserfunction-warning' => 'Advarsel: Denne siden inneholder for mange prosesskrevende parserfunksjoner.
'undeletedrevisions' => '{{PLURAL:$1|Én revisjon|$1 revisjoner}} gjenopprettet',
'undeletedrevisions-files' => '{{PLURAL:$1|Én revisjon|$1 revisjoner}} og {{PLURAL:$2|én fil|$2 filer}} gjenopprettet',
'undeletedfiles' => '{{PLURAL:$1|Én fil|$1 filer}} gjenopprettet',
-'cannotundelete' => 'Kunne ikke gjenopprette siden (den kan være gjenopprettet av noen andre).',
+'cannotundelete' => 'Gjennoppretting feilet:
+$1',
'undeletedpage' => "'''$1 ble gjenopprettet'''
Sjekk [[Special:Log/delete|slettingsloggen]] for en liste over nylige slettinger og gjenopprettelser.",
'immobile-target-namespace-iw' => 'Du kan ikke flytte en side til et navn som er en interwikilenke.',
'immobile-source-page' => 'Denne siden kan ikke flyttes.',
'immobile-target-page' => 'Kan ikke flytte til det navnet.',
+'bad-target-model' => 'Det ønskede målet bruker en annen innholdsmodell. Kan ikke konvertere fra $1 til $2.',
'imagenocrossnamespace' => 'Kan ikke flytte filer til andre navnerom enn filnavnerommet',
'nonfile-cannot-move-to-file' => 'Kan ikke flytte ikke-filer til filnavnerom',
'imagetypemismatch' => 'Den nye filendelsen tilsvarer ikke filtypen',
'pageinfo-magic-words' => '{{PLURAL:$1|Magisk|Magiske}} ord ($1)',
'pageinfo-hidden-categories' => '{{PLURAL:$1|Skjult kategori|Skjulte kategorier}} ($1)',
'pageinfo-templates' => 'Transkludert {{PLURAL:$1|mal|maler}} ($1)',
+'pageinfo-toolboxlink' => 'Sideinformasjon',
# Skin names
'skinname-standard' => 'Standard',
# Scary transclusion
'scarytranscludedisabled' => '[Interwiki-transkludering er slått av]',
'scarytranscludefailed' => '[Malen kunne ikke hentes for $1]',
+'scarytranscludefailed-httpstatus' => '[Henting av mal for $1 feilet: HTTP $2]',
'scarytranscludetoolong' => '[URL-en er for lang]',
# Delete conflict
'qbbrowse' => 'Blädern',
'qbedit' => 'Ännern',
'qbpageoptions' => 'Disse Sied',
-'qbpageinfo' => 'Sietendaten',
'qbmyoptions' => 'Instellen',
'qbspecialpages' => 'Spezialsieten',
'faq' => 'Faken stellte Fragen',
'qbbrowse' => 'Blaojen',
'qbedit' => 'Bewark',
'qbpageoptions' => 'Disse zied',
-'qbpageinfo' => 'Ziedinformasie',
'qbmyoptions' => 'Veurkeuren',
'qbspecialpages' => 'Spesiale ziejen',
'faq' => 'Vragen die vake esteld wörden',
'qbbrowse' => 'ब्राउज गर्ने',
'qbedit' => 'सम्पादन गर्ने',
'qbpageoptions' => 'यो पेज',
-'qbpageinfo' => 'सन्दर्भ',
'qbmyoptions' => 'मेरो पेज',
'qbspecialpages' => 'विशेष पृष्ठहरु',
'faq' => 'धैरै सोधिएका प्रश्नहरु',
* @author Trijnstel
* @author Troefkaart
* @author Tvdm
+ * @author Wiki13
* @author לערי ריינהארט
*/
'qbbrowse' => 'Bladeren',
'qbedit' => 'Bewerken',
'qbpageoptions' => 'Deze pagina',
-'qbpageinfo' => 'Pagina-informatie',
'qbmyoptions' => "Mijn pagina's",
'qbspecialpages' => 'Speciale pagina’s',
'faq' => 'Veel gestelde vragen',
'edit-already-exists' => 'De pagina is niet aangemaakt.
Deze bestaat al.',
'defaultmessagetext' => 'Standaardinhoud',
+'content-failed-to-parse' => 'Het was niet mogelijk de inhoud van het MIME-type $2 voor het model $1 te verwerken: $3.',
+'invalid-content-data' => 'Ongeldige inhoudsgegevens',
+'content-not-allowed-here' => 'De inhoud "$1" is niet toegestaan op pagina [[$2]].',
+
+# Content models
+'content-model-wikitext' => 'wikitekst',
+'content-model-text' => 'platte tekst',
+'content-model-javascript' => 'JavaScript',
+'content-model-css' => 'CSS',
# Parser/template warnings
'expensive-parserfunction-warning' => "'''Waarschuwing:''' deze pagina gebruikt te veel kostbare parserfuncties.
'shared-repo' => 'een gedeelde mediadatabank',
'shared-repo-name-wikimediacommons' => 'Wikimedia Commons',
'filepage.css' => "/ * De CSS die hier geplaatst wordt, wordt opgenomen in de bestandsbeschrijvingspagina en wordt ook opgenomen op externe wiki's (via externe repositories) * /",
-'upload-disallowed-here' => 'U kunt deze afbeelding helaas niet overschrijven.',
+'upload-disallowed-here' => 'U kunt dit bestand niet overschrijven.',
# File reversion
'filerevert' => '$1 terugdraaien',
'undeletedrevisions' => '$1 {{PLURAL:$1|versie|versies}} teruggeplaatst',
'undeletedrevisions-files' => '{{PLURAL:$1|1 versie|$1 versies}} en {{PLURAL:$2|1 bestand|$2 bestanden}} teruggeplaatst',
'undeletedfiles' => '{{PLURAL:$1|1 bestand|$1 bestanden}} teruggeplaatst',
-'cannotundelete' => 'Het terugplaatsen is mislukt.
-Misschien heeft een andere gebruiker de pagina al teruggeplaatst.',
+'cannotundelete' => 'Het terugplaatsen is mislukt:
+$1',
'undeletedpage' => "'''$1 is teruggeplaatst'''
In het [[Special:Log/delete|verwijderingslogboek]] staan recente verwijderingen en herstelhandelingen.",
'immobile-target-namespace-iw' => 'Een interwikiverwijzing is geen geldige bestemming voor het hernoemen van een pagina.',
'immobile-source-page' => 'Deze pagina kan niet hernoemd worden.',
'immobile-target-page' => 'Het is niet mogelijk te hernoemen naar die paginanaam.',
+'bad-target-model' => 'De gewenste bestemming gebruikt een ander inhoudsmodel. Het is niet mogelijk om te zetten van $1 naar $2.',
'imagenocrossnamespace' => 'Een mediabestand kan niet naar een andere naamruimte verplaatst worden',
'nonfile-cannot-move-to-file' => 'Het is niet mogelijk te hernoemen van en naar de bestandsnaamruimte',
'imagetypemismatch' => 'De nieuwe bestandsextensie is niet gelijk aan het bestandstype',
# Info page
'pageinfo-title' => 'Informatie over "$1"',
+'pageinfo-not-current' => 'Gegegevens worden mogelijk alleen weergegeven voor de huidige versie.',
'pageinfo-header-basic' => 'Basisgegevens',
'pageinfo-header-edits' => 'Bewerkingsgeschiedenis',
'pageinfo-header-restrictions' => 'Paginabeveiliging',
'pageinfo-magic-words' => '{{PLURAL:$1|Magisch woord|Magische woorden}} ($1)',
'pageinfo-hidden-categories' => 'Verborgen {{PLURAL:$1|categorie|categorieën}} ($1)',
'pageinfo-templates' => '{{PLURAL:$1|Gebruikt sjabloon|Gebruikte sjablonen}} ($1)',
+'pageinfo-toolboxlink' => 'Paginagegevens',
# Skin names
'skinname-standard' => 'Klassiek',
# Scary transclusion
'scarytranscludedisabled' => '[Interwiki-invoeging van sjablonen is uitgeschakeld]',
'scarytranscludefailed' => '[Het sjabloon $1 kon niet opgehaald worden]',
+'scarytranscludefailed-httpstatus' => '[Het sjabloon $1 kon niet opgehaald worden: HTTP $2]',
'scarytranscludetoolong' => '[De URL is te lang]',
# Delete conflict
* @author Guttorm Flatabø
* @author H92
* @author Harald Khan
+ * @author Jeblad
* @author Jon Harald Søby
* @author Jorunn
* @author Kaganer
'qbbrowse' => 'Bla gjennom',
'qbedit' => 'Endre',
'qbpageoptions' => 'Denne sida',
-'qbpageinfo' => 'Samanheng',
'qbmyoptions' => 'Sidene mine',
'qbspecialpages' => 'Spesialsider',
'faq' => 'OSS',
Det siste loggelementet er oppgjeve under som referanse:",
'templatesused' => '{{PLURAL:$1|Mal|Malar}} som er brukte på denne sida:',
'templatesusedpreview' => '{{PLURAL:$1|Mal som er brukt|Malar som er brukte}} i førehandsvisinga:',
-'templatesusedsection' => '{{PLURAL:$1|Mal|Malar}} som er brukte i denne bolken:',
+'templatesusedsection' => '{{PLURAL:$1|Mal som er brukt|Malar som er brukte}} i denne bolken:',
'template-protected' => '(verna)',
'template-semiprotected' => '(delvis verna)',
'hiddencategories' => 'Denne sida er med i {{PLURAL:$1|éin gøymd kategori|$1 gøymde kategoriar}}:',
Du bør tenkje over om det er høveleg å halde fram med å endre denne sida.
Sletteloggen for sida finn du her:",
-'moveddeleted-notice' => 'Denne sida er vorten sletta. Sletteloggen og flytteloggen er vist nedanfor for referanse.',
+'moveddeleted-notice' => 'Sida er vorten sletta. Sletteloggen og flytteloggen er viste nedanfor for referanse.',
'log-fulllog' => 'Sjå full loggføring',
'edit-hook-aborted' => 'Endring avbroten av ein funksjon, utan forklaring.',
'edit-gone-missing' => 'Kunne ikkje oppdatere sida.
'edit-no-change' => 'Redigeringa di vart ignorert fordi det ikkje vart gjort endringar i teksten.',
'edit-already-exists' => 'Kunne ikkje opprette ny side fordi ho alt eksisterer.',
'defaultmessagetext' => 'Standard meldingstekst',
+'content-failed-to-parse' => 'Klarte ikkje å tolke innhaldet «$2» for innhaldsmodellen «$1»: $3',
+'invalid-content-data' => 'Ugyldig innhald',
+'content-not-allowed-here' => 'Innhaldsmodellen «$1» er ikkje tillaten på sida [[$2]]',
+
+# Content models
+'content-model-wikitext' => 'WikiTekst',
+'content-model-text' => 'Rein tekst',
+'content-model-javascript' => 'JavaScript',
+'content-model-css' => 'CSS',
# Parser/template warnings
'expensive-parserfunction-warning' => 'Åtvaring: Denne sida inneheld for mange prosesskrevande parserfunksjonar.
'recentchanges-legend' => 'Alternativ for siste endringar',
'recentchanges-summary' => 'På denne sida ser du dei sist endra sidene i {{SITENAME}}.',
'recentchanges-feed-description' => 'Fylg med på dei siste endringane på denne wikien med dette abonnementet.',
-'recentchanges-label-newpage' => 'Denne endringa oppretta ei ny side',
+'recentchanges-label-newpage' => 'Endringa oppretta ei ny side',
'recentchanges-label-minor' => 'Dette er ei mindre endring',
'recentchanges-label-bot' => 'Denne endringa vart gjort av ein bot',
-'recentchanges-label-unpatrolled' => 'Denne endringa er ikkje patruljert enno',
+'recentchanges-label-unpatrolled' => 'Endringa er ikkje patruljert enno',
'rcnote' => "Nedanfor er {{PLURAL:$1|den siste endringa gjord|dei siste '''$1''' endringane gjorde}} {{PLURAL:$2|den siste dagen|dei siste '''$2''' dagane}}, for $4, kl. $5.",
'rcnotefrom' => "Nedanfor vert opp til '''$1''' endringar sidan ''' $2''' viste.",
'rclistfrom' => 'Vis nye endringar sidan $1',
'filestatus' => 'Opphavsrettsstatus:',
'filesource' => 'Kjelde:',
'uploadedfiles' => 'Filer som er opplasta',
-'ignorewarning' => 'Oversjå åtvaringa og lagre fila',
+'ignorewarning' => 'Sjå bort frå åtvaringa og lagre fila likevel',
'ignorewarnings' => 'Oversjå åtvaringar',
'minlength1' => 'Filnamn må ha minst eitt teikn.',
'illegalfilename' => 'Filnamnet «$1» inneheld teikn som ikkje er tillatne i sidetitlar. Skift namn på fila og prøv på nytt.',
'upload-too-many-redirects' => 'URL-en inneheldt for mange omdirigeringar',
'upload-unknown-size' => 'Ukjend storleik',
'upload-http-error' => 'Ein HTTP-feil oppstod: $1',
+'upload-copy-upload-invalid-domain' => 'Kopiopplastingar er ikkje tilgjengelege frå dette domenet.',
# File backend
'backend-fail-stream' => 'Kunne ikkje strøyma fila «$1».',
'backend-fail-backup' => 'Kunne ikkje tryggingskopiera fila «$1».',
'backend-fail-notexists' => 'Fila $1 finst ikkje.',
+'backend-fail-hashes' => 'Kunne ikkje henta filnummer for samanlikning.',
'backend-fail-notsame' => 'Ein ikkje-identisk fil finst alt på «$1».',
'backend-fail-invalidpath' => '$1 er ikkje ein gyldig lagringsstig.',
'backend-fail-delete' => 'Kunne ikkje sletta fila «$1».',
'backend-fail-closetemp' => 'Kunne ikkje lata att mellombels fil.',
'backend-fail-read' => 'Kunne ikkje lesa fila «$1».',
'backend-fail-create' => 'Kunne ikkje oppretta fila «$1».',
+'backend-fail-maxsize' => 'Kunne ikkje skriva fila «$1» av di ho er større enn {{PLURAL:$2|éin byte|$2 byte}}.',
+'backend-fail-readonly' => "Largingsbaksystemet «$1» er for tida skriveverna. Oppgjeven grunn er: «''$2''»",
+'backend-fail-synced' => 'Fila «$1» er i ei inkonsistent stode i dei interne lagringsbaksystema',
+'backend-fail-connect' => 'Kunne ikkje kopla til filbaksystemet «$1».',
+'backend-fail-internal' => 'Ein ukjend feil oppstod i lagringsbaksystemet «$1».',
+
+# File journal errors
+'filejournal-fail-dbconnect' => 'Kunne ikkje kopla til journaldatabasen for lagringsbaksystemet «$1».',
+'filejournal-fail-dbquery' => 'Kunne ikkje oppdatera journaldatabasen for lagringsbaksystemet «$1».',
# Lock manager
+'lockmanager-notlocked' => 'Kunne ikkje låsa opp «$1» av di han ikkje er låst',
+'lockmanager-fail-closelock' => 'Kunne ikkje lata att låsefila for «$1».',
+'lockmanager-fail-deletelock' => 'Kunne ikkje sletta låsefila for «$1».',
+'lockmanager-fail-acquirelock' => 'Kunne ikkje henta lås for «$1».',
+'lockmanager-fail-openlock' => 'Kunne ikkje opna låsefila for «$1».',
'lockmanager-fail-releaselock' => 'Kunne ikkje løysa låsen for «$1».',
+'lockmanager-fail-db-bucket' => 'Kunne ikkje kontakta nok låsedatabasar i bytta $1.',
+'lockmanager-fail-db-release' => 'Kunne ikkje løysa låsane på databasen $1.',
+'lockmanager-fail-svr-acquire' => 'Kunne ikkje henta låsane på tenaren $1.',
+'lockmanager-fail-svr-release' => 'Kunne ikkje løysa låsane på tenaren $1.',
# ZipDirectoryReader
'zip-wrong-format' => 'Den oppgjevne fila var ikkje ei ZIP-fil',
'log' => 'Loggar',
'all-logs-page' => 'Alle offentlege loggar',
'alllogstext' => 'Kombinert vising av alle loggane på {{SITENAME}}. Du kan avgrense resultatet ved å velje loggtype, brukarnamn eller den sida som er påverka (hugs å skilje mellom store og små bokstavar)',
-'logempty' => 'Ingen treff i loggane.',
+'logempty' => 'Ingen element i loggen passar.',
'log-title-wildcard' => 'Søk i titlar som byrjar med denne teksten',
'showhideselectedlogentries' => 'Vis/gøym valde loggoppføringar',
'watchnochange' => 'Ingen av sidene i overvakingslista er endra i den valde perioden.',
'watchlist-details' => '{{PLURAL:$1|Éi side|$1 sider}} er overvaka, utanom diskusjonssider.',
'wlheader-enotif' => '* Funksjonen for endringsmeldingar per e-post er på.',
-'wlheader-showupdated' => "* Sider som har blitt endra sidan du sist såg på dei er '''utheva'''",
+'wlheader-showupdated' => "* Sider som har vorte endra sidan du sist såg på dei er '''utheva'''",
'watchmethod-recent' => 'sjekkar siste endringar for dei overvaka sidene',
'watchmethod-list' => 'sjekkar om dei overvaka sidene har blitt endra i det siste',
'watchlistcontains' => 'Overvakingslista di inneheld {{PLURAL:$1|éi side|$1 sider}}.',
'iteminvalidname' => 'Problem med «$1», ugyldig namn...',
'wlnote' => "Nedanfor er {{PLURAL:$1|den siste endringa|dei siste '''$1''' endringane}} {{PLURAL:$2|den siste timen|dei siste '''$2''' timane}}, for $3, kl. $4.",
-'wlshowlast' => 'Vis siste $1 timar $2 dagar $3',
+'wlshowlast' => 'Vis siste $1 timane $2 dagane $3',
'watchlist-options' => 'Alternativ for overvakingslista',
# Displayed when you click the "watch" button and it is in the process of watching
'watching' => 'Overvakar...',
'unwatching' => 'Fjernar frå overvakinglista...',
+'watcherrortext' => 'Det oppstod ein feil under endringa av overvakingsinnstillingane dine for «$1».',
'enotif_mailer' => '{{SITENAME}}-endringsmeldingssendar',
-'enotif_reset' => 'Merk alle sider som vitja',
+'enotif_reset' => 'Merk alle sidene som vitja',
'enotif_newpagetext' => 'Dette er ei ny side.',
'enotif_impersonal_salutation' => '{{SITENAME}}-brukar',
'changed' => 'endra',
'editcomment' => "Samandraget for endringa var: «''$1''».",
'revertpage' => 'Attenderulla endring gjord av [[Special:Contributions/$2|$2]] ([[User talk:$2|diskusjon]]) til siste versjonen av [[User:$1|$1]]',
'revertpage-nouser' => 'Tilbakestilte endringar av (brukarnamn fjerna) til den siste versjonen av [[User:$1|$1]]',
-'rollback-success' => 'Rulla attende endringane av $1, tilbake til siste versjon av $2.',
+'rollback-success' => 'Rulla attende endringane av $1, attende til siste versjonen av $2.',
# Edit tokens
'sessionfailure-title' => 'Feil med omgangen.',
'protect-title-notallowed' => 'Sjå vernenivået til «$1»',
'prot_1movedto2' => '«[[$1]]» flytt til «[[$2]]»',
'protect-badnamespace-title' => 'Namnerommet kan ikkje vernast',
+'protect-badnamespace-text' => 'Sider i dette namnerommet kan ikkje vernast.',
'protect-legend' => 'Stadfest vern',
'protectcomment' => 'Grunngjeving:',
'protectexpiry' => 'Endar:',
'undeletedrevisions' => '{{PLURAL:$1|Éin versjon|$1 versjonar}} attoppretta.',
'undeletedrevisions-files' => '{{PLURAL:$1|Éin versjon|$1 versjonar}} og {{PLURAL:$2|éi fil|$2 filer}} er attoppretta',
'undeletedfiles' => '{{PLURAL:$1|Éi fil|$1 filer}} er attoppretta',
-'cannotundelete' => 'Feil ved attoppretting, andre kan allereie ha attoppretta sida.',
+'cannotundelete' => 'Attopprettinga gjekk ikkje:
+$1',
'undeletedpage' => "'''$1 er attoppretta'''
Sjå [[Special:Log/delete|sletteloggen]] for eit oversyn over sider som nyleg er sletta eller attoppretta.",
'import-interwiki-templates' => 'Inkluder alle malar',
'import-interwiki-submit' => 'Importer',
'import-interwiki-namespace' => 'Målnamnerom:',
+'import-interwiki-rootpage' => 'Målrotside (valfri):',
'import-upload-filename' => 'Filnamn:',
'import-comment' => 'Kommentar:',
'importtext' => 'Lagre fila frå kjeldewikien med [[Special:Export|eksporteringsverktøyet]] på din eigen datamaskin, og last henne så opp her.',
'import-error-interwiki' => 'Sida «$1» vart ikkje importert sidan namnet hennar er reservert for ekstern lenking (interwiki).',
'import-error-special' => 'Sida «$1» vart ikkje importert sidan ho høyrer til eit spesialnamnerom som ikkje tillèt sider.',
'import-error-invalid' => 'Sida «$1» vart ikkje importert sidan namnet er ugildt.',
+'import-options-wrong' => '{{PLURAL:$2|Galt val|Gale val}}: <nowiki>$1</nowiki>',
+'import-rootpage-invalid' => 'Den oppgjevne rotsida er ein ugild tittel',
+'import-rootpage-nosubpage' => 'Namnerommet «$1» til rotsida tillèt ikkje undersider.',
# Import log
'importlogpage' => 'Importeringslogg',
'pageinfo-watchers' => 'Tal på overvakarar av sida',
'pageinfo-redirects-name' => 'Omdirigeringar til sida',
'pageinfo-subpages-name' => 'Undersider av sida',
+'pageinfo-subpages-value' => '$1 ({{PLURAL:$2|éi omdirigering|$2 omdirigeringar}}; {{PLURAL:$3|éi ikkje-omdirigering|$3 ikkje-omdirigeringar}})',
'pageinfo-firstuser' => 'Sideopprettar',
'pageinfo-firsttime' => 'Dato for opprettinga av sida',
'pageinfo-lastuser' => 'Siste forfattaren',
'file-info-size-pages' => '$1 × $2 pikslar, filstorleik: $3, MIME-type: $4, {{PLURAL:$5|éi side|$5 sider}}',
'file-nohires' => 'Høgare oppløysing er ikkje tilgjengeleg.',
'svg-long-desc' => 'SVG-fil, standardoppløysing: $1 × $2 pikslar, filstorleik: $3',
+'svg-long-desc-animated' => 'Animert SVG-fil, standardoppløysing $1 × $2 pikslar, filstorleik: $3',
'show-big-image' => 'Full oppløysing',
'show-big-image-preview' => 'Storleik på førehandsvising: $1.',
'show-big-image-other' => '{{PLURAL:$2|Anna oppløysing|Andre oppløysingar}}: $1.',
'file-info-png-looped' => '↓oppatteke',
'file-info-png-repeat' => 'spela av {{PLURAL:$1|éin gong|$1 gonger}}',
'file-info-png-frames' => '$1 {{PLURAL:$1|bilete|bilete}}',
+'file-no-thumb-animation' => "'''Merk: Grunna tekniske avgrensingar vil ikkje miniatyrbilete av fila verta animerte.'''",
+'file-no-thumb-animation-gif' => "'''Merk: Grunna tekniska avgrensingar vil ikkje miniatyrbilete av høgoppløyselege GIF-bilete som dette verta animerte.'''",
# Special:NewFiles
'newimages' => 'Filgalleri',
'exif-languagecode' => 'Språk',
'exif-iimversion' => 'IIM-versjon',
'exif-iimcategory' => 'Kategori',
+'exif-datetimeexpires' => 'Skal ikkje nyttast etter',
+'exif-datetimereleased' => 'Frigjeve',
'exif-lens' => 'Objektiv',
+'exif-serialnumber' => 'Serienummeret på kameraet',
'exif-cameraownername' => 'Eigar av kameraet',
'exif-label' => 'Merkelapp',
+'exif-nickname' => 'Det uformelle namnet på biletet',
'exif-rating' => 'Vurdering (av 5)',
'exif-copyrighted' => 'Opphavsrettsstode',
'exif-copyrightowner' => 'Opphavsrettseigar',
'exif-usageterms' => 'Bruksvilkår',
+'exif-licenseurl' => 'URL for opphavsrettsløyve',
+'exif-morepermissionsurl' => 'Alternativ løyveinformasjon',
+'exif-attributionurl' => 'Når dette verket vert nytta, lenkja til',
+'exif-preferredattributionname' => 'Når dette verket vert nytta, godskriv',
'exif-pngfilecomment' => 'PNG-filkommentar',
'exif-disclaimer' => 'Atterhald',
'exif-contentwarning' => 'Innholdsåtvaring',
'exif-gpsdop-excellent' => 'Utmerkt ($1)',
'exif-gpsdop-good' => 'God ($1)',
'exif-gpsdop-moderate' => 'Moderat ($1)',
+'exif-gpsdop-fair' => 'Medels ($1)',
'exif-gpsdop-poor' => 'Dårleg ($1)',
'exif-objectcycle-a' => 'Berre morgon',
$5
+Denne stadfestingskoden vert forelda $4.',
+'confirmemail_body_changed' => 'Nokon, truleg deg, frå IP-adressa $1, har endra e-postadressa til kontoen «$2» på {{SITENAME}} til denne e-postadressa.
+
+For å stadfesta at denne kontoen faktisk høyrer til deg, og for å slå på
+funksjonar knytte til e-post på {{SITENAME}}, opna denne lenkja i nettlesaren din:
+
+$3
+
+Om brukarkontoen *ikkje* høyrer til deg, fylg denne lenkja for å bryta av stadfestinga av e-postadressa:
+
+$5
+
+Denne stadfestingskoden vert forelda $4.',
+'confirmemail_body_set' => 'Nokon, truleg deg, frå IP-adressa $1, har sett e-postadressa til kontoen «$2» på {{SITENAME}} til denne e-postadressa.
+
+For å stadfesta at denne kontoen faktisk høyrer til deg, og for å slå på
+funksjonar knytte til e-post på {{SITENAME}}, opna denne lenkja i nettlesaren din:
+
+$3
+
+Om brukarkontoen *ikkje* høyrer til deg, fylg denne lenkja for å bryta av stadfestinga av e-postadressa:
+
+$5
+
Denne stadfestingskoden vert forelda $4.',
'confirmemail_invalidated' => 'Stadfestinga av e-postadresse er avbrote',
'invalidateemail' => 'Avbryt stadfestinga av e-postadressa',
# Scary transclusion
'scarytranscludedisabled' => '[Interwiki-tilkopling er slått av]',
'scarytranscludefailed' => '[Henting av mal for $1 gjekk ikkje]',
+'scarytranscludefailed-httpstatus' => '[Henting av mal for $1 gjekk ikkje: HTTP $2]',
'scarytranscludetoolong' => '[URL-en er for lang]',
# Delete conflict
'tog-previewontop' => 'Bontšha Ponopele pele ga lepokisi la diphetogo',
'tog-previewonfirst' => 'Bontšha Ponopeleka phetogo ya pele',
'tog-nocache' => "Thibela go tsenya matlakala go segakolodi (''cache'')",
-'tog-enotifwatchlistpages' => 'Nromele molaetša ge letlaka leo ke le tlhapetšego le eba le diphetogo',
+'tog-enotifwatchlistpages' => 'Nthomele molaetša ge letlaka leo ke le tlhapetšego le eba le diphetogo',
'tog-enotifusertalkpages' => 'Nromele molaetša ge letlakala la Dipoledišano laka le fetoga',
-'tog-enotifminoredits' => 'Nromele email ge goba le diphetogo tše nnyenyane go matlakala',
+'tog-enotifminoredits' => 'Ethomele e-mail ge goba le diphetogo tše nnyenyane go matlakala',
'tog-enotifrevealaddr' => 'Bonagatša email atrese go temošo tša poso',
'tog-shownumberswatching' => 'Laetša palo bašomiši bao ba tlhapetšego',
'tog-fancysig' => 'Tsaeno ya gose fihliwe',
'protect' => 'Lota',
'protect_change' => 'Fetola go lotega',
'protectthispage' => 'Lota letlakala le',
-'unprotect' => 'Tloša go lota',
-'unprotectthispage' => 'Tloša go lota letlakaleng',
+'unprotect' => 'Fetola go lota',
+'unprotectthispage' => 'Fetola go lota letlakaleng',
'newpage' => 'Letlakala le lempsha',
'talkpage' => 'Rêrišana ka letlakala le',
'talkpagelinktext' => 'Bolela',
'jumpto' => 'Taboga go:',
'jumptonavigation' => 'Tšweletšo',
'jumptosearch' => 'fetleka',
+'pool-errorunknown' => 'Phošo yago setsebege',
# All link text and link target definitions of links into project namespace that get used by other message strings, with the exception of user group pages (see grouppage) and the disambiguation template definition (see disambiguations).
'aboutsite' => 'Mabapi le {{SITENAME}}',
'youhavenewmessages' => 'O na le $1 ($2).',
'newmessageslink' => 'ya melaetša ye mefsa',
'newmessagesdifflink' => 'phetogo ya mafelelo',
+'newmessagesdifflinkplural' => 'l{{PLURAL:$1|Phetogo tša|Diphetogo ya}}go feta',
'youhavenewmessagesmulti' => 'O nale melaetša ye mefsa go $1',
'editsection' => 'lokiša',
'editold' => 'fetola',
<span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} fetleka "logs"],
goba [{{fullurl:{{FULLPAGENAME}}|action=edit}} wa fetola letlakala le]</span>.',
'note' => "'''Ela hloko:'''",
-'previewnote' => "'''Ye ke Taetšo ya sebopego sa letlakala fela; diphetogo ga di ya bolokwa!'''",
+'previewnote' => "'''Elelwa gore ye ke taetšo ya sebopego sa letlakala fela.'''
+Diphetogo tša gago ga šetšo di bolokwa!",
'editing' => 'O fetola $1',
'editingsection' => 'Phetolo ya $1 (sekgoba)',
'editingcomment' => 'O fetola $1 (sekgao se sempsha)',
'revertmerge' => 'Tloša kopaganyo',
# Diffs
-'history-title' => 'Histori ya diphetogo tša "$1"',
+'history-title' => 'Poeletšo ya diphetogo tša "$1"',
'lineno' => 'Mothalo $1:',
'compareselectedversions' => 'Bapetša diphapang tšeo di kgethilwego',
'editundo' => 'dirolla',
'booksources-go' => 'Sepela',
# Special:Log
-'specialloguserlabel' => 'Mošomiši:',
-'speciallogtitlelabel' => 'Thaetlele:',
+'specialloguserlabel' => 'Modiri:',
+'speciallogtitlelabel' => 'Thaetlele (goba mošumiši):',
'log' => "Di-''log''",
-'all-logs-page' => "Di-''log'' kamoka",
+'all-logs-page' => "Di-''log'' tša bohle ka moka",
# Special:AllPages
'allpages' => 'Matlakala ka moka',
'deletedcontributions-title' => 'Diabe tša mošomiši tšeo di phumutšwego',
# Special:LinkSearch
-'linksearch' => 'Dihlomaganyo tša ntle',
+'linksearch' => 'Fehleka dihlomaganyo tša ntle',
'linksearch-ok' => 'Fetleka',
'linksearch-line' => '$1 e kgokaganywa gotšwa $2',
'emailuser' => 'Romela mošomiši yo molaetša',
'emailpage' => 'Romela email go mošomiši',
'noemailtitle' => 'Gago email atrese',
-'emailfrom' => 'Go tšwa go',
-'emailsubject' => 'Sebolelwa',
-'emailmessage' => 'Molaetša',
+'emailfrom' => 'Go tšwa go:',
+'emailsubject' => 'Sebolelwa:',
+'emailmessage' => 'Molaetša:',
'emailsend' => 'Romela',
'emailccme' => 'Nromela kopi ya melaetša.',
'emailccsubject' => 'Kopi ya molaetša wa gago goya go $1: $2',
# Rollback
'rollbacklink' => 'bošetša morago',
-'editcomment' => "Ahlaahlo ya phetogo ke : \"''\$1''\".",
+'editcomment' => "Ahlaahlo ya phetogo e bile : \"''\$1''\".",
# Protect
'protectlogpage' => "''Log'' yago lota",
'protect-expiring' => 'fetatšatši ke $1 (UTC)',
'protect-cascade' => 'Lota matlakala, akaretša le letlakala le (go lota ka kakaretšo)',
'protect-cantedit' => 'Ga o kgone go fetola tekano ya bolotego letlakaleng le, ka ge o sena tumello yago bofetola.',
-'protect-expiry-options' => '2 diiri:2 hours,1 letšatši:1 day,3 matšatši:3 days,1 beke:1 week,2 dibeke:2 weeks,1 kgwedi:1 month,3 digkwedi:3 months,6 dikgwedi:6 months,1 ngwaga:1 year,ga efele:infinite',
+'protect-expiry-options' => 'iri:1 hour, letšatš1:1 day,beke:1 week,dibeke tše 2:2 weeks,kgwedi:1 month,dikgwedi tše 3:3 months,dikgwedi tše 6:6 months,ngwaga:1 year,gosefele (infinite):infinite',
'restriction-type' => 'Tumello:',
'restriction-level' => 'Seemo sago Lota:',
'sp-contributions-newbies' => 'Laetša diabe tša bašumiši ba bafsa fela',
'sp-contributions-newbies-sub' => 'Tša tšhupaleloko tše mphsa',
'sp-contributions-blocklog' => "''Log'' yago thiba",
-'sp-contributions-deleted' => 'Diabe tša mošomiši tšeo di phumutšwego',
+'sp-contributions-deleted' => 'diabe tša mošomiši tšeo di phumutšwego',
'sp-contributions-uploads' => 'di-"upload"',
'sp-contributions-logs' => "Di-''log''",
'sp-contributions-talk' => 'Poledišano',
'qbbrowse' => 'Far desfilar',
'qbedit' => 'Modificar',
'qbpageoptions' => 'Opcions de la pagina',
-'qbpageinfo' => 'Pagina d’entresenhas',
'qbmyoptions' => 'Mas opcions',
'qbspecialpages' => 'Paginas especialas',
'faq' => 'FAQ',
'qbbrowse' => 'ଖୋଜିବା',
'qbedit' => 'ଏହାକୁ ବଦଳାନ୍ତୁ',
'qbpageoptions' => 'ଏହି ପୃଷ୍ଠାଟି',
-'qbpageinfo' => 'ଭିତର ଚିଜ',
'qbmyoptions' => 'ମୋ ପୃଷ୍ଠାଗୁଡ଼ିକ',
'qbspecialpages' => 'ବିଶେଷ ପୃଷ୍ଠା',
'faq' => 'ବାରମ୍ବାର ପଚରାଯାଉଥିବା ପ୍ରଶ୍ନ',
ଏହା ଅଗରୁ ଅଛି ।',
'defaultmessagetext' => 'ଡିଫଲ୍ଟ ମେସେଜ ଲେଖାଗୁଡିକ',
+# Content models
+'content-model-wikitext' => 'ଉଇକିଟେକ୍ସଟ',
+
# Parser/template warnings
'expensive-parserfunction-warning' => "'''ଚେତାବନୀ:''' ଏହି ପୃଷ୍ଠାରେ ଅନେକ ଗୁଡ଼ିଏ ମୂଲ୍ୟବାନ ପାର୍ସର ଫଙ୍କସନ କଲ ଅଛି ।
# Info page
'pageinfo-title' => '"$1"ର ବିବରଣୀ',
+'pageinfo-header-basic' => 'ସାଧାରଣ ଜାଣିବା କଥା',
'pageinfo-header-edits' => 'ବଦଳସବୁ',
'pageinfo-article-id' => 'ପୃଷ୍ଠା ଆଇଡ଼ି',
'pageinfo-views' => 'ଦେଖଣା ସଂଖ୍ୟା',
'pageinfo-watchers' => 'ଦେଖଣାହାରି ସଂଖ୍ୟା',
-'pageinfo-edits' => 'ସମ୍ପାଦନା ସଂଖ୍ୟା:',
+'pageinfo-edits' => 'ସମ୍ପାଦନା ସଂଖ୍ୟା',
'pageinfo-authors' => 'ନିଆରା ଲେଖକଙ୍କ ସଂଖ୍ୟା',
# Patrolling
'file-info-size-pages' => '$1 × $2 ପିକ୍ସେଲ, ଫାଇଲ ଆକାର: $3, MIME ପ୍ରକାର: $4, $5 ଗୋଟି {{PLURAL:$5|ପୃଷ୍ଠା|ପୃଷ୍ଠା}}',
'file-nohires' => 'ବଡ଼ ରେଜୋଲୁସନ ନାହିଁ ।',
'svg-long-desc' => 'SVG ଫାଇଲ, ସାଧାରଣ ମାପ $1 × $2 ପିକ୍ସେଲ, ଫାଇଲ ଆକାର: $3',
+'svg-long-desc-animated' => 'Animated SVG ଫାଇଲ, ସାଧାରଣ ମାପ $1 × $2 ପିକ୍ସେଲ, ଫାଇଲ ଆକାର: $3',
'show-big-image' => 'ପୁରା ବଡ଼ ଆକାରରେ',
'show-big-image-preview' => 'ଏହି ଦେଖଣାର ଆକାର: $1 ।',
'show-big-image-other' => 'ବାକି {{PLURAL:$2|ରେଜୋଲୁସନ|ରେଜୋଲୁସନ}}: $1.',
# Search suggestions
'searchsuggest-search' => 'ଖୋଜିବା',
+'searchsuggest-containing' => 'ଧାରଣ ହେଉଛି...',
# API errors
'api-error-badaccess-groups' => 'ଆପଣଙ୍କୁ ଏହି ଉଇକିରେ ଅପଲୋଡ଼ କରିବାକୁ ଅନୁମତି ଦିଆଯାଇନାହିଁ ।',
'qbbrowse' => 'Фен',
'qbedit' => 'Ивын',
'qbpageoptions' => 'Ацы фарс',
-'qbpageinfo' => 'Фарсы контекст',
'qbmyoptions' => 'Мæ фæрстæ',
'qbspecialpages' => 'Сæрмагонд фæрстæ',
'faq' => 'FAQ',
'projectpage' => 'Проекты фарс фен',
'imagepage' => 'Файлы фарс фен',
'mediawikipage' => 'Фыстæджы фарс фен',
-'templatepage' => 'ШаблонÑ\8b Ñ\84аÑ\80Ñ\81 Ñ\84ен',
+'templatepage' => 'Ð¥Ñ\83Ñ\8bзæджÑ\8b Ñ\84аÑ\80Ñ\81 Ñ\84енÑ\8bн',
'viewhelppage' => 'Æххуысы фарс фен',
'categorypage' => 'Категорийы фарс фен',
'viewtalkpage' => 'Тæрхон фен',
'nstab-project' => 'Проекты тыххæй',
'nstab-image' => 'Файл',
'nstab-mediawiki' => 'Фыстæг',
-'nstab-template' => 'Шаблон',
+'nstab-template' => 'Ð¥Ñ\83Ñ\8bзæг',
'nstab-help' => 'Æххуысы фарс',
'nstab-category' => 'Категори',
'editconflict' => 'Ивыны конфликт: $1',
'yourtext' => 'Дæ текст',
'yourdiff' => 'Хицæндзинæдтæ',
-'templatesused' => 'Ð\90Ñ\86Ñ\8b Ñ\84аÑ\80Ñ\81Ñ\8b иÑ\81 {{PLURAL:$1|Ñ\88аблон|Ñ\88аблоны}}:',
+'templatesused' => 'Ð\90Ñ\86Ñ\8b Ñ\84аÑ\80Ñ\81Ñ\8b иÑ\81 {{PLURAL:$1|Ñ\85Ñ\83Ñ\8bзæг|Ñ\85Ñ\83Ñ\8bзæджы}}:',
'template-protected' => '(æхгæд)',
'template-semiprotected' => '(æрдæг-æхгæд)',
'hiddencategories' => 'Ацы фарс у {{PLURAL:$1|1 æмбæхст категорийы|$1 æмбæхст категориты}} уæнг:',
'edit-already-exists' => 'Ног фарс скæнæн нæй. Ахæм фарс ис.',
# Parser/template warnings
-'post-expand-template-inclusion-warning' => "'''СÑ\8bндæг: ''' Шаблоны бавæрд бæрц æгæр стыр у.
-Ð\9aæÑ\86Ñ\8bдæÑ\80 Ñ\88аблонтæ нæ бавæд уыдзысты.",
-'post-expand-template-inclusion-category' => 'ФæÑ\80Ñ\81Ñ\82æ, кæм Ñ\88аблоны бавæрд бæрц æгæр бирæ у',
-'post-expand-template-argument-warning' => "'''СÑ\8bндæг:''' Ð\90Ñ\86Ñ\8b Ñ\84аÑ\80Ñ\81Ñ\8b иÑ\81 Ñ\83æддæÑ\80 иÑ\83 Ñ\88аблоны аргумент, кæй райтынг у æгæр стыр.
+'post-expand-template-inclusion-warning' => "'''СÑ\8bндæг: ''' Ð¥Ñ\83Ñ\8bзæджы бавæрд бæрц æгæр стыр у.
+Ð\9aæÑ\86Ñ\8bдæÑ\80 Ñ\85Ñ\83Ñ\8bзæгтæ нæ бавæд уыдзысты.",
+'post-expand-template-inclusion-category' => 'ФæÑ\80Ñ\81Ñ\82æ, кæм Ñ\85Ñ\83Ñ\8bзæджы бавæрд бæрц æгæр бирæ у',
+'post-expand-template-argument-warning' => "'''СÑ\8bндæг:''' Ð\90Ñ\86Ñ\8b Ñ\84аÑ\80Ñ\81Ñ\8b иÑ\81 Ñ\83æддæÑ\80 иÑ\83 Ñ\85Ñ\83Ñ\8bзæджы аргумент, кæй райтынг у æгæр стыр.
Уыцы аргументтæ уагъд æрцыдысты.",
-'post-expand-template-argument-category' => 'ФæÑ\80Ñ\81Ñ\82æ, кæдонÑ\8b иÑ\81 Ñ\88аблоны уагъд аргумент',
+'post-expand-template-argument-category' => 'ФæÑ\80Ñ\81Ñ\82æ, кæдонÑ\8b иÑ\81 Ñ\85Ñ\83Ñ\8bзæджы уагъд аргумент',
# History pages
'viewpagelogs' => 'Ацы фарсæн йæ логтæ равдисын',
'download' => 'æрбавгæн',
# Unused templates
-'unusedtemplates' => 'Ð\9fайда кæмæй нæ Ñ\87Ñ\8bндæÑ\83Ñ\8b, аÑ\85æм Ñ\88аблонтæ',
+'unusedtemplates' => 'Ð\9fайда кæмæй нæ Ñ\87Ñ\8bндæÑ\83Ñ\8b, аÑ\85æм Ñ\85Ñ\83Ñ\8bзæгтæ',
# Random page
'randompage' => 'Халæй ист фарс',
'lonelypages' => 'Сидзæр фæрстæ',
'uncategorizedpages' => 'Æнæкатегори фæрстæ',
'uncategorizedimages' => 'Æнæкатегори файлтæ',
-'uncategorizedtemplates' => 'Ã\86нæкаÑ\82егоÑ\80и Ñ\88аблонтæ',
+'uncategorizedtemplates' => 'Ã\86нæкаÑ\82егоÑ\80и Ñ\85Ñ\83Ñ\8bзæгтæ',
'popularpages' => 'Популярон фæрстæ',
'wantedcategories' => 'Хъæугæ категоритæ',
'wantedpages' => 'Хъæугæ фæрстæ',
'tooltip-ca-nstab-special' => 'Ай сæрмагонд фарс у, дæ бон æй нæу ивын',
'tooltip-ca-nstab-project' => 'Фенын проекты фарс',
'tooltip-ca-nstab-image' => 'Нывы фарс',
-'tooltip-ca-nstab-template' => 'ФенÑ\8bн Ñ\88аблон',
+'tooltip-ca-nstab-template' => 'Ð¥Ñ\83Ñ\8bзæг Ñ\84енÑ\8bн',
'tooltip-ca-nstab-category' => 'Категорийы фарс',
'tooltip-minoredit' => 'Чысыл ивдæй йæ банысан кæнын',
'tooltip-save' => 'Бавæр дæ ивдтытæ',
'qbbrowse' => 'Bation (browse)',
'qbedit' => 'Mag-edit',
'qbpageoptions' => 'Ining bulung',
-'qbpageinfo' => 'Kontekstu/kabilian',
'qbmyoptions' => 'Deng kakung bulung',
'qbspecialpages' => 'Bulung a makabukud',
'faq' => 'Maralas a Kukutang (MAK)',
'tog-editsectiononrightclick' => "Pérmet l'édichion del sekchion par un droé buke su ch'tite del sekchion (i feut JavaScript)",
'tog-showtoc' => "Aficher l'tabe ed ches étnus (pou ches paches aveuc plu ed 3 intétes)",
'tog-rememberpassword' => "Warder min lodjine su chl'ordinateu-lo (pour un maximum éd $1 {{PLURAL:$1|jour|jours}})",
-'tog-watchcreations' => "Ajouter chés paches qu'éj crée dseu em lisse",
-'tog-watchdefault' => "Ajouter chés paches qu'éj édite dseu em lisse.",
-'tog-watchmoves' => "Ajouter chés paches qu'éj déplache dseu m'lisse.",
-'tog-watchdeletion' => "Ajouter chés paches qu'éj déface dseur m'lisse.",
+'tog-watchcreations' => "Ajouter chés paches qu'éj crée et pi chés fichiés qu'éj téléquertche édseur em lisse à suire",
+'tog-watchdefault' => "Ajouter chés paches pi chés fichiés qu'éj cange édseur em lisse à suire.",
+'tog-watchmoves' => "Ajouter chés paches pi chés fichiés qu'éj déplache édseur m'lisse à suire.",
+'tog-watchdeletion' => "Ajouter chés paches pi chés fichiés qu'éj déface édseur m'lisse à suire.",
'tog-previewontop' => "Aficher l'prévue édvint el bouéte édite",
'tog-previewonfirst' => "Aficher l'prévue au preumié édite.",
-'tog-enotifwatchlistpages' => "Éspédier din m'boéte un imèle quante eune pache su m'lisse est candgée",
+'tog-enotifwatchlistpages' => "M'éspédier un imèle quante eune pache su m'lisse à suire est cangée",
'tog-enotifusertalkpages' => 'Éspédier un imèle su em bouéte quante m\'pache "Dvise Uzeu" est candgée.',
-'tog-enotifminoredits' => 'Éspédier à mi étous un imèle pou ches tiots édites éd ches paches',
+'tog-enotifminoredits' => "M'éspédier étou un imèle pou chés tiots canjemints d'chés paches o d'chés fichiés",
'tog-shownumberswatching' => "Aficher ch'nombe ed gins qu'ont vu.",
'tog-watchlisthideown' => 'Muche mes édites dseur el lisse',
'tog-watchlisthidebots' => 'Muche ches robots édites su el lisse',
'qbbrowse' => 'Trifouille',
'qbedit' => 'Editer',
'qbpageoptions' => 'Chol pache-lo',
-'qbpageinfo' => 'Conteske',
'qbmyoptions' => 'Mes paches',
'qbspecialpages' => 'Espéciales paches',
'faq' => 'FAQ',
'vector-action-protect' => 'Garantir',
'vector-action-undelete' => "N'poin défacher",
'vector-action-unprotect' => "Canger l'garantie",
-'vector-simplesearch-preference' => "Déhousser chés avanches d'ércherche améliorées (seulemint pour Vector)",
+'vector-simplesearch-preference' => "Actionner l'barette pour chés traches simpes (seulemint pour l'habillure Vector)",
'vector-view-create' => 'Créer',
'vector-view-edit' => 'Éditer',
'vector-view-history' => "Vir l'histoère",
'hiddencategories' => '{{PLURAL:$1|Catégorie muchée|Catégories muchées}} pou chol pache:',
'permissionserrorstext-withaction' => "Vos n’avez poin l'pérmichon éd $2, pou {{PLURAL:$1|ch'motif suivant|chés motifs suivants}}:",
+# Parser/template warnings
+'post-expand-template-inclusion-warning' => "Affute : Chèle pache ale a trop d’modèles. Des inclusions n'sront poin foaites.",
+'post-expand-template-inclusion-category' => "Paches aveuc granmint d'modèles",
+
# History pages
'viewpagelogs' => 'Vir chés gasètes del pache-lo',
'currentrev-asof' => 'Coursaule vérchon in date du $1',
'revisionasof' => 'Ércordé conme $1',
+'revision-info' => 'Version du $1 pèr $2',
'previousrevision' => '← érvue dvant',
'nextrevision' => 'Cangemint pu nouvieu →',
'currentrevisionlink' => 'Érvision éd qhére',
# Revision deletion
'rev-delundel' => 'montrer/mucher',
'revdel-restore' => 'cange écmint vir',
+'revdel-restore-deleted' => 'canjemints abolis',
+'revdel-restore-visible' => 'canjemints visibes',
'pagehist' => 'Histoère del pache',
# Merge log
'nextn' => 'apreu {{PLURAL:$1|$1}}',
'prevn-title' => 'Dvant $1 {{PLURAL:$1|résultat|résultats}}',
'viewprevnext' => 'Vir ($1 {{int:pipe-separator}} $2) ($3)',
+'searchmenu-new' => "'''Créer l'pache « [[:$1|$1]] » édseur ech wiki !'''",
+'searchprofile-articles' => "Paches d'étnu",
+'searchprofile-project' => "Paches d’aïude et pi d'prodjé",
+'searchprofile-images' => 'Multimédia',
+'searchprofile-everything' => 'Tout',
+'searchprofile-advanced' => 'Értrache avanchée',
+'searchprofile-articles-tooltip' => 'tracher dins $1',
+'searchprofile-project-tooltip' => 'Tracher dins $1',
+'searchprofile-images-tooltip' => 'Tracher des fichiés multimédias',
+'searchprofile-everything-tooltip' => "Tracher dins tout ch'wikipédia (et ochi dins chés paches éd distchucion)",
+'searchprofile-advanced-tooltip' => "Couésir chés éspaches d'noms pour l'értrache",
'search-result-size' => '$1 ({{PLURAL:$2|1 mot|$2 mots}})',
'search-redirect' => '(érdirection $1)',
'search-section' => '(sekchon $1)',
'search-interwiki-caption' => 'Proujé analocq',
'search-interwiki-default' => '$1 résultats:',
'search-interwiki-more' => '(pus)',
+'searchall' => 'tout',
'nonefound' => "'''Note''': il y o tasseulemint quéques éspaces éd noms éq sont trachés pèr défeut. <br /> Pou tracher din tous chés contnus (paches éd pérlache, modéles, etc... comprins) insséyer in imploéyant ch'préfixe ''all:'' o bin imploéyer echl éspace éd noms édmindé conme préfixe.",
+'search-nonefound' => 'Y a autchun résultat pour chol dmanne.',
'powersearch' => 'Érvue avanchée',
'powersearch-legend' => 'Érvue avanchée',
'powersearch-ns' => 'Tracher din chés éspaches éd chés noms:',
# Associated actions - in the sentence "You do not have permission to X"
'action-read' => "Vir l'pache-lo",
'action-edit' => "édite l'pache-lo",
+'action-createpage' => 'créer des paches',
+'action-createtalk' => 'créer des paches éd dichtchussion',
+'action-createaccount' => "créer ech compte d'uzeu",
+'action-minoredit' => 'mértcher chol canjemint conme mineur',
+'action-move' => "érlonmer l'pache-lo",
+'action-move-subpages' => 'érlonmer chol pache et pi ses dsous-paches',
+'action-move-rootuserpages' => "érlonmer l' pache princhipale d’un uzeu",
+'action-movefile' => "érlonmer ch'fichié-lo",
+'action-upload' => 'téléquértcher ech fichié',
+'action-reupload' => "écatir l'anchien fichié",
+'action-upload_by_url' => "téléquértcher ch' fichié à partir d’eune URL",
+'action-writeapi' => "foaire aveuc l'écrivure API",
+'action-delete' => "Défacer l'pache-lo",
+'action-deleterevision' => "défacer ch'canjemint-lo",
+'action-deletedhistory' => 'vir l’histoère muchée éd chol pache',
+'action-browsearchive' => 'tracher des paches défacées',
+'action-undelete' => "n'poin défacer chol pache",
+'action-suppressrevision' => 'vir pi érfoaire chol version muchée',
+'action-suppressionlog' => 'vir ech jornal privé',
+'action-block' => "blotcher l'écrivure éd chol uzeu-lo",
+'action-import' => 'téléquértcher chol pache à partir d’un eute wiki',
+'action-importupload' => "téléquértcher chol pache à partir d'un fichié",
# Recent changes
'nchanges' => '$1 {{PLURAL:$1|cange|canges}}',
'uploadedimage' => '"[[$1]]" quértchée',
# File description page
+'file-anchor-link' => 'Fichié',
'filehist' => 'Histoère dech fichié',
'filehist-help' => "Buke su eune date/heure pou vir ch'fichié conme il étoait ach momint-lo.",
+'filehist-revert' => 'invérser',
'filehist-current' => 'courant',
'filehist-datetime' => 'Date/Tans',
'filehist-thumb' => 'Tiote image',
'filehist-user' => 'Uzeu',
'filehist-dimensions' => 'Diminsions',
'filehist-comment' => 'Fichié éd chés conmints',
-'imagelinks' => 'Loïens dech fichié',
+'imagelinks' => 'Usage dech fichié',
'linkstoimage' => "{{PLURAL:$1|L'pache d'apreu est liée|Chés $1 paches d'apreu sont liées}} à ch'fichié-lo :",
'sharedupload' => "Cht'fichié vient éd $1 pi i put ète imploïé par d'eutes proujés.",
+'sharedupload-desc-here' => "Ch'fichié i vient éd $1. I put ète uzer pèr d’eutes prodjés.
+Vir apré ([$2 pache]).",
'uploadnewversion-linktext' => 'Quértcher eune novèle vérchion del pache-lo',
# Random page
'nbytes' => '$1 {{PLURAL:$1|octé|octés}}',
'nmembers' => '$1 {{PLURAL:$1|mimbe|mimbes}}',
'prefixindex' => 'Tertous chés paches aveuc préfix',
+'usercreated' => '{{GENDER:$3|Créé}} ech $1 à $2',
'newpages' => 'Novèles paches',
'move' => 'Déplacher',
'movethispage' => "Déplacher l'pache-lo",
# Watchlist
'watchlist' => 'Em lisse à suire',
'mywatchlist' => "M'lisse à suire",
+'watchlistfor2' => 'Pour $1 $2',
'addedwatchtext' => "L' pache « [[:$1]] » o té rajoutée à vote [[Special:Watchlist|lisse à suire]].<br /> Chés canjemints à vnir del pache-lo pi del page éd pérlache sront mis din l'lisse. L'pache sro '''in cros''' din el [[Special:RecentChanges|lisse d'chés darins canjemints]] pou les értreuver fachilmint. Pou értirer chol pache del ''lisse à suire'', bukez su « {{MediaWiki:Unwatch}} ».",
'removedwatchtext' => "L'pache « [[:$1]] » o té értirée éd vote [[Special:Watchlist|lisse à suire]].",
'watch' => 'Suire',
# Undelete
'undeletelink' => 'vir/érfoaire',
+'undeleteviewlink' => 'Vir',
# Namespace form on various pages
'namespace' => 'Éspace du nom:',
'sp-contributions-newbies' => 'Montrer chés contérbuchons éd chés nouvieus conptes seulemint',
'sp-contributions-blocklog' => 'jornal éd chés blotcåjhes',
+'sp-contributions-logs' => 'Gasètes',
+'sp-contributions-talk' => 'Dviser',
'sp-contributions-search' => 'Tracher pou chés contérbuchons',
'sp-contributions-username' => "Adérche IP ou nom d'uzeu",
'sp-contributions-toponly' => "n'montrer qu'chés darins canjemints",
# Export
'export' => 'Ésporter chés paches',
+# Namespace 8 related
+'allmessagesname' => 'Nom',
+
# Thumbnails
'thumbnail-more' => 'Pu grand',
'tooltip-rollback' => '« Racacher » cancéle aveuc un clic el (ou chés) modificachon(s) del pache-lo pèr sin darin contérbucheu.',
'tooltip-undo' => "« Undo » ( ''démangler'' ) értire ch'canjemint-lo pi ouvre l' fénéte d'édichon din ch'mode ''prévir''. <br /> In put mette un motif din ch'résumé.",
'tooltip-preferences-save' => 'Warder chés préférinches.',
+'tooltip-summary' => 'Intrer un tiot résumè',
# Browsing diffs
'previousdiff' => '← Pu vieille édition',
'underline-default' => 'Des nemme, was em Broweser gsaacht hoscht.',
# Dates
+'sunday' => 'Sundaach',
+'monday' => 'Mondaach',
+'tuesday' => 'Dienschdaach',
+'wednesday' => 'Midwoch',
+'thursday' => 'Dunnaschdaach',
+'friday' => 'Fraidaach',
+'saturday' => 'Somschdaach',
'january' => 'Jänner',
'february' => 'Fewwer',
'march' => 'März',
'qbmyoptions' => 'Mai Saide',
# Vector skin
+'vector-action-delete' => 'Lesche',
'vector-action-move' => 'Verschiewe',
+'vector-action-protect' => 'Schitze',
'vector-view-edit' => 'Bearwaide',
+'vector-view-view' => 'Lese',
+'actions' => 'Agzione',
'errorpagetitle' => 'Fehler',
'returnto' => 'Zrick zu $1.',
De Leschaidrach fa die Said isch do unne als Kwell aagewwe.',
# History pages
-'viewpagelogs' => 'D Lochbiecher fer die Said aagucke',
+'viewpagelogs' => 'Lochbicher fer die Said aagucke',
'currentrev-asof' => 'Aktuelle Version vun $1',
'revisionasof' => 'Version vun $1',
'previousrevision' => '← Ältere Versione',
'rclinks' => 'Zeich die letschte $1 Ännerunge in de letschte $2 Dache<br />$3',
'diff' => 'Unnerschied',
'hist' => 'Gschicht',
-'hide' => 'versteggeln',
-'show' => 'zaiche',
+'hide' => 'vaschdeggle',
+'show' => 'zaische',
'minoreditletter' => 'k',
'newpageletter' => 'N',
'boteditletter' => 'B',
'file-anchor-link' => 'Datei',
'filehist' => 'Dateigschicht',
'filehist-help' => 'Drick uff e Zaidpunkt zum aazääche, wie s dort ausgsähne hot.',
+'filehist-revert' => 'zuriggsedze',
'filehist-current' => 'aktuell',
'filehist-datetime' => 'Zaidpunkt',
'filehist-thumb' => 'Vorschaubild',
'filehist-user' => 'Benutzer',
'filehist-dimensions' => 'Moß',
'filehist-comment' => 'Kommentar',
-'imagelinks' => 'Dateilinks',
+'imagelinks' => 'Dadaivawendung',
'linkstoimage' => 'Die {{PLURAL:$1|Said verwaist|$1 Saire verwaise}} uff die Datei:',
'sharedupload' => 'Die Datei isch vun $1 un s kann sai, dass se ach vun annere Projekt gebraucht werd.',
'uploadnewversion-linktext' => 'E naiere Version vun derre Datei hochlade',
'pager-older-n' => '{{PLURAL:$1|vorich 1|voriche $1}}',
# Book sources
-'booksources' => 'Buchquelle',
+'booksources' => 'Buchqwelle',
'booksources-search-legend' => 'No Buchquelle suche',
'booksources-go' => 'Geh',
# Special:Log
-'log' => 'Logbiecher',
+'log' => 'Logbicher',
# Special:AllPages
'allpages' => 'Alle Saide',
'linkshere' => "Die Saide verlinke zu '''[[:$1]]''':",
'isredirect' => 'Wairerlaitungsaid',
'istemplate' => 'Vorlacheaibindung',
-'isimage' => 'Bildlink',
+'isimage' => "Dadailing'g",
'whatlinkshere-prev' => '{{PLURAL:$1|vorich|voriche $1}}',
'whatlinkshere-next' => '{{PLURAL:$1|negscht|negschte $1}}',
'whatlinkshere-links' => '← Links',
'ipbsubmit' => 'Benutzer bloggiere',
'ipboptions' => '2 Stunne:2 hours,1 Dach:1 day,3 Dache:3 days,1 Woch:1 week,2 Woche:2 weeks,1 Monet:1 month,3 Monet:3 months,6 Monet:6 months,1 Johr:1 year,Fer immer:infinite',
'ipusubmit' => 'Die Adreß freigewwe',
-'ipblocklist' => 'Gsperrte IP-Adresse un Benutzername',
+'ipblocklist' => 'Gschberrdi IP-Adress un Benudzernome',
'blocklink' => 'sperre',
-'unblocklink' => 'Sperr uffhewwe',
+'unblocklink' => 'Sperr uffhewe',
'change-blocklink' => 'Sperr ännere',
'contribslink' => 'Baidräch',
'blocklogpage' => 'Sperrlogbuch',
'tooltip-p-logo' => 'Haubdsaid',
'tooltip-n-mainpage' => 'Uff d Hääptsaid geh',
'tooltip-n-mainpage-description' => 'Haubdsaid aagucke',
-'tooltip-n-portal' => 'Iwwer s Projekt, was de duu kannscht, wo de ebbes finnscht',
+'tooltip-n-portal' => 'Iwwers Brojegd, wude duu kannschd, wu ebbes finne duschd',
'tooltip-n-currentevents' => 'hinnergundsinformatione finne iwwer naie Eraichnis',
'tooltip-n-recentchanges' => 'D Lischt vun de letschte Ännerunge in dem Wiki',
'tooltip-n-randompage' => 'E zufälliche Said lade',
'tooltip-n-help' => 'De Ort zum rausfinne',
-'tooltip-t-whatlinkshere' => 'Lischt vun alle Wikisaide, wo do her verlinkt sin',
+'tooltip-t-whatlinkshere' => 'Lischt vun alle Wikisaide, wu do her verlinkt sinn',
'tooltip-t-recentchangeslinked' => 'Letschte Ännerunge in Saide, wu vun do verlinkt sin',
'tooltip-feed-rss' => 'RSS feed fer die Said',
'tooltip-feed-atom' => 'Atom feed fer die Said',
'tooltip-t-contributions' => 'Die letschte Baidräch vun däm Benutzer aagucke',
'tooltip-t-emailuser' => 'Dem Benutzer e E-Mail schicke',
-'tooltip-t-upload' => 'Dateie nuflade',
+'tooltip-t-upload' => 'Dateije nufflade',
'tooltip-t-specialpages' => 'Lischt vun alle Spezialsaide',
'tooltip-t-print' => 'Druckversion vun derre Said',
'tooltip-t-permalink' => 'E dauerhafte Link zu derre Version vun de Said',
'tooltip-diff' => 'Guck, welle Ännerunge Du im Text gmacht hoscht',
'tooltip-compareselectedversions' => 'D Unnerschied zwische denne zwai ausgwehlte Versione aagucke',
'tooltip-watch' => 'Die Said zu Dainer Beowachdunglischd zufieche',
-'tooltip-rollback' => '„Zericksetze“ maxcht alle Bearwaidunge vum letschte Bearwaider rickgängich',
+'tooltip-rollback' => '„Zericksetze“ machd alle Bearwaidunge vum letschte Bearwaider rickgängich',
'tooltip-undo' => '„Zerick“ macht numme die Ännerung rickgängich un zaicht d Vorschau aa.
Du kannscht e Grund in dr Zammfassung aagewwe',
Wenn die Datei verännert worre isch, dann kann s sai, dass die zusätzlich Information fer die verännert Datei nimmi richtich isch.',
'metadata-expand' => 'Erwaiterte Details aazaiche',
'metadata-collapse' => 'Erwaiterte Details versteckeln',
-'metadata-fields' => 'Die EXIF-Metadate werre in de Bild-Bschraiwung ach aagezeicht, wenn d Metadate-Tabelle versteckelt isch.
-Annere Metadate sinn standardmäßig versteckelt.
+'metadata-fields' => 'Die EXIF-Medadaade werren inde Bild-Bschraiwung a ogzaischd, wonn die Medadaade-Tabelle verschdegld isch.
+Annere Medadaade sinn noamalawais verschdegld.
* make
* model
* datetimeoriginal
'qbbrowse' => 'Przeglądanie',
'qbedit' => 'Edycja',
'qbpageoptions' => 'Ta strona',
-'qbpageinfo' => 'Kontekst',
'qbmyoptions' => 'Moje strony',
'qbspecialpages' => 'strony specjalne',
'faq' => 'FAQ',
'qbbrowse' => 'Sfeuja',
'qbedit' => 'Modìfica',
'qbpageoptions' => 'Opsion dla pàgina',
-'qbpageinfo' => 'Anformassion rësguard a la pàgina',
'qbmyoptions' => 'Mie opsion',
'qbspecialpages' => 'Pàgine speciaj',
'faq' => 'Chestion frequente',
'virus-unknownscanner' => 'antivìrus nen conossù:',
# Login and logout pages
-'logouttext' => "'''A l'é sortù da 'nt ël sistema.'''
+'logouttext' => "'''A l'é surtì da 'nt ël sistema.'''
-A peul tiré anans a dovré {{SITENAME}} coma Utent anonim, ò pura a peul <span class='plainlinks'>[$1 rintré torna ant ël sistema]</span> con l'istess stranòm che a dovrava prima, ò con un diferent.
-Ch'a nòta che chèich pàgine a peulo continué a esse visualisà com s'a fussa ancó ant ël sistema, fin ch'a scancela pa la cache ëd sò navigador.",
+A peul tiré anans a dovré {{SITENAME}} coma Utent anònim, ò pura a peul <span class='plainlinks'>[$1 rintré torna ant ël sistema]</span> con l'istess stranòm che a dovrava prima, ò con un diferent.
+Ch'a nòta che chèiche pàgine a peulo continué a esse visualisà com s'a fussa ancor ant ël sistema, fin ch'a scancela nen la memòria local ëd sò navigador.",
'welcomecreation' => '==Bin ëvnù, $1!==
Sò cont a l\'é stàit creà.
Che as dësmentia pa ëd cambié ij [[Special:Preferences|"sò gust" an {{SITENAME}}]].',
'edit-already-exists' => 'As peul nen creesse la pàgina.
A esist già.',
'defaultmessagetext' => "Test che a-i sarìa se a-i fusso pa 'd modìfiche",
+'content-failed-to-parse' => "Faliment ëd l'anàlisi dël contnù ëd $2 për ël model $1: $3",
+'invalid-content-data' => 'Dat dël contnù pa bon',
+'content-not-allowed-here' => "Ël contnù «$1» a l'é nen autorisà an sla pàgina [[$2]]",
+
+# Content models
+'content-model-wikitext' => 'test wiki',
+'content-model-text' => 'mach test',
+'content-model-javascript' => 'JavaScript',
+'content-model-css' => 'CSS',
# Parser/template warnings
'expensive-parserfunction-warning' => "'''Atension:''' Costa pàgina a l'ha tròpe ciamà costose a le fonsions ëd parser.
'undeletedrevisions' => '{{PLURAL:$1|Na revision pijàita|$1 revision pijàite}} andré',
'undeletedrevisions-files' => "{{PLURAL:$1|Na|$1}} revision e {{PLURAL:$2|n'|$2 }}archivi pijàit andré",
'undeletedfiles' => "{{PLURAL:$1|N'|$1 }}archivi pijàit andaré",
-'cannotundelete' => "Riprìstin falì; a peul esse che i fusse antra doi a felo ant l'istess temp e l'àutr a sia riva prima.",
+'cannotundelete' => 'Riprìstin falì:
+$1',
'undeletedpage' => "'''$1 a l'é stàit pijait andaré'''
Che as varda ël [[Special:Log/delete|Registr djë scancelament]] për ës-ciairé j'ùltim scancelament e arcuperassion.",
'immobile-target-namespace-iw' => "Na liura interwiki a l'é pa na destinassion vàlida për tramudé na pàgina.",
'immobile-source-page' => 'Sta pàgina-sì as peul pa tramudesse.',
'immobile-target-page' => 'As peul pa tramudesse vers cost tìtol ëd destinassion.',
+'bad-target-model' => 'La destinassion vorsùa a deuvra un model ëd contnù diferent. As peul pa convertisse da $1 a $2.',
'imagenocrossnamespace' => "As peul pa tramudesse n'archivi a në spassi nominal diferent",
'nonfile-cannot-move-to-file' => "As peul nen tramudesse lòn ch'a l'é pa n'archivi a lë spassi nominal dj'archivi",
'imagetypemismatch' => "La neuva estension ëd l'archivi a corispond pa a sò tipo",
'pageinfo-magic-words' => '{{PLURAL:$1|Paròla màgica|Paròle màgiche}} ($1)',
'pageinfo-hidden-categories' => '{{PLURAL:$1|Categorìa|Categorìe}} stërmà ($1)',
'pageinfo-templates' => '{{PLURAL:$1|stamp contnù|stamp contnù}} ($1)',
+'pageinfo-toolboxlink' => 'Anformassion an sla pàgina',
# Patrolling
'markaspatrolleddiff' => 'Marché coma verificà',
'exif-copyrighted' => "Stat dël drit d'autor",
'exif-copyrightowner' => "Titolar dël drit d'autor",
'exif-usageterms' => "Condission d'utilisassion",
-'exif-webstatement' => "Diciarassion an linia dël drit d'autor",
-'exif-originaldocumentid' => 'ID unìvoch dël document original',
+'exif-webstatement' => "Diciarassion ëd drit d'autor an linia",
+'exif-originaldocumentid' => 'Identificativ ùnich dël papé original',
'exif-licenseurl' => "Anliura ëd la licensa dij drit d'autor",
-'exif-morepermissionsurl' => 'Anformassion an sle license alternativa',
-'exif-attributionurl' => "Quand as deuvra torna cost travaj, për piasì ch'a-j buta l'anliura a",
+'exif-morepermissionsurl' => 'Anformassion an sle license alternative',
+'exif-attributionurl' => "An dovrand n'àutra vira cost travaj, për piasì ch'a-j buta l'anliura a",
'exif-preferredattributionname' => "Quand as deuvra torna cost travaj, për piasì dé l'arconossiment a",
'exif-pngfilecomment' => "Coment ëd l'archivi PNG",
'exif-disclaimer' => 'Avis',
'exif-event' => 'Event fotografà',
'exif-organisationinimage' => 'Organisassion fotografà',
'exif-personinimage' => 'Përson-a fotografà',
-'exif-originalimageheight' => "Autëssa dla figura prima ch'a sia ritajà",
-'exif-originalimagewidth' => "Larghëssa dla figura prima ch'a sia ritajà",
+'exif-originalimageheight' => "Autëssa dla figura prima ch'a fussa ritajà",
+'exif-originalimagewidth' => "Larghëssa dla figura prima ch'a fussa ritajà",
# EXIF attributes
-'exif-compression-1' => 'Pa compress',
+'exif-compression-1' => 'Nen comprimù',
'exif-compression-2' => "CCITT Partìa 3 longheur dla codìfica d'esecussion dla codìfica Huffman modificà ëd dimension 1",
'exif-compression-3' => 'CCITT Partìa 3 codìfica dël fax',
'exif-compression-4' => 'CCITT Partìa 4 codìfica dël fax',
'exif-unknowndate' => 'Data nen conossùa',
'exif-orientation-1' => 'Normal',
-'exif-orientation-2' => 'Specolar',
+'exif-orientation-2' => 'A specc',
'exif-orientation-3' => 'Arvirà ëd 180°',
-'exif-orientation-4' => 'Arvirà dzorsuta',
-'exif-orientation-5' => 'Arvirà dzorsota e ëd 90° contramostra',
+'exif-orientation-4' => 'Arvirà dzor-sota',
+'exif-orientation-5' => 'Arvirà dzor-sota e ëd 90° contramostra',
'exif-orientation-6' => 'Arvirà ëd 90° contramostra',
-'exif-orientation-7' => 'Arvirà dzorsota e ëd 90° ant ël sens dla mostra',
+'exif-orientation-7' => 'Arvirà dzor-sota e ëd 90° ant ël sens dla mostra',
'exif-orientation-8' => 'Arvirà ëd 90° ant ël sens dla mostra',
-'exif-planarconfiguration-1' => 'për blòch (chunky)',
+'exif-planarconfiguration-1' => 'dàit a blòch',
'exif-planarconfiguration-2' => 'an planar',
'exif-xyresolution-i' => '$1 pont për pòles (dpi)',
'exif-exposureprogram-2' => 'Programa normal',
'exif-exposureprogram-3' => 'Priorità ëd temp',
'exif-exposureprogram-4' => 'Priorità ëd diaframa',
-'exif-exposureprogram-5' => "Programa creativ (coregiù për avej pì ëd profondità 'd camp)",
-'exif-exposureprogram-6' => "Programa d'assion (coregiù për avej ël temp pì curt che as peul)",
+'exif-exposureprogram-5' => "Programa creativ (coregiù për avèj pì ëd profondità 'd camp)",
+'exif-exposureprogram-6' => "Programa d'assion (coregiù për avèj ël temp pì curt che as peul)",
'exif-exposureprogram-7' => 'Programa ritrat (për fotografìe pijaite da davsin, con lë sfond fòra feu)',
'exif-exposureprogram-8' => 'Panorama (sogèt lontan e con lë sfond a feu)',
# Scary transclusion
'scarytranscludedisabled' => "[L'inclusion ëd pàgine antra wiki diferente a l'é nen abilità]",
'scarytranscludefailed' => "[Darmagi, ma lë stamp $1 a l'é pa podusse carié]",
+'scarytranscludefailed-httpstatus' => '[Letura dlë stamp falìa për $1: HTTP $2]',
'scarytranscludetoolong' => "[L'URL a l'é tròp longa]",
# Delete conflict
# Search suggestions
'searchsuggest-search' => 'Arserca',
+'searchsuggest-containing' => 'contenent ...',
# API errors
'api-error-badaccess-groups' => "Chiel a peul pa carié d'archivi su sta wiki.",
'qbbrowse' => 'لبو',
'qbedit' => 'لکھو',
'qbpageoptions' => 'اے صفہ',
-'qbpageinfo' => 'محول',
'qbmyoptions' => 'میرے صفے',
'qbspecialpages' => 'خاص صفے',
'faq' => 'FAQ',
'qbbrowse' => 'Pradirēis',
'qbedit' => 'Redigīs',
'qbpageoptions' => 'Šin pāusan',
-'qbpageinfo' => 'Kōnteksts',
'qbmyoptions' => 'Majāi pāusai',
'qbspecialpages' => 'Speciālai pāusai',
'faq' => 'Ukadeznai prasīsenei',
'qbbrowse' => 'Navegar',
'qbedit' => 'Editar',
'qbpageoptions' => 'Esta página',
-'qbpageinfo' => 'Contexto',
'qbmyoptions' => 'Minhas páginas',
'qbspecialpages' => 'Páginas especiais',
'faq' => 'FAQ',
'qbbrowse' => 'Navegar',
'qbedit' => 'Editar',
'qbpageoptions' => 'Esta página',
-'qbpageinfo' => 'Contexto',
'qbmyoptions' => 'Minhas páginas',
'qbspecialpages' => 'Páginas especiais',
'faq' => 'FAQ',
ou [{{fullurl:{{FULLPAGENAME}}|action=edit}} criar esta página]</span>.',
'noarticletext-nopermission' => 'No momento, não há conteúdo nesta página
Você pode [[Special:Search/{{PAGENAME}}|pesquisar pelo título desta página]] em outras páginas,
-ou <span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} buscar por registros relacionados] </span>.',
+ou <span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} buscar por registros relacionados] </span>. Note que, no entanto, você não tem permissão para criar esta página.',
'missing-revision' => 'A revisão #$1 da página denominada "{{PAGENAME}}" não existe.
Isto é geralmente causado por seguir um link de histórico desatualizado para uma página que foi eliminada.
'explainconflict' => 'Appears at the top of a page when there is an edit conflict.',
'storedversion' => 'This is used in an edit conflict as the label for the top revision that has been stored, as opposed to your version that has not been stored which is shown at the bottom of the page.',
'yourdiff' => '',
-'copyrightwarning' => 'Copyright warning displayed under the edit box in editor',
+'copyrightwarning' => 'Copyright warning displayed under the edit box in editor
+*$1 - ...
+*$2 - ...',
'longpageerror' => 'Warning displayed when trying to save a text larger than the maximum size allowed',
'protectedpagewarning' => '{{Related|Semiprotectedpagewarning}}',
'semiprotectedpagewarning' => '{{Related|Semiprotectedpagewarning}}',
'moveddeleted-notice' => 'Shown on top of a deleted page in normal view modus ([http://translatewiki.net/wiki/Test example]).',
'edit-conflict' => "An 'Edit conflict' happens when more than one edit is being made to a page at the same time. This would usually be caused by separate individuals working on the same page. However, if the system is slow, several edits from one individual could back up and attempt to apply simultaneously - causing the conflict.",
'defaultmessagetext' => 'Caption above the default message text shown on the left-hand side of a diff displayed after clicking “Show changes” when creating a new page in the MediaWiki: namespace',
+'content-failed-to-parse' => "Error message indicating that the page's content can not be saved because it is syntactically invalid. This may occurr for content types using serialization or a strict markup syntax.
+*$1 – content model ({{msg-mw|Content-model-wikitext}}, {{msg-mw|Content-model-javascript}}, {{msg-mw|Content-model-css}} or {{msg-mw|Content-model-text}})
+*$2 – content format as MIME type (e.g. <tt>text/css</tt>)
+*$3 – specific error message",
+'invalid-content-data' => "Error message indicating that the page's content can not be saved because it is invalid. This may occurr for content types with internal consistency constraints.",
+'content-not-allowed-here' => 'Error message indicating that the desired content model is not supported in given localtion.
+* $1 is the human readable name of the content model: {{msg-mw|Content-model-wikitext}}, {{msg-mw|Content-model-javascript}}, {{msg-mw|Content-model-css}} or {{msg-mw|Content-model-text}}
+* $2 is the title of the page in question.',
+
+# Content models
+'content-model-wikitext' => 'Name for the wikitext content model, used when decribing what type of content a page contains.
+
+This message is substituted in:
+*{{msg-mw|Bad-target-model}}
+*{{msg-mw|Content-not-allowed-here}}',
+'content-model-text' => 'Name for the plain text content model, used when decribing what type of content a page contains.
+
+This message is substituted in:
+*{{msg-mw|Bad-target-model}}
+*{{msg-mw|Content-not-allowed-here}}',
+'content-model-javascript' => 'Name for the JavaScript content model, used when decribing what type of content a page contains.
+
+This message is substituted in:
+*{{msg-mw|Bad-target-model}}
+*{{msg-mw|Content-not-allowed-here}}',
+'content-model-css' => 'Name for the CSS content model, used when decribing what type of content a page contains.
+
+This message is substituted in:
+*{{msg-mw|Bad-target-model}}
+*{{msg-mw|Content-not-allowed-here}}',
# Parser/template warnings
'expensive-parserfunction-warning' => 'On some (expensive) [[MetaWikipedia:Help:ParserFunctions|parser functions]] (e.g. <code><nowiki>{{#ifexist:}}</nowiki></code>) there is a limit of how many times it may be used. This is an error message shown when the limit is exceeded.
'recentchanges-legend' => 'Legend of the fieldset of [[Special:RecentChanges]]',
'recentchanges-summary' => 'Summary of [[Special:RecentChanges]].',
'recentchanges-label-newpage' => 'Tooltip for {{msg-mw|newpageletter}}',
-'recentchanges-label-minor' => 'Tooltip for {{msg-mw|newpageletter}}',
+'recentchanges-label-minor' => 'Tooltip for {{msg-mw|minoreditletter}}',
'recentchanges-label-bot' => 'Tooltip for {{msg-mw|boteditletter}}',
'recentchanges-label-unpatrolled' => 'Tooltip for {{msg-mw|unpatrolledletter}}',
'rcnote' => 'Used on [[Special:RecentChanges]].
{{Identical|Reset}}',
'undeleteinvert' => '{{Identical|Invert selection}}',
'undeletecomment' => '{{Identical|Reason}}',
+'cannotundelete' => 'Message shown when undeletion failed for some reason.
+* <code>$1</code> is the combined wikitext of messages for all errors that caused the failure.',
'undelete-search-title' => 'Page title when showing the search form in Special:Undelete',
'undelete-search-submit' => '{{Identical|Search}}',
'undelete-error' => 'Page title when a page could not be undeleted',
'immobile-target-namespace-iw' => "This message appears when attempting to move a page, if a person has typed an interwiki link as a namespace prefix in the input box labelled 'To new title'. The special page 'Movepage' cannot be used to move a page to another wiki.
'Destination' can be used instead of 'target' in this message.",
+'bad-target-model' => 'This message is shown when attempting to move a page, but the move would change the page\'s content model.
+This may be the case when [[mw:Manual:$wgContentHandlerUseDB|$wgContentHandlerUseDB]] is set to false, because then a page\'s content model is derived from the page\'s title.
+* $1: The localized name of the original page\'s content model:
+**{{msg-mw|Content-model-wikitext}}, {{msg-mw|Content-model-javascript}}, {{msg-mw|Content-model-css}} or {{msg-mw|Content-model-text}}
+* $2: The localized name of the content model used by the destination title:
+**{{msg-mw|Content-model-wikitext}}, {{msg-mw|Content-model-javascript}}, {{msg-mw|Content-model-css}} or {{msg-mw|Content-model-text}}',
'fix-double-redirects' => 'This is a checkbox in [[Special:MovePage]] which allows to move all redirects from the old title to the new title.',
'protectedpagemovewarning' => 'Related message: [[MediaWiki:protectedpagewarning/{{#titleparts:{{PAGENAME}}|1|2}}]]
{{Related|Semiprotectedpagewarning}}',
# Info page
'pageinfo-title' => 'Page title for action=info. Parameters:
* $1 is the page name',
+'pageinfo-not-current' => 'Error message displayed when information for an old revision is requested. Example: [{{fullurl:Project:News|oldid=4266597&action=info}}]',
'pageinfo-header-basic' => 'Table section header in action=info.',
'pageinfo-header-edits' => 'Table section header in action=info.',
'pageinfo-header-restrictions' => 'Table section header in action=info.',
'qbbrowse' => 'Maskapuy',
'qbedit' => "Llamk'apuy",
'qbpageoptions' => "P'anqap akllanankuna",
-'qbpageinfo' => "P'anqamanta willay",
'qbmyoptions' => 'Akllanaykuna',
'qbspecialpages' => "Sapaq p'anqakuna",
'faq' => 'Pasaq tapuykuna',
'qbbrowse' => 'Răsfoiește',
'qbedit' => 'Modificare',
'qbpageoptions' => 'Opțiuni ale paginii',
-'qbpageinfo' => 'Informații ale paginii',
'qbmyoptions' => 'Paginile mele',
'qbspecialpages' => 'Pagini speciale',
'faq' => 'Întrebări frecvente',
'edit-already-exists' => 'Pagina nouă nu a putut fi creată.
Ea există deja.',
'defaultmessagetext' => 'Textul implicit',
+'content-failed-to-parse' => 'Nu s-a putut analiza conținutul de tip $2 pentru modelul $1: $3',
+'invalid-content-data' => 'Date de conținut invalide',
+'content-not-allowed-here' => 'Conținutul de tip „$1” nu este permis pe pagina [[$2]]',
+
+# Content models
+'content-model-wikitext' => 'wikitext',
+'content-model-text' => 'text simplu',
+'content-model-javascript' => 'JavaScript',
+'content-model-css' => 'CSS',
# Parser/template warnings
'expensive-parserfunction-warning' => 'Atenție: Această pagină conține prea multe apelări costisitoare ale funcțiilor parser.
'uploadnewversion-linktext' => 'Încarcă o versiune nouă a acestui fișier',
'shared-repo-from' => 'de la $1',
'shared-repo' => 'un depozit partajat',
-'upload-disallowed-here' => 'Din păcate, nu puteți suprascrie această imagine.',
+'upload-disallowed-here' => 'Nu puteți suprascrie acest fișier.',
# File reversion
'filerevert' => 'Revenire $1',
'undeletedrevisions' => '{{PLURAL:$1|o versiune restaurată|$1 versiuni restaurate|$1 de versiuni restaurate}}',
'undeletedrevisions-files' => '{{PLURAL:$1|O versiune|$1 versiuni|$1 de versiuni}} și {{PLURAL:$2|un fișier|$2 fișiere|$2 de fișiere}} recuperate',
'undeletedfiles' => '{{PLURAL:$1|O versiune recuperată|$1 versiuni recuperate|$1 de versiuni recuperate}}',
-'cannotundelete' => 'Recuperarea a eșuat; este posibil ca altcineva să fi recuperat pagina deja.',
+'cannotundelete' => 'Recuperarea a eșuat:
+$1',
'undeletedpage' => "'''$1 a fost recuperat'''
Consultați [[Special:Log/delete|jurnalul ștergerilor]] pentru a vedea toate ștergerile și recuperările recente.",
'immobile-target-namespace-iw' => 'Legătura interwiki nu este o țintă validă pentru redenumire.',
'immobile-source-page' => 'Această pagină nu poate fi redenumită.',
'immobile-target-page' => 'Imposibil de redenumit pagina la acel titlu.',
+'bad-target-model' => 'Destinația dorită folosește un alt model de conținut. Nu se poate converti $1 în $2.',
'imagenocrossnamespace' => 'Fișierul nu poate fi mutat la un spațiu de nume care nu este destinat fișierelor',
'nonfile-cannot-move-to-file' => 'Entitatea (care nu este un fișier) nu poate fi mutată în spațiul de nume destinat fișierelor',
'imagetypemismatch' => 'Extensia nouă a fișierului nu se potrivește cu tipul acestuia',
# Info page
'pageinfo-title' => 'Informații pentru „$1”',
+'pageinfo-not-current' => 'Informațiile se pot afișa doar pentru versiunea curentă.',
'pageinfo-header-basic' => 'Informații de bază',
'pageinfo-header-edits' => 'Istoric modificări',
'pageinfo-header-restrictions' => 'Protecție pagină',
'qbbrowse' => 'Sfoglie',
'qbedit' => 'Cange',
'qbpageoptions' => 'Pàgene currende',
-'qbpageinfo' => 'Condeste',
'qbmyoptions' => 'Pàggene mije',
'qbspecialpages' => 'Pàggene speciale',
'faq' => 'FAQ',
* @author Sagan
* @author Shirayuki
* @author Sk
+ * @author Spider
* @author TarzanASG
* @author Temuri rajavi
* @author Vago
'qbbrowse' => 'Просмотреть',
'qbedit' => 'Править',
'qbpageoptions' => 'Настройки страницы',
-'qbpageinfo' => 'Сведения о странице',
'qbmyoptions' => 'Ваши настройки',
'qbspecialpages' => 'Специальные страницы',
'faq' => 'ЧаВО',
'vector-action-protect' => 'Защитить',
'vector-action-undelete' => 'Восстановить',
'vector-action-unprotect' => 'Изменить защиту',
-'vector-simplesearch-preference' => 'Ð\92клÑ\8eÑ\87иÑ\82Ñ\8c Ñ\80аÑ\81Ñ\88иÑ\80еннÑ\8bе поиÑ\81ковÑ\8bе подÑ\81казки (только для оформления «Векторное»)',
+'vector-simplesearch-preference' => 'Ð\92клÑ\8eÑ\87иÑ\82Ñ\8c Ñ\83пÑ\80оÑ\89Ñ\91ннÑ\83Ñ\8e Ñ\81Ñ\82Ñ\80окÑ\83 поиÑ\81ка (только для оформления «Векторное»)',
'vector-view-create' => 'Создание',
'vector-view-edit' => 'Правка',
'vector-view-history' => 'История',
'edit-already-exists' => 'Невозможно создать новую страницу.
Она уже существует.',
'defaultmessagetext' => 'Текст по умолчанию',
+'content-failed-to-parse' => 'Содержимое $2 не соответствует типу $1: $3.',
+'invalid-content-data' => 'Недопустимые данные',
+'content-not-allowed-here' => 'Содержимое "$1" недопустимо на странице [[$2]]',
+
+# Content models
+'content-model-wikitext' => 'викитекст',
+'content-model-text' => 'обычный текст',
+'content-model-javascript' => 'JavaScript',
+'content-model-css' => 'CSS',
# Parser/template warnings
'expensive-parserfunction-warning' => 'Внимание. Эта страница содержит слишком много вызовов ресурсоёмких функций.
'shared-repo-from' => 'из $1',
'shared-repo' => 'общего хранилища',
'shared-repo-name-wikimediacommons' => 'Викисклада',
-'upload-disallowed-here' => 'Ð\9a Ñ\81ожалениÑ\8e, вÑ\8b не можеÑ\82е пеÑ\80езапиÑ\81аÑ\82Ñ\8c Ñ\8dÑ\82о изобÑ\80ажение.',
+'upload-disallowed-here' => 'Ð\92Ñ\8b не можеÑ\82е пеÑ\80езапиÑ\81аÑ\82Ñ\8c Ñ\8dÑ\82оÑ\82 Ñ\84айл.',
# File reversion
'filerevert' => 'Возврат к старой версии $1',
'undeletedrevisions' => '$1 {{PLURAL:$1|изменение|изменения|изменений}} восстановлено',
'undeletedrevisions-files' => '$1 {{PLURAL:$1|версия|версии|версий}} и $2 {{PLURAL:$2|файл|файла|файлов}} восстановлено',
'undeletedfiles' => '$1 {{PLURAL:$1|файл восстановлен|файла восстановлено|файлов восстановлено}}',
-'cannotundelete' => 'Ошибка восстановления. Возможно, кто-то другой уже восстановил страницу.',
+'cannotundelete' => 'Ошибка восстановления:
+$1',
'undeletedpage' => "'''Страница «$1» была восстановлена.'''
Для просмотра списка последних удалений и восстановлений см. [[Special:Log/delete|журнал удалений]].",
'immobile-target-namespace-iw' => 'Ссылка интервики не может быть использована для переименования.',
'immobile-source-page' => 'Эту страницу нельзя переименовать.',
'immobile-target-page' => 'Нельзя присвоить странице это имя.',
+'bad-target-model' => 'Невозможно преобразовать $1 в $2: несовместимые модели данных.',
'imagenocrossnamespace' => 'Невозможно дать файлу имя из другого пространства имён',
'nonfile-cannot-move-to-file' => 'Невозможно переименовывать страницы в файлы',
'imagetypemismatch' => 'Новое расширение файла не соответствует его типу',
# Info page
'pageinfo-title' => 'Сведения по «$1»',
+'pageinfo-not-current' => 'Данные предоставляются только для текущей правки.',
'pageinfo-header-basic' => 'Основные сведения',
'pageinfo-header-edits' => 'История изменений',
'pageinfo-header-restrictions' => 'Защита страницы',
'pageinfo-magic-words' => '{{PLURAL:$1|Магическое слово|Магические слова}} ($1)',
'pageinfo-hidden-categories' => '{{PLURAL:$1|Скрытая категория|Скрытых категорий}} ($1)',
'pageinfo-templates' => '{{PLURAL:$1|Шаблон|Шаблонов}} ($1)',
+'pageinfo-toolboxlink' => 'Сведения о странице',
# Skin names
'skinname-standard' => 'Классическое',
# Scary transclusion
'scarytranscludedisabled' => '[Интервики-включение отключено]',
'scarytranscludefailed' => '[Ошибка обращения к шаблону $1]',
+'scarytranscludefailed-httpstatus' => '[Не удалось загрузить шаблон для $1: HTTP $2]',
'scarytranscludetoolong' => '[Слишком длинный URL]',
# Delete conflict
'qbbrowse' => 'Переглядати',
'qbedit' => 'Едітовати',
'qbpageoptions' => 'Тота сторінка',
-'qbpageinfo' => 'Контекст',
'qbmyoptions' => 'Мої сторінкы',
'qbspecialpages' => 'Шпеціалны сторінкы',
'faq' => 'Часты звідованя',
'qbbrowse' => 'ब्राउस् इत्येतत् करोतु।',
'qbedit' => 'सम्पाद्यताम्',
'qbpageoptions' => 'इदं पृष्ठम्',
-'qbpageinfo' => 'प्रसंगः',
'qbmyoptions' => 'मम पृष्ठानि',
'qbspecialpages' => 'विशेषपृष्ठानि',
'faq' => 'बहुधा पृच्छ्यमानाः प्रश्नाः',
'viewpagelogs' => 'अस्य पृष्ठस्य लॉंग् इत्येतद् दर्शयतु',
'nohistory' => 'अस्य पृष्ठस्य कृते पृष्ठेतिहासः न वर्तते।',
'currentrev' => 'सद्यःकालीना आवृत्तिः',
-'currentrev-asof' => 'वरà¥\8dतमतà¥\80 आवृत्तिः $1 इति समये',
+'currentrev-asof' => 'वरà¥\8dतमाना आवृत्तिः $1 इति समये',
'revisionasof' => '$1 इत्यस्य आवृत्तिः',
'revision-info' => '$1इति समयस्य आवृत्तिः $2 इत्यनेन',
'previousrevision' => '← पुरातनानि संस्करणानि',
'filehist-current' => 'सद्योजातम्',
'filehist-datetime' => 'दिनाङ्कः/समयः',
'filehist-thumb' => 'अंगुष्ठनखाकारम्',
-'filehist-thumbtext' => '$1 समयà¥\87 विदà¥\8dयमतà¥\8dयाः आवृत्तेः अंगुष्ठनखाकारम्',
+'filehist-thumbtext' => '$1 समयà¥\87 विदà¥\8dयमानायाः आवृत्तेः अंगुष्ठनखाकारम्',
'filehist-nothumb' => 'अङ्गुष्टनखाकारकं नाश्ति ।',
'filehist-user' => 'योजकः',
'filehist-dimensions' => 'आयामाः',
'qbbrowse' => 'Көр',
'qbedit' => 'Уларыт',
'qbpageoptions' => 'Бу сирэй',
-'qbpageinfo' => 'Ис хоһооно',
'qbmyoptions' => 'Мин сирэйдэрим',
'qbspecialpages' => 'Аналлаах сирэйдэр',
'faq' => 'FAQ',
'qbbrowse' => 'Sendra',
'qbedit' => 'Tońge',
'qbpageoptions' => 'Noa sakam',
-'qbpageinfo' => 'Sakam reaḱ thuti',
'qbmyoptions' => 'In̕anḱ sakamko',
'qbspecialpages' => 'Asokay teaḱ sakamko',
'faq' => 'Baḍae kupuliko',
'last' => 'Laha renaḱ',
'page_first' => 'Pahilaḱ',
'page_last' => 'Mucạt́aḱ',
-'histlegend' => "Farak bachao: oka nãwã aroeko tulạoem menet́kan, onako cinhạ em kate boloḱ se latar baṭon linmẽ.<br/>
+'histlegend' => "Farak bachao: oka nãwã aroeko tulạoem menet́kan, onako cinhạ em kate boloḱ se latar baṭon linmẽ.<br />
Unuduḱ: '''({{int:cur}})''' = nahaḱ nãwã aroeko saõte tulạo, '''({{int:last}})''' = laha reaḱ nãwã aroe sãote tulạo, '''{{int:minoreditletter}}''' = huḍiń sompadon.",
'history-fieldset-title' => 'Sendray jaṛ',
'history-show-deleted' => 'khạli get giḍiyaḱ koge',
'qbbrowse' => 'Sfogghia',
'qbedit' => 'Cancia',
'qbpageoptions' => 'Opzioni pàggina',
-'qbpageinfo' => 'Nfurmazzioni supra la pàggina',
'qbmyoptions' => 'Li mè pàggini',
'qbspecialpages' => 'Pàggini spiciali',
'faq' => 'Dumanni cumuni',
'qbbrowse' => 'Brouse',
'qbedit' => 'Edit',
'qbpageoptions' => 'This page',
-'qbpageinfo' => 'Context',
'qbmyoptions' => 'Ma pages',
'qbspecialpages' => 'Byordinar pages',
'faq' => 'ASQ',
'qbbrowse' => 'Iffuglia',
'qbedit' => 'Mudifigga',
'qbpageoptions' => 'Prifirenzi pàgina',
-'qbpageinfo' => "Infuimmazioni i' la pàgina",
'qbmyoptions' => "Li me' pàgini",
'qbspecialpages' => 'Pàgini ippiziari',
'faq' => 'FAQ (infuimmazioni e aggiuddu)',
'qbbrowse' => 'Bláđe',
'qbedit' => 'Rievdat',
'qbpageoptions' => 'Siidoásahusat',
-'qbpageinfo' => 'Siiddu dieđut',
'qbmyoptions' => 'Ásahusat',
'qbspecialpages' => 'Doaibmasiiddut',
'qbbrowse' => 'Prelistajte',
'qbedit' => 'Uredi',
'qbpageoptions' => 'Opcije stranice',
-'qbpageinfo' => 'Informacije o stranici',
'qbmyoptions' => 'Moje opcije',
'qbspecialpages' => 'Posebne stranice',
'faq' => 'ČPP',
'qbbrowse' => 'පිරික්සන්න',
'qbedit' => 'සංස්කරණය',
'qbpageoptions' => 'මෙම පිටුව',
-'qbpageinfo' => 'සන්දර්භය',
'qbmyoptions' => 'මගේ පිටු',
'qbspecialpages' => 'විශේෂ පිටු',
'faq' => 'නිවිප්ර',
* '''ෆයර්ෆොක්ස්/ සෆාරි:''' ''Reload'' ඔබන අතරතුර ''Shift'' ඔබන්න, නැතහොත් ''Ctrl-F5'' හෝ''Ctrl-R'' (මැක්හීදී ''Command-R'' ) ඔබන්න
* '''ගූගල් ක්රෝම්:''' ''Ctrl-Shift-R'' ඔබන්න(මැක්හී ''Command-Shift-R'' )
* '''ඉන්ටර්නෙට් එක්ස්ප්ලෝර:''' ''Refresh'' ඔබන අතරතුර ''Ctrl'' ඔබන්න, නැතහොත් ''Ctrl-F5'' ඔබන්න
-* '''කොන්කරර්:''' ''Reload'' ඔබන්න හෝ ''F5'' ඔබන්න
* '''ඔපෙරා:''' ''Tools → Preferences'' හි කෑෂය හිස් කරන්න",
'usercssyoucanpreview' => "'''හෝඩුවාව:'''සුරැකුමට පෙර, ඔබගේ නව CSS පරික්ෂා කරනු වස්, \"{{int:පෙර-දසුන පෙන්වන්න}}\" බොත්තම භාවිතා කරන්න.",
'userjsyoucanpreview' => "'''හෝඩුවාව:'''සුරැකුමට පෙර, ඔබගේ නව ජාවා ස්ක්රිප්ට් පරික්ෂා කරනු වස්, \"{{int:පෙර-දසුන පෙන්වන්න}}\" බොත්තම භාවිතා කරන්න.",
'revdelete-only-restricted' => '$2 දිනැති අයිතමය සැඟවීමේ දෝෂය , $1:අනෙකුත් සැඟවීම් විකල්පයන් අතුරින් එකක් තෝරාගන්නේ නැතිව, පරිපාලකයන්ගේ දර්ශනයෙන් අයිතමයන් සැඟවීම ඔබහට සිදුකල නොහැක.',
'revdelete-reason-dropdown' => '*මකා දැමීමේ පොදු හේතු
**කතු හිමිකම් උල්ලංඝනය
-**නුසුදුසු පුද්ගලික කොරතුරු
+**නුසුදුසු පරිකථන හෝ පුද්ගලික කොරතුරු
+**නුසුදුසු පරිශීලක නම
**අපහාසාත්මක විය හැකි තොරතුරු',
'revdelete-otherreason' => 'වෙනත්/අමතර හේතු:',
'revdelete-reasonotherlist' => 'වෙනත් හේතු',
# Suppression log
'suppressionlog' => 'යටපත්කිරීම් පිළිබඳ සටහන',
'suppressionlogtext' => 'පරිපාලකයන්ගෙන් සැඟවුනු අන්තර්ගතය සම්බන්ධ මකාදැමීම් හා වාරණ ලැයිස්තුවක් මෙහි පහත දැක්වේ.
-දà·\90නට à¶\9aà·\8aâ\80\8dරà·\92යà·\8fà¶à·\8aමà¶\9a à·\80න à¶à·\84නමà·\8a à·\84à·\8f à·\80à·\8fරණයනà·\8a ලà·\90යà·\92à·\83à·\8aà¶à·\94à·\80à¶\9aà·\8a à·\83ඳà·\84à·\8f [[Special:BlockList|à¶\85නà·\8aà¶à¶»à·\8aජà·\8fල à·\80à·\8fරණ ලà·\90යà·\92à·\83à·\8aà¶à·\94à·\80]] බලනà·\8aන.',
+දැනට ක්රියාත්මක වන තහනම් හා වාරණයන් ලැයිස්තුවක් සඳහා [[Special:BlockList|වාරණ ලැයිස්තුව]] බලන්න.',
# History merging
'mergehistory' => 'පිටු ඉතිහාසයන් ඒකාබද්ධ කරන්න',
'backend-fail-create' => '$1 ගොනුව ලිවිය නොහැකි විය.',
'backend-fail-maxsize' => '{{PLURAL:$2|බයිට එකකට|බයිට $2 කට}} වඩා විහාල බැවින් $1 ගොනුව ලිවිය නොහැකි විය.',
'backend-fail-readonly' => 'ගබඩා බැක්එන්ඩය "$1" දැනට කියවීම-පමණක් සඳහා වෙයි. දක්වා ඇති හේතුව නම්: "\'\'$2\'\'"',
-'backend-fail-usable' => 'අවසර ප්රමාණවත් නොවීම නිසාවෙන් හෝ නාමාවලී/බහාලුම් නොමැති වීම නිසාවෙන් $1 ගොනුව ලිවිය නොහැකි විය.',
+'backend-fail-usable' => 'අවසර ප්රමාණවත් නොවීම නිසාවෙන් හෝ නාමාවලී/බහාලුම් නොමැති වීම නිසාවෙන් "$1" ගොනුව කියවිය හෝ ලිවිය හෝ නොහැකි විය.',
# Lock manager
'lockmanager-notlocked' => '"$1" හී අගුළු ඇරිය නොහැක; එය අගුළු දමාද නොමැත.',
'qbbrowse' => 'Prehliadať',
'qbedit' => 'Upraviť',
'qbpageoptions' => 'Táto stránka',
-'qbpageinfo' => 'Kontext',
'qbmyoptions' => 'Moje stránky',
'qbspecialpages' => 'Špeciálne stránky',
'faq' => 'Často kladené otázky',
'vector-action-protect' => 'Zamknúť',
'vector-action-undelete' => 'Obnoviť',
'vector-action-unprotect' => 'Zmeniť stav ochrany',
-'vector-simplesearch-preference' => 'Povoliť rozšírené návrhy hľadania (iba pre tému Vector)',
+'vector-simplesearch-preference' => 'Povoliť zjednodušené pole hľadania (iba pre tému Vector)',
'vector-view-create' => 'Vytvoriť',
'vector-view-edit' => 'Upraviť',
'vector-view-history' => 'Zobraziť históriu',
'protectedpagetext' => 'Táto stránka bola zamknutá aby sa zamedzilo úpravám.',
'viewsourcetext' => 'Môžete si zobraziť a kopírovať zdroj tejto stránky:',
'viewyourtext' => "Môžete si prehliadnuť a skopírovať zdrojový kód '''vašich zmien''' tejto stránky:",
-'protectedinterface' => 'Táto stránka poskytuje text používateľského rozhrania a je zamknutá, aby sa predišlo jej zneužitiu.',
-'editinginterface' => "'''Upozornenie:''' Upravujete stránku, ktorá poskytuje text používateľského rozhrania. Zmeny tejto stránky ovplyvnia vzhľad používateľského rozhrania ostatných používateľov. Zmeny prosím vykonávajte prostredníctvom [//translatewiki.net/wiki/Main_Page?setlang=sk translatewiki.net], projektu pre lokalizáciu MediaWiki.",
+'protectedinterface' => 'Táto stránka poskytuje text používateľského rozhrania tejto wiki a je zamknutá, aby sa predišlo jej zneužitiu.
+Ak chcete pridať alebo zmeniť preklady pre všetky wiki, prosím, použite [//translatewiki.net/wiki/Main_Page?setlang=sk translatewiki.net], projekt lokalizácie MediaWiki.',
+'editinginterface' => "'''Upozornenie:''' Upravujete stránku, ktorá poskytuje text používateľského rozhrania.
+Zmeny tejto stránky ovplyvnia vzhľad používateľského rozhrania ostatným používateľom.
+Ak chcete pridať alebo zmeniť preklady pre všetky wiki, prosím, použite [//translatewiki.net/wiki/Main_Page?setlang=sk translatewiki.net], projekt lokalizácie MediaWiki.",
'sqlhidden' => '(SQL príkaz je skrytý)',
'cascadeprotected' => 'Táto stránka bola zamknutá proti úpravám, pretože je použitá na {{PLURAL:$1|nasledovnej stránke, ktorá je zamknutá|nasledovných stránkach, ktoré sú zamknuté}} voľbou „kaskádového zamknutia“:
$2',
alebo [{{fullurl:{{FULLPAGENAME}}|action=edit}} upravovať túto stránku]</span>.',
'noarticletext-nopermission' => 'Táto stránka momentálne neobsahuje žiadny text.
Môžete [[Special:Search/{{PAGENAME}}|hľadať názov tejto stránky]] v texte iných stránok
-alebo <span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} si pozrieť súvisiace záznamy]</span>.',
+alebo <span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} hľadať v súvisiacich záznamoch]</span>, ale nemáte oprávnenie túto stránku vytvoriť.',
'missing-revision' => 'Revízia #$1 stránky s názvom „{{PAGENAME}}“ neexistuje.
Pravdepodobne ste nasledovali zastaraný odkaz na historickú verziu stránky, ktorá bola medzičasom odstránená.
'edit-already-exists' => 'Nebolo možné vytvoriť novú stránku.
Už existuje.',
'defaultmessagetext' => 'Predvolený text správy',
+'content-failed-to-parse' => 'Nepodarilo sa spracovať obsah $2 pre model $1: $3',
+'invalid-content-data' => 'Neplatné dáta obsahu',
+'content-not-allowed-here' => 'Obsah „$1“ nie je povolený na stránke [[$2]]',
+
+# Content models
+'content-model-wikitext' => 'wikitext',
+'content-model-text' => 'čistý text',
+'content-model-javascript' => 'JavaScript',
+'content-model-css' => 'CSS',
# Parser/template warnings
'expensive-parserfunction-warning' => 'Upozornenie: Táto stránka obsahuje príliš mnoho volaní funkcií syntaktického analyzátora, ktoré nadmerne zaťažujú server.
'expansion-depth-exceeded-warning' => 'Stránka prekročila povolenú hĺbku expanzie',
'parser-unstrip-loop-warning' => 'Zistené zacyklenie volania rozširovacej značky',
'parser-unstrip-recursion-limit' => 'Prektočený limit rekurzie volania rozširovacej značky ($1)',
+'converter-manual-rule-error' => 'Bola zistená chyba v pravidle manuálnej konverzie jazyka',
# "Undo" feature
'undo-success' => 'Úpravu je možné vrátiť. Prosím skontrolujte tento rozdiel, čím overíte, že táto úprava je tá, ktorú chcete, a následne uložte zmeny, čím ukončíte vrátenie.',
'timezoneregion-indian' => 'Indický oceán',
'timezoneregion-pacific' => 'Tichý oceán',
'allowemail' => 'Povoliť prijímanie e-mailov od iných používateľov',
-'prefs-searchoptions' => 'Voľby hľadania',
+'prefs-searchoptions' => 'Hľadanie',
'prefs-namespaces' => 'Menné priestory',
'defaultns' => 'Inak vyhľadávať v týchto menných priestoroch:',
'default' => 'predvolený',
'undeletedrevisions' => '{{PLURAL:$1|jedna verzia bola obnovená|$1 verzie boli obnovené|$1 verzií bolo obnovených}}',
'undeletedrevisions-files' => '{{PLURAL:$1|Jedna revízia|$1 revízie|$1 revízií}} a {{PLURAL:$2|jeden súbor bol obnovený|$2 súbory boli obnovené|$2 súborov bolo obnovených}}',
'undeletedfiles' => '{{PLURAL:$1|Jeden súbor bol obnovený|$1 súbory boli obnovené|$1 súborov bolo obnovených}}',
-'cannotundelete' => 'Obnovenie sa nepodarilo; pravdepodobne niekto iný obnovil stránku skôr ako vy.',
+'cannotundelete' => 'Obnovenie sa nepodarilo:
+$1',
'undeletedpage' => "'''$1 bol obnovený'''
Zoznam posledných mazaní a obnovení nájdete v [[Special:Log/delete|Zázname mazaní]].",
'immobile-target-namespace-iw' => 'Interwiki odkaz nie je platným cieľom na presun stránky.',
'immobile-source-page' => 'Túto stránku nemožno presunúť.',
'immobile-target-page' => 'Nie je možné presunúť na cieľovú stránku z daným názvom.',
+'bad-target-model' => 'Požadovaný cieľ používa iný model obsahu. Nie je možné konvertovať z $1 na $2.',
'imagenocrossnamespace' => 'Obrázok nemožno presunúť mimo menného priestoru obrázkov',
'nonfile-cannot-move-to-file' => 'Nie je možné presunúť objekt, ktorý nie je súbor do menného priestoru Súbor',
'imagetypemismatch' => 'Nová prípona súboru nezodpovedá jeho typu',
'import-interwiki-templates' => 'Vložiť všetky šablóny',
'import-interwiki-submit' => 'Importovať',
'import-interwiki-namespace' => 'Cieľový menný priestor:',
+'import-interwiki-rootpage' => 'Koreňová stránka cieľa (nepovinné):',
'import-upload-filename' => 'Názov súboru:',
'import-comment' => 'komentár:',
'importtext' => 'Prosím, exportujte súbor zo zdrojovej wiki použitím [[Special:Export|nástroja na export]].
'import-error-interwiki' => 'Stránka „$1“ nie je importovaná, pretože jej názov je vyhradený pre externé odkazy (interwiki).',
'import-error-special' => 'Stránka „$1“ nie je importovaná, pretože patrí do špeciálneho menného priestoru, ktorý nepovoľuje stránky.',
'import-error-invalid' => 'Stránka „$1“ nie je importovaná, pretože jej názov je neplatný.',
+'import-options-wrong' => '{{PLURAL:$2|Nesprávna voľba|Nesprávne voľby}}: <nowiki>$1</nowiki>',
+'import-rootpage-invalid' => 'Uvedená koreňová stránka nie je platný názov stránky.',
+'import-rootpage-nosubpage' => 'Menný priestor „$1“ koreňovej stránky nepodporuje podstránky.',
# Import log
'importlogpage' => 'Záznam importov',
'pageinfo-title' => 'Informácie o „$1“',
'pageinfo-header-basic' => 'Základné údaje',
'pageinfo-header-edits' => 'História úprav',
+'pageinfo-header-restrictions' => 'Ochrana stránky',
+'pageinfo-header-properties' => 'Vlastnosti stránky',
+'pageinfo-display-title' => 'Zobraziť názov',
+'pageinfo-default-sort' => 'Predvolený kľúč zoraďovania:',
+'pageinfo-length' => 'Dĺžka stránky (v bajtoch)',
'pageinfo-article-id' => 'ID stránky',
+'pageinfo-robot-policy' => 'Stav vyhľadávača',
+'pageinfo-robot-index' => 'Indexovať stránku',
+'pageinfo-robot-noindex' => 'Neindexovať stránku',
'pageinfo-views' => 'Počet zobrazení',
-'pageinfo-watchers' => 'Počet sledovateľov',
+'pageinfo-watchers' => 'Počet používateľov sledujúcich stránku',
+'pageinfo-redirects-name' => 'Presmerovania na túto stránku',
+'pageinfo-subpages-name' => 'Podstránky tejto stránky',
+'pageinfo-subpages-value' => '$1 ($2 {{PLURAL:$2|presmerovanie|presmerovania|presmerovaní}}; $3 {{PLURAL:$3|nie je presmerovanie|nie sú presmerovania}})',
+'pageinfo-firstuser' => 'Tvorca stránky',
+'pageinfo-firsttime' => 'Dátum vytvorenia stránky',
+'pageinfo-lastuser' => 'Naposledy upravil',
'pageinfo-lasttime' => 'Dátum poslednej úpravy',
'pageinfo-edits' => 'Celkový počet úprav',
'pageinfo-authors' => 'Celkový počet autorov',
+'pageinfo-recent-edits' => 'Počet nedávnych úprav (za posledných $1)',
+'pageinfo-recent-authors' => 'Počet nedávnych jedinečných autorov',
+'pageinfo-magic-words' => 'Magické {{PLURAL:$1|slovo|slová}} ($1)',
+'pageinfo-hidden-categories' => '{{PLURAL:$1|Skrytá kategória|Skryté kategórie}} ($1)',
+'pageinfo-templates' => '{{PLURAL:$1|Vložená šablóna|Vložené šablóny}} ($1)',
+'pageinfo-toolboxlink' => 'Informácie o stránke',
# Skin names
'skinname-standard' => 'Klasický',
'file-info-size-pages' => '$1 × $2 pixlov, veľkosť súboru: $3, typ MIME: $4, $5 {{PLURAL:$5|stránka|stránky|stránok}}',
'file-nohires' => 'Nie je dostupné vyššie rozlíšenie.',
'svg-long-desc' => 'SVG súbor, $1 × $2 pixelov, veľkosť súboru: $3',
+'svg-long-desc-animated' => 'Animovaný súbor SVG, nominálne $1 × $2 pixlov, veľkosť súboru: $3',
'show-big-image' => 'Obrázok vo vyššom rozlíšení',
'show-big-image-preview' => 'Veľkosť tohto náhľadu: $1.',
'show-big-image-other' => 'Iné {{PLURAL:$2|rozlíšenie|rozlíšenia}}: $1 .',
'file-info-png-looped' => 'opakované',
'file-info-png-repeat' => 'prehrané $1-krát{{PLURAL:$1|||}}',
'file-info-png-frames' => '$1 {{PLURAL:$1|rámec|rámce|rámcov}}',
+'file-no-thumb-animation' => "'''Pozn.: Z dôvodu technických obmedzení nebudú náhľady tohto súboru animované.'''",
+'file-no-thumb-animation-gif' => "'''Pozn.: Z dôvodu technických obmedzení nebudú náhľady súborov GIF vo vysokom rozlíšení (ako je tento súbor) animované.'''",
# Special:NewFiles
'newimages' => 'Galéria nových obrázkov',
# Scary transclusion
'scarytranscludedisabled' => '[Transklúzia interwiki je vypnutá]',
'scarytranscludefailed' => '[Nepodarilo sa priniesť šablónu pre $1]',
+'scarytranscludefailed-httpstatus' => '[Stiahnutie šablóny zlyhalo pre $1: HTTP $2]',
'scarytranscludetoolong' => '[URL je príliš dlhé]',
# Delete conflict
'qbbrowse' => 'Prebrskaj',
'qbedit' => 'Uredi',
'qbpageoptions' => 'Možnosti strani',
-'qbpageinfo' => 'Podatki o strani',
'qbmyoptions' => 'Moje možnosti',
'qbspecialpages' => 'Posebne strani',
'faq' => 'Najpogostejša vprašanja',
'restorelink' => '$1 {{PLURAL:$1|izbrisano redakcijo|izbrisani redakciji|izbrisane redakcije|izbrisanih redakcij}}',
'feedlinks' => 'Podajanje:',
'feed-invalid' => 'Neveljavna vrsta naročniškega vira.',
-'feed-unavailable' => 'Živi zaznamki niso na voljo',
+'feed-unavailable' => 'Živi zaznamki niso na razpolago.',
'site-rss-feed' => 'RSS-vir strani »$1«',
'site-atom-feed' => 'Atom-vir strani »$1«',
'page-rss-feed' => 'RSS-vir strani »$1«',
'badtitle' => 'Nepravilen naslov',
'badtitletext' => 'Navedeni naslov strani je neveljaven, prazen, napačno povezan k drugim jezikom oziroma wikiprojektom.
Morda vsebuje enega ali več nepodprtih znakov.',
-'perfcached' => 'Navedeni podatki so shranjeni v predpomnilniku in morda niso popolnoma posodobljeni. V predpomnilniku {{PLURAL:$1|je|sta|so|je}} na voljo največ $1 {{PLURAL:$1|rezultat|rezultata|rezultate|rezultatov}}.',
-'perfcachedts' => 'Prikazani podatki so shranjeni v predpomnilniku in so bili nazadnje osveženi $1. V predpomnilniku {{PLURAL:$4|je|sta|so|je}} na voljo največ $4 {{PLURAL:$4|rezultat|rezultata|rezultate|rezultatov}}.',
+'perfcached' => 'Navedeni podatki so shranjeni v predpomnilniku in morda niso popolnoma posodobljeni. V predpomnilniku {{PLURAL:$1|je|sta|so|je}} na razpolago največ $1 {{PLURAL:$1|rezultat|rezultata|rezultate|rezultatov}}.',
+'perfcachedts' => 'Prikazani podatki so shranjeni v predpomnilniku in so bili zadnjič osveženi $1. V predpomnilniku {{PLURAL:$4|je|sta|so|je}} na razpolago največ $4 {{PLURAL:$4|rezultat|rezultata|rezultate|rezultatov}}.',
'querypage-no-updates' => 'Posodobitve za to stran so trenutno onemogočene. Tukajšnji podatki se v kratkem ne bodo osvežili.',
'wrong_wfQuery_params' => 'Nepravilni parametri za wfQuery()<br />
Funkcija: $1<br />
Premislite preden nadaljujete s pisanjem, morda bo stran zaradi istih razlogov ponovno odstranjena.
Spodaj je prikazan dnevnik brisanja in prestavljanja:",
-'moveddeleted-notice' => 'Ta stran je bila izbrisana.
-Dnevnik brisanja in prestavljanja strani je na voljo spodaj.',
+'moveddeleted-notice' => 'Stran je bila izbrisana.
+Spodaj sta za sklicevanje na razpolago dnevnik brisanja in dnevnik prestavljanja strani.',
'log-fulllog' => 'Ogled celotnih dnevniških zapiskov',
'edit-hook-aborted' => 'Urejanje je bilo brez obrazložitve prekinjeno zaradi neznane napake.',
'edit-gone-missing' => 'Strani ni mogoče posodobiti.
'edit-already-exists' => 'Ni bilo mogoče ustvariti nove strani, ker že obstaja.',
'defaultmessagetext' => 'Prednastavljeno besedilo',
+# Content models
+'content-model-wikitext' => 'wikibesedilo',
+'content-model-text' => 'golo besedilo',
+'content-model-javascript' => 'JavaScript',
+'content-model-css' => 'CSS',
+
# Parser/template warnings
'expensive-parserfunction-warning' => "'''Opozorilo:''' Ta stran vsebuje preveč klicev funkcije razčlenjevalnika kode.
PICT # mešano
#</pre> <!-- pustite to vrstico takšno, kot je -->',
'upload-success-subj' => 'Datoteka je bila uspešno naložena',
-'upload-success-msg' => 'Vaša datoteka iz [$2] je bila uspešno naložena. Na voljo je tukaj: [[:{{ns:file}}:$1]]',
+'upload-success-msg' => 'Datoteka s strani [$2] se je uspešno naložila. Na razpolago je tukaj: [[:{{ns:file}}:$1]]',
'upload-failure-subj' => 'Težava pri nalaganju',
'upload-failure-msg' => 'Prišlo je do težave z vašo naloženo datoteko iz [$2]:
'upload-too-many-redirects' => 'URL vsebuje preveč preusmeritev',
'upload-unknown-size' => 'neznana velikost',
'upload-http-error' => 'Prišlo je do napake HTTP: $1',
-'upload-copy-upload-invalid-domain' => 'Nalaganje kopij s te domene ni na voljo.',
+'upload-copy-upload-invalid-domain' => 'Nalaganje kopij s te domene ni možno.',
# File backend
'backend-fail-stream' => 'Ne morem pretakati datoteke $1.',
Prosimo, preverite delovanje strani, počakajte kratek čas in poskusite ponovno.
Morda želite poskusiti ob času manjše zasedenosti.',
-'license' => 'Dovoljenje:',
-'license-header' => 'Dovoljenje',
+'license' => 'Licenca:',
+'license-header' => 'Licenca',
'nolicense' => 'Nobeno',
'license-nopreview' => '(Predogled ni na voljo)',
'upload_source_url' => ' (veljaven, javnosti dostopen URL)',
'imagelinks' => 'Uporaba datoteke',
'linkstoimage' => 'Datoteka je del {{PLURAL:$1|naslednje $1 strani|naslednjih $1 strani}} {{GRAMMAR:rodilnik|{{SITENAME}}}}:',
'linkstoimage-more' => 'Na to datoteko se {{PLURAL:$1|povezuje več kot $1 stran|povezujeta več kot $1 strani|povezujejo več kot $1 strani|povezuje več kot $1 strani}}.
-Naslednji seznam obsega samo {{PLURAL:$1|prvo stran, ki se povezuje|prvi $1 strani, ki se povezujeta|prve $1 strani, ki se povezujejo|prvih $1 strani, ki se povezujejo}} na to datoteko.
-Na razpolago je tudi [[Special:WhatLinksHere/$2|celotni seznam]].',
+Naslednji seznam obsega samo {{PLURAL:$1|prvo stran, ki se povezuje|prvi $1 strani, ki se povezujeta|prve $1 strani, ki se povezujejo|prvih $1 strani, ki se povezujejo}} na datoteko.
+Na razpolago je tudi [[Special:WhatLinksHere/$2|popoln seznam]].',
'nolinkstoimage' => 'Z datoteko se ne povezuje nobena stran.',
'morelinkstoimage' => 'Preglejte [[Special:WhatLinksHere/$1|več povezav]] na to datoteko.',
'linkstoimage-redirect' => '$1 (preusmeritev datoteke) $2',
'shared-repo-from' => 'iz $1',
'shared-repo' => 'skupno skladišče',
'shared-repo-name-wikimediacommons' => 'Wikimedijina Zbirka',
-'upload-disallowed-here' => 'Slike žal ne morete prepisati.',
+'upload-disallowed-here' => 'Datoteke žal ne morete prepisati.',
# File reversion
'filerevert' => 'Vrni $1',
'filerevert-legend' => 'Vrni datoteko',
'filerevert-intro' => "Vračate datoteko '''[[Media:$1|$1]]''' na [$4 različico $3, $2].",
'filerevert-comment' => 'Razlog:',
-'filerevert-defaultcomment' => 'Vrnjeno na različico $2, $1',
+'filerevert-defaultcomment' => 'Vrnjeno na različico $2, $1.',
'filerevert-submit' => 'Vrni',
'filerevert-success' => "Datoteka '''[[Media:$1|$1]]''' je bila vrnjena na [$4 različico $3, $2].",
'filerevert-badversion' => 'Ne najdem preteklih lokalnih verzij datoteke s podanim časovnim žigom.',
# Special:ListGroupRights
'listgrouprights' => 'Pravice uporabniških skupin',
-'listgrouprights-summary' => 'Spodaj se nahaja seznam uporabniških skupin na tem wikiju in njim dodeljene pravice dostopa.
-Na voljo so morda [[{{MediaWiki:Listgrouprights-helppage}}|dodatne informacije]] o posameznih skupinah.',
+'listgrouprights-summary' => 'Tu je na razpolago seznam uporabniških skupin na tem wikiju z navedbo dodeljenih pravic dostopa.
+Morda so na razpolago tudi [[{{MediaWiki:Listgrouprights-helppage}}|dodatne informacije]] o posameznih skupinah.',
'listgrouprights-key' => '* <span class="listgrouprights-granted">Dodeljena pravica</span>
* <span class="listgrouprights-revoked">Odvzeta pravica</span>',
'listgrouprights-group' => 'Skupina',
'dellogpage' => 'Dnevnik brisanja',
'dellogpagetext' => 'Spodaj je prikazan seznam nedavnih brisanj.',
'deletionlog' => 'dnevnik brisanja',
-'reverted' => 'Obnovljeno na prejšnjo redakcijo',
+'reverted' => 'Vrnjeno na prejšnjo redakcijo.',
'deletecomment' => 'Razlog:',
'deleteotherreason' => 'Drugi/dodatni razlogi:',
'deletereasonotherlist' => 'Drug razlog',
'undeletedrevisions' => '{{PLURAL:$1|obnovljena $1 redakcija|obnovljeni $1 redakciji|obnovljene $1 redakcije|obnovljenih $1 redakcij}}',
'undeletedrevisions-files' => '$1 {{PLURAL:$1|redakcija|redakciji|redakcije|redakcij}} in $2 {{PLURAL:$2|datoteka|datoteki|datoteke|datotek}} {{PLURAL:$1+$2|obnovljena|obnovljeni|obnovljene|obnovljenih}}',
'undeletedfiles' => '{{PLURAL:$1|obnovljena je $1 datoteka|obnovljeni sta $1 datoteki|obnovljene so $1 datoteke|obnovljenih je $1 datotek}}',
-'cannotundelete' => 'Obnova ni uspela;
-morda je stran obnovil že kdo drug.',
+'cannotundelete' => 'Obnova je spodletela:
+$1',
'undeletedpage' => "'''Obnovili ste stran $1.'''
Nedavna brisanja in obnove so zapisani v [[Special:Log/delete|dnevniku brisanja]].",
Razlog za blokado uporabnika $1 je: »$2«',
'blocklogpage' => 'Dnevnik blokiranja',
'blocklog-showlog' => 'Ta uporabnik je že bil blokiran.
-Dnevnik blokiranja je na voljo spodaj:',
+Za sklicevanje so tule navedeni vnosi v dnevniku blokiranja:',
'blocklog-showsuppresslog' => 'Ta uporabnik je že bil blokiran in skrit.
Dnevnik skrivanja je na voljo spodaj:',
'blocklogentry' => '[[$1]] blokiran s časom poteka blokade $2 $3',
'imageinvalidfilename' => 'Ciljno ime datoteke je neveljavno',
'fix-double-redirects' => 'Posodobi vse preusmeritve, ki kažejo na prvotni naslov',
'move-leave-redirect' => 'Na prejšnji strani ustvari preusmeritev',
-'protectedpagemovewarning' => "'''Opozorilo:''' Stran je bila zaklenjena, tako da jo lahko prestavljajo samo uporabniki z administratorskimi dovoljenji.
-Najnovejši vnos v dnevniku je na voljo spodaj:",
-'semiprotectedpagemovewarning' => "'''Opomba:''' Stran je bila zaklenjena, tako da jo lahko prestavljajo samo registrirani uporabniki.
-Najnovejši vnos v dnevniku je na voljo spodaj:",
+'protectedpagemovewarning' => "'''Opozorilo:''' Stran je bila zaklenjena in jo lahko prestavljajo samo uporabniki z administratorskimi pravicami.
+Za sklicevanje je naveden zadnji vnos v dnevnik:",
+'semiprotectedpagemovewarning' => "'''Opomba:''' Stran je bila zaklenjena in jo lahko prestavljajo samo registrirani uporabniki.
+Za sklicevanje je naveden zadnji vnos v dnevniku:",
'move-over-sharedrepo' => '== Datoteka obstaja ==
[[:$1]] obstaja v deljeni shrambi. Premik datoteke na ta naslov bo prepisalo deljeno datoteko.',
'file-exists-sharedrepo' => 'Izbrano ime datoteke je že v uporabi v deljeni shrambi.
'tooltip-watchlistedit-raw-submit' => 'Posodobi spisek nadzorov',
'tooltip-recreate' => 'Ponovno ustvari stran, čeprav je bila izbrisana',
'tooltip-upload' => 'Pričnite z nalaganjem',
-'tooltip-rollback' => 'Funkcija »Vrni« z enim klikom povrne vsa urejanja zadnjega urejevalca te strani',
+'tooltip-rollback' => 'Možnost »Vrni« z enim klikom povrne vsa urejanja zadnjega urejevalca te strani.',
'tooltip-undo' => '"Razveljavi" vrne to urejanje in odpre predogled v oknu za urejanje.
Omogoča vnos pojasnila v povzetku urejanja.',
'tooltip-preferences-save' => 'Shrani nastavitve',
'file-info' => 'Velikost datoteke: $1, MIME-vrsta: <code>$2</code>',
'file-info-size' => '$1 × $2 točk, velikost datoteke: $3, vrsta MIME: $4',
'file-info-size-pages' => '$1 × $2 točk, velikost datoteke: $3, vrsta MIME: $4, $5 {{PLURAL:$5|stran|strani}}',
-'file-nohires' => 'Slika višje ločljivosti ni na voljo.',
+'file-nohires' => 'Višja ločljivost slike ni na voljo.',
'svg-long-desc' => 'datoteka SVG, v izvirniku $1 × $2 slikovnih točk, velikost datoteke: $3',
'svg-long-desc-animated' => 'animirana datoteka SVG, v izvirniku $1 × $2 slikovnih točk, velikost datoteke: $3',
'show-big-image' => 'Slika v višji ločljivosti',
'version-hook-name' => 'Ime razširitve',
'version-hook-subscribedby' => 'Naročen s strani',
'version-version' => '(Različica $1)',
-'version-license' => 'Dovoljenje',
+'version-license' => 'Licenca',
'version-poweredby-credits' => "Ta wiki poganja '''[//www.mediawiki.org/ MediaWiki]''', vse pravice pridržave © 2001-$1 $2.",
'version-poweredby-others' => 'drugi',
'version-license-info' => 'MediaWiki je prosto programje; lahko ga razširjate in / ali spreminjate pod pogoji GNU General Public License, kot ga je objavila Free Software Foundation; bodisi License različice 2 ali (po vaši izbiri) katere koli poznejše različice.
'qbfind' => 'Fenda',
'qbedit' => 'Ändern',
'qbpageoptions' => 'Seytaoptiona',
-'qbpageinfo' => 'Seytadata',
'qbmyoptions' => 'Menne Seyta',
'qbspecialpages' => 'Spezialseyta',
'faq' => 'FAQ',
'qbbrowse' => 'Ka soo raadi',
'qbedit' => 'Wax ka bedel',
'qbpageoptions' => 'Boggaan',
-'qbpageinfo' => 'isku xiran',
'qbmyoptions' => 'Boggageyga',
'qbspecialpages' => 'Bogaga qaaska ah',
'faq' => 'SIL',
'qbbrowse' => 'Shfletoni',
'qbedit' => 'Redaktoni',
'qbpageoptions' => 'Kjo faqe',
-'qbpageinfo' => 'Kontekst',
'qbmyoptions' => 'Faqet e mia',
'qbspecialpages' => 'Faqet speciale',
'faq' => 'Pyetje që bëhen shpesh',
'qbbrowse' => 'Потражи',
'qbedit' => 'Уреди',
'qbpageoptions' => 'Поставке странице',
-'qbpageinfo' => 'Садржај странице',
'qbmyoptions' => 'Моје странице',
'qbspecialpages' => 'Посебне странице',
'faq' => 'НПП',
'qbbrowse' => 'Potraži',
'qbedit' => 'Uredi',
'qbpageoptions' => 'Postavke stranice',
-'qbpageinfo' => 'Sadržaj stranice',
'qbmyoptions' => 'Moje stranice',
'qbspecialpages' => 'Posebne stranice',
'faq' => 'NPP',
'qbbrowse' => 'Bleederje',
'qbedit' => 'Annerje',
'qbpageoptions' => 'Disse Siede',
-'qbpageinfo' => 'Siedendoatäie',
'qbmyoptions' => 'Mien Sieden',
'qbspecialpages' => 'Spezialsieden',
'faq' => 'Oafte stoalde Froagen',
'qbbrowse' => 'Sungsi',
'qbedit' => 'Édit',
'qbpageoptions' => 'Kaca ieu',
-'qbpageinfo' => 'Kontéks',
'qbmyoptions' => 'Kaca kuring',
'qbspecialpages' => 'Kaca husus',
'faq' => 'NLD',
* @author Rotsee
* @author S.Örvarr.S
* @author Sannab
+ * @author Sendelbach
* @author Sertion
* @author Skalman
* @author Stefan2
'qbbrowse' => 'Bläddra igenom',
'qbedit' => 'Redigera',
'qbpageoptions' => 'Denna sida',
-'qbpageinfo' => 'Sidinformation',
'qbmyoptions' => 'Mina inställningar',
'qbspecialpages' => 'Specialsidor',
'faq' => 'FAQ',
'edit-already-exists' => 'Sidan kunde inte skapas.
Den finns redan.',
'defaultmessagetext' => 'Standardtext för meddelande',
+'content-failed-to-parse' => 'Det gick inte att parsa $2 innehåll för $1 modell: $3',
+'invalid-content-data' => 'Ogiltig innehållsdata',
+'content-not-allowed-here' => 'innehåll av "$1" är inte tillåtet på sidan [[$2]]',
+
+# Content models
+'content-model-wikitext' => 'wikitext',
+'content-model-text' => 'oformaterad text',
+'content-model-javascript' => 'JavaScript',
+'content-model-css' => 'CSS',
# Parser/template warnings
'expensive-parserfunction-warning' => 'Varning: Denna sida innehåller för många anrop av resurskrävande parserfunktioner.
'undeletedrevisions' => '{{PLURAL:$1|en version återställd|$1 versioner återställda}}',
'undeletedrevisions-files' => '$1 {{PLURAL:$1|version|versioner}} och $2 {{PLURAL:$2|fil|filer}} återställda',
'undeletedfiles' => '{{PLURAL:$1|en fil återställd|$1 filer återställda}}',
-'cannotundelete' => 'Återställning misslyckades; kanske någon redan har återställt sidan.',
+'cannotundelete' => 'Återställning misslyckades:
+$1',
'undeletedpage' => "'''$1 har återställts'''
Se [[Special:Log/delete|raderingsloggen]] för en förteckning över de senaste raderingarna och återställningarna.",
'immobile-target-namespace-iw' => 'Interwikilänk är inte ett giltigt mål för sidflyttar.',
'immobile-source-page' => 'Denna sida är inte flyttbar.',
'immobile-target-page' => 'Kan inte flytta till det målnamnet.',
+'bad-target-model' => 'Den önskade destinationen använder en annan innehållsmodell. Kan inte konvertera från $1 till $2.',
'imagenocrossnamespace' => 'Kan inte flytta filer till andra namnrymder än filnamnrymden',
'nonfile-cannot-move-to-file' => 'Kan inte flytta icke-fil till filnamnrymden',
'imagetypemismatch' => 'Den nya filändelsen motsvarar inte filtypen',
# Scary transclusion
'scarytranscludedisabled' => '[Interwiki-inklusion är inte aktiverad]',
'scarytranscludefailed' => '[Hämtning av mall för $1 misslyckades]',
+'scarytranscludefailed-httpstatus' => '[Hämtning av mall för $1 misslyckades: HTTP $2]',
'scarytranscludetoolong' => '[För lång URL]',
# Delete conflict
'qbbrowse' => 'Vinjari',
'qbedit' => 'Hariri',
'qbpageoptions' => 'Ukurasa huu',
-'qbpageinfo' => 'Muktadha',
'qbmyoptions' => 'Kurasa zangu',
'qbspecialpages' => 'Kurasa za pekee',
'faq' => 'Maswali ya kawaida',
'qbbrowse' => 'Uoglůndańy',
'qbedit' => 'Sprowjej',
'qbpageoptions' => 'Ta zajta',
-'qbpageinfo' => 'Kontekst',
'qbmyoptions' => 'Moje zajty',
'qbspecialpages' => 'Szpecyjalne zajty',
'faq' => 'FAQ',
'qbbrowse' => 'உலவு',
'qbedit' => 'தொகு',
'qbpageoptions' => 'பக்க விருப்பத் தேர்வுகள்',
-'qbpageinfo' => 'பக்கத் தகவல்கள்',
'qbmyoptions' => 'என் விருப்பத்தேர்வுகள்',
'qbspecialpages' => 'சிறப்புப் பக்கங்கள்',
'faq' => 'அடிக்கடி கேட்கப்படும் கேள்விகள்',
'qbbrowse' => 'విహరించు',
'qbedit' => 'సవరించు',
'qbpageoptions' => 'ఈ పేజీ',
-'qbpageinfo' => 'సందర్భం',
'qbmyoptions' => 'నా పేజీలు',
'qbspecialpages' => 'ప్రత్యేక పేజీలు',
'faq' => 'తరచూ అడిగే ప్రశ్నలు',
అది ఇప్పటికే ఉంది.',
'defaultmessagetext' => 'అప్రమేయ సందేశపు పాఠ్యం',
+# Content models
+'content-model-wikitext' => 'వికీపాఠ్యం',
+'content-model-text' => 'సాదా పాఠ్యం',
+'content-model-javascript' => 'జావాస్క్రిప్ట్',
+
# Parser/template warnings
'expensive-parserfunction-warning' => 'హెచ్చరిక: ఈ పేజీలో ఖరీదైన పార్సరు పిలుపులు చాలా ఉన్నాయి.
'mailnologin' => 'పంపించవలసిన చిరునామా లేదు',
'mailnologintext' => 'ఇతరులకు ఈ-మెయిలు పంపించాలంటే, మీరు [[Special:UserLogin|లాగిన్]] అయి ఉండాలి, మరియు మీ [[Special:Preferences|అభిరుచుల]]లో సరైన ఈ-మెయిలు చిరునామా ఇచ్చి ఉండాలి.',
'emailuser' => 'ఈ వాడుకరికి ఈ-మెయిలుని పంపించండి',
+'emailuser-title-target' => 'ఈ {{GENDER:$1|వాడుకరికి}} ఈమెయిలు పంపించండి',
'emailpage' => 'వాడుకరికి ఈ-మెయిలుని పంపించు',
'emailpagetext' => 'వాడుకరికి ఈమెయిలు సందేశము పంపించుటకు క్రింది ఫారంను ఉపయోగించవచ్చు. [[Special:Preferences|మీ వాడుకరి అభిరుచుల]]లో మీరిచ్చిన ఈ-మెయిలు చిరునామా "నుండి" ఆ సందేశం వచ్చినట్లుగా ఉంటుంది, కనుక వేగుని అందుకునేవారు నేరుగా మీకు జవాబివ్వగలుగుతారు.',
'usermailererror' => 'మెయిలు ఆబ్జెక్టు ఈ లోపాన్ని చూపింది:',
'pageinfo-views' => 'వీక్షణల సంఖ్య',
'pageinfo-watchers' => 'పేజీ వీక్షకుల సంఖ్య',
'pageinfo-edits' => 'మొత్తం మార్పుల సంఖ్య',
+'pageinfo-toolboxlink' => 'పేజీ సమాచారం',
# Skin names
'skinname-standard' => 'సంప్రదాయ',
'qbbrowse' => 'Мурур',
'qbedit' => 'Вироиш',
'qbpageoptions' => 'Ин саҳифа',
-'qbpageinfo' => 'Бофт',
'qbmyoptions' => 'Саҳифаҳои ман',
'qbspecialpages' => 'Саҳифаҳои вижа',
'faq' => 'Саволҳои тез-тез пурсидашуда',
'revdelete-success' => "'''Тағйири намоёнии нусха бо муваффақият анҷом шуд.'''",
'logdelete-success' => "'''Тағйири намоёнии маврид бо муваффақият анҷом шуд.'''",
'revdel-restore' => 'Тағйири падидорӣ',
+'revdel-restore-visible' => 'нусхаҳои намоён',
'pagehist' => 'Таърихи саҳифа',
'deletedhist' => 'Таърихи ҳазфшуда',
'revdelete-edit-reasonlist' => 'Вироиш ҳазф далелҳо',
'filehist-dimensions' => 'Андоза',
'filehist-filesize' => 'Андозаи парванда',
'filehist-comment' => 'Тавзеҳ',
-'imagelinks' => 'Ð\9fайвандҳои парванда',
+'imagelinks' => 'Ð\98Ñ\81Ñ\82иÑ\84одаи парванда',
'linkstoimage' => '{{PLURAL:$1|Саҳифаҳои|$1 Саҳифаи}} зерин ба ин акс пайванданд:',
'nolinkstoimage' => 'Ҳеҷ саҳифае ба ин акс пайванд надорад.',
'sharedupload' => 'Ин парванда аз $1 мебошад ва шояд аз тарафи дигар лоиҳаҳо истифода шавад.',
'qbbrowse' => 'Murur',
'qbedit' => 'Viroiş',
'qbpageoptions' => 'In sahifa',
-'qbpageinfo' => 'Boft',
'qbmyoptions' => 'Sahifahoi man',
'qbspecialpages' => 'Sahifahoi viƶa',
'faq' => 'Savolhoi tez-tez pursidaşuda',
'qbbrowse' => 'สืบค้น',
'qbedit' => 'แก้ไข',
'qbpageoptions' => 'หน้านี้',
-'qbpageinfo' => 'บริบท',
'qbmyoptions' => 'หน้าของฉัน',
'qbspecialpages' => 'หน้าพิเศษ',
'faq' => 'คำถามถามบ่อย',
'qbbrowse' => 'Göz aýla',
'qbedit' => 'Redaktirle',
'qbpageoptions' => 'Bu sahypa',
-'qbpageinfo' => 'Kontekst',
'qbmyoptions' => 'Meniň sahypalarym',
'qbspecialpages' => 'Ýörite sahypalar',
'faq' => 'KSS',
'qbbrowse' => 'Basa-basahin',
'qbedit' => 'Baguhin',
'qbpageoptions' => 'Itong pahina',
-'qbpageinfo' => 'Konteksto',
'qbmyoptions' => 'Mga pahina ko',
'qbspecialpages' => 'Mga natatanging pahina',
'faq' => "Mga karaniwang itinatanong (''FAQ'')",
'qbbrowse' => 'Tara',
'qbedit' => 'Değiştir',
'qbpageoptions' => 'Bu sayfa',
-'qbpageinfo' => 'Bağlam',
'qbmyoptions' => 'Sayfalarım',
'qbspecialpages' => 'Özel sayfalar',
'faq' => 'SSS',
Sayfa zaten mevcut.',
'defaultmessagetext' => 'Varsayılan mesaj metni',
+# Content models
+'content-model-javascript' => 'JavaScript',
+
# Parser/template warnings
'expensive-parserfunction-warning' => 'Uyarı: Bu sayfa çok fazla zengin derleyici fonksiyonu çağrısı içeriyor.
* @file
*
* @author Alfredie
+ * @author Arlin
* @author Kaganer
* @author Reedy
* @author Sahran
'thu' => 'پ',
'fri' => 'ج',
'sat' => 'ش',
-'january' => 'Ù\82Û\95ھرÙ\89تاÙ\86',
-'february' => 'ھۇت',
-'march' => 'Ù\86Û\95Û\8bرÛ\87ز',
-'april' => 'ئۇمۇت',
-'may_long' => 'باھار',
-'june' => 'سÛ\95Ù¾Û\95ر',
-'july' => 'چىللە',
-'august' => 'تÙ\88Ù\85Û\87ز',
-'september' => 'مىزان',
-'october' => 'ئوغۇز',
-'november' => 'ئوغلاق',
-'december' => 'كۆنەك',
-'january-gen' => 'Ù\82Û\95ھرÙ\89تاÙ\86',
-'february-gen' => 'ھۇت',
-'march-gen' => 'Ù\86Û\95Û\8bرÛ\87ز',
-'april-gen' => 'ئۇمۇت',
-'may-gen' => 'باھار',
-'june-gen' => 'سÛ\95Ù¾Û\95ر',
-'july-gen' => 'چىللە',
-'august-gen' => 'تÙ\88Ù\85Û\87ز',
-'september-gen' => 'مىزان',
-'october-gen' => 'ئوغۇز',
-'november-gen' => 'ئوغلاق',
-'december-gen' => 'كۆنەك',
-'jan' => 'Ù\82Û\95ھرÙ\89تاÙ\86',
-'feb' => 'ھۇت',
-'mar' => 'Ù\86Û\95Û\8bرÛ\87ز',
-'apr' => 'ئۇمۇت',
-'may' => 'باھار',
-'jun' => 'سÛ\95Ù¾Û\95ر',
-'jul' => 'چىللە',
-'aug' => 'تÙ\88Ù\85Û\87ز',
-'sep' => 'مىزان',
-'oct' => 'ئوغۇز',
-'nov' => 'ئوغلاق',
-'dec' => 'كۆنەك',
+'january' => 'Ù\8aاÙ\86Û\8bار',
+'february' => 'فېۋرال',
+'march' => 'Ù\85ارت',
+'april' => 'ئاپرېل',
+'may_long' => 'ماي',
+'june' => 'ئÙ\89Ù\8aÛ\87Ù\86',
+'july' => 'ئىيۇل',
+'august' => 'ئاÛ\8bغÛ\87ست',
+'september' => 'سىنتەبىر',
+'october' => 'ئۆكتەبىر',
+'november' => 'نويابىر',
+'december' => 'دېكابىر',
+'january-gen' => 'Ù\8aاÙ\86Û\8bار',
+'february-gen' => 'فېۋرال',
+'march-gen' => 'Ù\85ارت',
+'april-gen' => 'ئاپرېل',
+'may-gen' => 'ماي',
+'june-gen' => 'ئÙ\89Ù\8aÛ\87Ù\86',
+'july-gen' => 'ئىيۇل',
+'august-gen' => 'ئاÛ\8bغÛ\87ست',
+'september-gen' => 'سىنتەبىر',
+'october-gen' => 'ئۆكتەبىر',
+'november-gen' => 'نويابىر',
+'december-gen' => 'دېكابىر',
+'jan' => 'Ù\8aاÙ\86Û\8bار',
+'feb' => 'فېۋرال',
+'mar' => 'Ù\85ارت',
+'apr' => 'ئاپرېل',
+'may' => 'ماي',
+'jun' => 'ئÙ\89Ù\8aÛ\87Ù\86',
+'jul' => 'ئىيۇل',
+'aug' => 'ئاÛ\8bغÛ\87ست',
+'sep' => 'سىنتەبىر',
+'oct' => 'ئۆكتەبىر',
+'nov' => 'نويابىر',
+'dec' => 'دېكابىر',
# Categories related messages
-'pagecategories' => '{{PLURAL:$1|تۈر|تۈر}}',
+'pagecategories' => '{{PLURAL:$1|تۈر|تۈرلەر}}',
'category_header' => '"$1" تۈردىكى بەتلەر',
'subcategories' => 'تارماق تۈر',
'category-media-header' => '"$1" تۈردىكى ۋاسىتە',
'linkprefix' => '/^(.*?)([a-zA-Z\\x80-\\xff]+)$/sD',
'about' => 'ھەققىدە',
-'article' => 'Ù\85Û\95زÙ\85Û\87Ù\86 بÛ\90تÙ\89',
+'article' => 'Ù\85Û\87Ù\86دÛ\95رÙ\89جÛ\95',
'newwindow' => '(يېڭى كۆزنەكتە ئاچ)',
'cancel' => 'ۋاز كەچ',
'moredotdotdot' => 'تەپسىلىي…',
'qbbrowse' => 'كۆز يۈگۈرت',
'qbedit' => 'تەھرىر',
'qbpageoptions' => 'بۇ بەت',
-'qbpageinfo' => 'كونتېكست',
'qbmyoptions' => 'بەتلەرىم',
'qbspecialpages' => 'ئالاھىدە بەتلەر',
'faq' => 'كۆپ كۆرۈلىدىغان مەسىلىلەر',
'vector-action-protect' => 'قوغدا',
'vector-action-undelete' => 'ئەسلىگە قايتۇر',
'vector-action-unprotect' => 'قوغداش ئۆزگەرت',
-'vector-simplesearch-preference' => 'ئالىي ئىزدەش تەكلىپىنى ئاچ (Vector تېرىدىلا)',
+'vector-simplesearch-preference' => 'ئاددىي ئىزدەش ئىستون ئاچ (پەقەت ۋېكتور قېلىپ)',
'vector-view-create' => 'قۇر',
'vector-view-edit' => 'تەھرىر',
'vector-view-history' => 'تارىخ كۆرسەت',
[[Special:Version|نەشر بېتى]] نى كۆرۈڭ.',
'ok' => 'ماقۇل',
+'pagetitle' => '$1 - {{SITENAME}}',
'pagetitle-view-mainpage' => '{{SITENAME}}',
'backlinksubtitle' => '← $1',
'retrievedfrom' => '"$1" دىن ئېرىشكەن',
'site-atom-feed' => '$1 نىڭ Atom قانالى',
'page-rss-feed' => '"$1" نىڭ RSS قانىلى',
'page-atom-feed' => '"$1" نىڭ Atom قانىلى',
-'feed-atom' => 'ئاتوم',
+'feed-atom' => 'Atom',
'feed-rss' => 'RSS',
'red-link-title' => '$1 (بەت مەۋجۇد ئەمەس)',
'sort-descending' => 'كېمەيگۈچى تەرتىپ',
'dberrortext' => 'ساندان سۈرۈشتۈرۈشتە گرامماتىكىلىق خاتالىق يۈز بەردى.
يۇمشاق دېتالنىڭ ئۆزىدىكى خاتالىقتىن كېلىپ چىققان بولۇشى مۇمكىن.
ئاخىرقى قېتىملىق ساندان سۈرۈشتۈرۈش بۇيرۇقى:
-<blockquote><tt>$1</tt></blockquote>
- \\"<tt>$2</tt>\\"فۇنكسىيىدىن كەلگەن.
-MySQL قايتۇرغان خاتالىق \\"<tt>$3: $4</tt>\\".',
+<blockquote><code>$1</code></blockquote>
+ "<code>$2</code>"فۇنكسىيىدىن كەلگەن.
+ساندان قايتۇرغان خاتالىق "<samp>$3: $4</samp>".',
'dberrortextcl' => 'ساندان سۈرۈشتۈرۈشتە گرامماتىكىلىق خاتالىق يۈز بەردى.
ئاخىرقى قېتىملىق ساندان سۈرۈشتۈرۈش بۇيرۇقى:
"$1"
'protectedpagetext' => 'بۇ بەت تەھرىرلەشنىڭ ئالدىنى ئېلىش ئۈچۈن قۇلۇپلانغان.',
'viewsourcetext' => 'سىز بۇ بەتنى ئەسلى كودىنى كۆرەلەيسىز ۋە كۆچۈرەلەيسىز:',
'viewyourtext' => "بۇ بەتتىكى '''تەھرىرلىگەنلىرىڭىز'''نىڭ ئەسلى كودىنى كۆرۈپ كۆچۈرەلەيسىز.",
-'protectedinterface' => 'بۇ بەت يۇمشاق دېتالنىڭ كۆرۈنۈش تېكستىنى تەمىنلىگەن، خالىغانچە تەھرىرلەشتىن ساقلىنىش ئۈچۈن قۇلۇپلانغان.',
+'protectedinterface' => 'بۇ بەت يۇمشاق دېتالنىڭ كۆرۈنۈش تېكستىنى تەمىنلىگەن، خالىغانچە تەھرىرلەشتىن ساقلىنىش ئۈچۈن قۇلۇپلانغان.
+مەسىلەن ئەگەر تەرجىمە قىلسىڭىز [//translatewiki.net/wiki/Main_Page?setlang=ug translatewiki.net] ئۇنداقتا MediaWiki يەرلىكلەشتۈرۈش پىلانىنى ئىشلىتىشنى ئويلىشىڭ.',
'editinginterface' => "'''ئاگاھلاندۇرۇش:''' سىز تەھرىرلەۋاتقان بەت يۇمشاق دېتالنىڭ كۆرۈنۈش تېكستىگە ئىشلىتىلىدۇ.
بۇ بەت ئۆزگەرتىلسە باشقا ئىشلەتكۈچىلەرنىڭ كۆرۈنۈش ئۇسلۇبىغا تەسىر كۆرسىتىدۇ.
<span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} مۇناسىۋەتلىك خاتىرىسىنى ئىزدىيەلەيسىز،],
[{{fullurl:{{FULLPAGENAME}}|action=edit}} بۇ بەتنى تەھرىرلىيەلەيسىز]</span>',
'noarticletext-nopermission' => 'بۇ بەتتە ھازىرچە مەزمۇن يوق.
- سىز باشقا بەتتە [[Special:Search/{{PAGENAME}}|بۇ بەتنىڭ ماۋزۇسىنى ئىزدىيەلەيسىز]] ياكى <span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}}] مۇناسىۋەتلىك خاتىرىسىنى ئىزدىيەلەيسىز،</span>',
+ سىز باشقا بەتتە [[Special:Search/{{PAGENAME}}|بۇ بەتنىڭ ماۋزۇسىنى ئىزدىيەلەيسىز]] ياكى <span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}}] مۇناسىۋەتلىك خاتىرىسىنى ئىزدىيەلەيسىز،</span>لىكىن سزنىڭ بەت قۇرۇش ھوقوقىڭز يوق.',
'missing-revision' => '"{{PAGENAME}}" ئاتلىق بەتنىڭ تۈزىتىلگەن نەشرى #$1 مەۋجۇت ئەمەس.
ئادەتتە بۇ ئۆچۈرۈلگەن بىر بەتنىڭ ئۇلانمىسىغا كىرگەنلىك سەۋەبىدىن بولىدۇ.
* '''Mozilla / Firefox / Safari:''' دا ''Shift'' كۇنۇپكىسىنى بېسىپ تۇرۇپ ''قايتا يۈكلە''نى ياكى ''Ctrl-F5'' ياكى ''Ctrl-R'' (''Mac تا Command-R'')؛
* '''Google Chrome:''' دا ''Ctrl-Shift-R'' (''Command-Shift-R'' Mac)
*'''Internet Explorer:''' دا ''Ctrl'' نى بېسىپ تۇرۇپ ''يېڭىلا,'' ياكى ''Ctrl-F5''؛
-* '''Konqueror: دا ''' ''قايتا يۈكلە'' ياكى ''F5''؛
* '''Opera:''' دا ''قورال → مايىللىق''؛ نى بېسىپ غەملەكنى تازىلاڭ.",
'usercssyoucanpreview' => "ئەسكەرتىش:''' ساقلاشتىن ئىلگىرى \"{{int:showpreview}}\" توپچىنى ئىشلىتىپ يېڭى CSS نى سىناڭ.",
'userjsyoucanpreview' => "ئەسكەرتىش:''' ساقلاشتىن ئىلگىرى \"{{int:showpreview}}\" توپچىنى ئىشلىتىپ يېڭى JS نى سىناڭ.",
'edit-already-exists' => 'يېڭى بەت قۇرالمىدى
ئۇ مەۋجۇد.',
'defaultmessagetext' => 'كۆڭۈلدىكى ئۇچۇر تېكستى',
+'content-failed-to-parse' => '$2 نى $1 گە ئانالىز قلش مەغلۇپ بولدى: $3',
# Parser/template warnings
'expensive-parserfunction-warning' => "'''ئاگاھلاندۇرۇش:''' بۇ بەت ناھايىتى كۆپ يۇقىرى سەرپىياتتىكى گىرامماتىكىلىق ئىقتىدارنى چاقىرغان.\\n
'username' => 'ئىشلەتكۇچى ئىسمى:',
'uid' => 'ئىشلەتكۈچى كىملىك:',
'prefs-memberingroups' => '{{PLURAL:$1|بىر|كۆپ}} گۇرۇپپا ئەزاسى:',
+'prefs-memberingroups-type' => '$1',
'prefs-registration' => 'خەتلەتكەن ۋاقىت:',
+'prefs-registration-date-time' => '$1',
'yourrealname' => 'ﺗﻮﻟﯘﻕ ئىسىم:',
'yourlanguage' => 'تىل:',
'yourvariant' => 'مەزمۇن تىل شالغۇتى:',
'userrights-notallowed' => 'ھېساباتىڭىزنىڭ ئىشلەتكۈچى ھوقۇقىنى قوشۇش ياكى ئۆزگەرتىش ھوقۇقى يوق.',
'userrights-changeable-col' => 'سىز ئۆزگەرتەلەيدىغان گۇرۇپپا',
'userrights-unchangeable-col' => 'سىز ئۆزگەرتەلمەيدىغان گۇرۇپپا',
+'userrights-irreversible-marker' => '$1*',
# Groups
'group' => 'گۇرۇپپا:',
'minoreditletter' => 'ئازراقلا',
'newpageletter' => 'يېڭى',
'boteditletter' => 'ماشىنا ئادەم',
+'unpatrolledletter' => '!',
'number_of_watching_users_pageview' => '[$1 {{PLURAL:$1|ئىشلەتكۈچى|ئىشلەتكۈچى}}كۆزىتىۋاتىدۇ]',
'rc_categories' => 'تۈر چېگرىسى ("|" بىلەن ئايرىلىدۇ )',
'rc_categories_any' => 'خالىغان',
+'rc-change-size' => '$1',
'newsectionsummary' => '* $1 * يېڭى ئابزاس',
'rc-enhanced-expand' => 'تەپسىلاتىنى كۆرسەت (JavaScript قوللىشى زۆرۈر)',
'rc-enhanced-hide' => 'تەپسىلاتىنى يوشۇر',
# Book sources
'booksources' => 'كىتاب مەنبەسى',
'booksources-search-legend' => 'كىتاب مەنبەسى ئىزدە',
+'booksources-isbn' => 'ISBN:',
'booksources-go' => 'يۆتكەل',
'booksources-text' => 'تۆۋەندىكىسى بىر قىسىم تور كىتابخانىلىرىنىڭ تىزىملىكى، ئىچىدە سىز ئىزدىمەكچى بولغان كىتابلارنىڭ تېخىمۇ كۆپ ئۇچۇرى بولۇشى مۇمكىن:',
'booksources-invalid-isbn' => 'تەمىنلىگەن ISBN نومۇرى توغرا ئەمەس. ئەسلى كۆچۈرگەن مەنبەدىكى نومۇردا خاتالىق بار يوقلۇقىنى تەكشۈرۈڭ.',
'listgrouprights-rights' => 'ھوقۇق',
'listgrouprights-helppage' => 'Help: گۇرۇپپا ھوقۇقى',
'listgrouprights-members' => '(ئەزالار تىزىملىكى)',
+'listgrouprights-right-revoked' => '<span class="listgrouprights-revoked">$1 <code>($2)</code></span>',
'listgrouprights-addgroup' => ' {{PLURAL:$2|بىر|بىر قانچە}} گۇرۇپپىغا قوشالايدۇ: $1',
'listgrouprights-removegroup' => ' {{PLURAL:$2|بىر|بىر قانچە}} گۇرۇپپىدىن چىقىرىۋېتەلەيدۇ: $1',
'listgrouprights-addgroup-all' => 'ھەممە گۇرۇپپىغا قوش',
[[Special:Log/delete|ئۆچۈرۈش خاتىرىسى]]دىن پايدىلىنىپ ئۆچۈر ۋە ئەسلىگە كەلتۈر خاتىرىسىنى كۆرۈڭ.",
'undelete-header' => 'يېقىنقى خاتىرىنى سۈرۈشتۈرمەكچى بولسىڭىز [[Special:Log/delete|ئۆچۈرۈش خاتىرىسى]]دىن پايدىلىنىڭ.',
+'undelete-search-title' => 'ئۆچۈرۈئتلگەن بەتنى ئزدەش',
'undelete-search-box' => 'ئۆچۈرۈلگەن بەتنى ئىزدە',
'undelete-search-prefix' => 'باشلانغان بەتنى كۆرسەت:',
'undelete-search-submit' => 'ئىزدەش',
$1',
'undelete-show-file-confirm' => '$2 $3 دىكى \\"<nowiki>$1</nowiki>\\" نىڭ ئۆچۈرۈلگەن تۈزىتىلگەن نەشرىنى راستىنلا كۆرەمسىز؟',
'undelete-show-file-submit' => 'ھەئە',
+'undelete-revisionrow' => '$1 $2 ($3) $4 . . $5 $6 $7',
# Namespace form on various pages
'namespace' => 'ئات بوشلۇقى',
'proxyblockreason' => 'IP ئادرېسىڭىز ئوچۇق ۋاكالەتچى، ئۇ ئاللىبۇرۇن چەكلەنگەن.
ئىنتېرنېت مۇلازىمىتى تەمىنلىگۈچى سودىگەر ياكى تېخنىكىلىق قوللىغۇچى بىلەن ئالاقىلىشىڭ ھەمدە ئۇلارغا بۇ ئېغىر بىخەتەرلىك مەسىلىسىنى ئۇقتۇرۇڭ.',
'proxyblocksuccess' => 'تامام',
+'sorbs' => 'DNSBL',
'sorbsreason' => 'IP ئادرېسىڭىز {{SITENAME}} دا DNSBL تەرىپىدىن ئوچۇق ۋاكالەتچى تىزىملىكىگە قوشۇلغان.',
'sorbs_create_account_reason' => 'IP ئادرېسىڭىز {{SITENAME}} دا DNSBL تەرىپىدىن ئوچۇق ۋاكالەتچى تىزىملىكىگە قوشۇلغان.
شۇڭا سىز يېڭى ھېسابات قۇرالمايسىز.',
بەت چىقىرىشتا، تۆۋەندىكى تېكست رامكىسىغا بەت ماۋزۇسىنى كىرگۈزۈپ، ھەر بىر قۇردا بىر ماۋزۇ، ھەمدە بەت تارىخ بار ئىلگىرىكى تۈزىتىلگەن نەشرىنى تاللامسىز يوق، ياكى پەقەت ئاخىرقى قېتىملىق تەھرىر ئۇچۇرى بار نۆۋەتتىكى تۈزىتىلگەن نەشرىنى چىقىرىشنى تاللاڭ.
ئۇنىڭدىن باشقا ئۇلانمىدىن پايدىلىنىپ ھۆججەت چىقىرالايسىز، مەسىلەن سىز [[{{#Special:Export}}/{{MediaWiki:Mainpage}}]] دىن پايدىلىنىپ "[[{{MediaWiki:Mainpage}}]]"بەت چىقىرالايسىز.',
+'exportall' => 'بارلىق بەتنى چىقىرىش',
'exportcuronly' => 'ھەممە تارىخنى ئەمەس بەلكى نۆۋەتتىكى تۈزىتىلگەن نەشرىنىلا ئۆز ئىچىگە ئالىدۇ.',
'exportnohistory' => "----
'''دىققەت:''' ئىقتىدار سەۋەبلىك بۇ جەدۋەلدىن ھەممە تارىخنى چىقىرىش چەكلەنگەن.",
'thumbnail_error' => 'كىچىك رەسىم قۇرۇش خاتالىقى: $1',
'djvu_page_error' => 'DjVu بېتى دائىرىدىن ھالقىپ كەتتى',
'djvu_no_xml' => 'DjVu ھۆججىتىدىن XML گە ئېرىشەلمىدى',
+'thumbnail-dest-create' => 'قارار ھۈجەتتە ۋاقتلىق كىچىك ھۈجەت ساقلىغل بولمدى',
'thumbnail_invalid_params' => 'ئىناۋەتسىز كىچىك رەسىم پارامېتىرى',
'thumbnail_dest_directory' => 'نىشان مۇندەرىجە قۇرالمىدى',
'thumbnail_image-type' => 'سۈرەت فورماتىنى قوللىمايدۇ',
'tooltip-preferences-save' => 'مايىللىق ساقلا',
'tooltip-summary' => 'قىسقىچە ئۈزۈندە كىرگۈزۈڭ',
+# Stylesheets
+'common.css' => '/* CSS placed here will be applied to all skins */',
+'standard.css' => '/* CSS placed here will affect users of the Standard skin */',
+'nostalgia.css' => '/* CSS placed here will affect users of the Nostalgia skin */',
+'cologneblue.css' => '/* CSS placed here will affect users of the Cologne Blue skin */',
+'monobook.css' => '/* CSS placed here will affect users of the Monobook skin */',
+'myskin.css' => '/* CSS placed here will affect users of the MySkin skin */',
+'chick.css' => '/* CSS placed here will affect users of the Chick skin */',
+'simple.css' => '/* CSS placed here will affect users of the Simple skin */',
+'modern.css' => '/* CSS placed here will affect users of the Modern skin */',
+'vector.css' => '/* CSS placed here will affect users of the Vector skin */',
+'print.css' => '/* CSS placed here will affect the print output */',
+'noscript.css' => '/* CSS placed here will affect users with JavaScript disabled */',
+'group-autoconfirmed.css' => '/* CSS placed here will affect autoconfirmed users only */',
+'group-bot.css' => '/* CSS placed here will affect bots only */',
+'group-sysop.css' => '/* CSS placed here will affect sysops only */',
+'group-bureaucrat.css' => '/* CSS placed here will affect bureaucrats only */',
+
+# Scripts
+'common.js' => '/* Any JavaScript here will be loaded for all users on every page load. */',
+'standard.js' => '/* Any JavaScript here will be loaded for users using the Standard skin */',
+'nostalgia.js' => '/* Any JavaScript here will be loaded for users using the Nostalgia skin */',
+'cologneblue.js' => '/* Any JavaScript here will be loaded for users using the Cologne Blue skin */',
+'monobook.js' => '/* Any JavaScript here will be loaded for users using the MonoBook skin */',
+'myskin.js' => '/* Any JavaScript here will be loaded for users using the MySkin skin */',
+'chick.js' => '/* Any JavaScript here will be loaded for users using the Chick skin */',
+'simple.js' => '/* Any JavaScript here will be loaded for users using the Simple skin */',
+'modern.js' => '/* Any JavaScript here will be loaded for users using the Modern skin */',
+'vector.js' => '/* Any JavaScript here will be loaded for users using the Vector skin */',
+'group-autoconfirmed.js' => '/* Any JavaScript here will be loaded for autoconfirmed users only */',
+'group-bot.js' => '/* Any JavaScript here will be loaded for bots only */',
+'group-sysop.js' => '/* Any JavaScript here will be loaded for sysops only */',
+'group-bureaucrat.js' => '/* Any JavaScript here will be loaded for bureaucrats only */',
+
# Metadata
'notacceptable' => 'wiki مۇلازىمىتىرى سىزنىڭ خېرىدار تەرىپىڭىز ئوقۇيالايدىغان سانلىق مەلۇمات فورماتى بىلەن تەمىنلىيەلمەيدۇ.',
# Info page
'pageinfo-title' => '"$1" نىڭ ئۇچۇرى',
-'pageinfo-header-edits' => 'تەھرىر',
+'pageinfo-header-basic' => 'ئاساسىي ئۇچۇر',
+'pageinfo-header-edits' => 'تەھرىر خاتىرىسى',
+'pageinfo-header-restrictions' => 'بەت قوغداش',
+'pageinfo-header-properties' => 'بەت خاراكتېرى',
+'pageinfo-display-title' => 'كۆرسىتىدغان نام',
+'pageinfo-article-id' => 'بەت ID',
'pageinfo-views' => 'كۆرۈنۈش سانى',
-'pageinfo-watchers' => 'كۆزەتكۈچىلەر سانى',
+'pageinfo-watchers' => 'بەت كۆزەتكۈچىلەر سانى',
+'pageinfo-redirects-value' => '$1',
'pageinfo-edits' => 'تەھرىر سانى',
'pageinfo-authors' => 'يازغۇچىلار سانى',
+# Skin names
+'skinname-standard' => 'Classic',
+'skinname-nostalgia' => 'Nostalgia',
+'skinname-cologneblue' => 'Cologne Blue',
+'skinname-monobook' => 'MonoBook',
+'skinname-myskin' => 'MySkin',
+'skinname-chick' => 'Chick',
+'skinname-simple' => 'Simple',
+'skinname-modern' => 'Modern',
+'skinname-vector' => 'Vector',
+
# Patrolling
'markaspatrolleddiff' => 'چارلاش بەلگىسى قوي',
'markaspatrolledtext' => 'بۇ بەتكە چارلاش بەلگىسى قوي',
'mediawarning' => "'''ئاگاھلاندۇرۇش''': بۇ ھۆججەتتە زەھەرخەندە كود بولۇشى مۇمكىن، ئۇنى ئىجرا قىلسىڭىز سىستېمىڭىزغا خەۋپ ئېلىپ كېلىشى مۇمكىن.",
'imagemaxsize' => "سۈرەت چوڭلۇق چەكلىمىسى: <br />''(ھۆججەت چۈشەندۈرۈش بېتى ئۈچۈن)''",
'thumbsize' => 'كىچىك سۈرەت چوڭلۇقى:',
+'widthheight' => '$1 × $2',
'widthheightpage' => '$1 × $2, $3 {{PLURAL:$3|بەت|بەت}}',
'file-info' => 'ھۆججەت چوڭلۇقى: $1, MIME تىپى: $2',
'file-info-size' => '$1 × $2 پىكسېل، ھۆججەت چوڭلۇقى: $3، MIME تىپى: $4',
'bydate' => 'چېسلا بويىچە',
'sp-newimages-showfrom' => '$2 دىن $1 باشلاپ يېڭى ھۆججەت كۆرسىتىۋاتىدۇ',
+# Video information, used by Language::formatTimePeriod() to format lengths in the above messages
+'video-dims' => '$1, $2 × $3',
+'seconds-abbrev' => '$1s',
+'minutes-abbrev' => '$1m',
+'hours-abbrev' => '$1h',
+'days-abbrev' => '$1d',
+
# Bad image list
'bad_image_list' => 'تۆۋەندىكى فورماتتا يېزىڭ:
بۇ ھۆججەت قايسى بەتلەردە كۆرسىتىلىشىدىن قەتئىي نەزەر،
ئوخشاش بىر قۇرنىڭ ئاخىرىدىكى ئۇلانما مۇستەسنا دەپ قارىلىدۇ،',
+/*
+Short names for language variants used for language conversion links.
+To disable showing a particular link, set it to 'disable', e.g.
+'variantname-zh-sg' => 'disable',
+Variants for Chinese language
+*/
+'variantname-zh-hans' => 'hans',
+'variantname-zh-hant' => 'hant',
+'variantname-zh-cn' => 'cn',
+'variantname-zh-tw' => 'tw',
+'variantname-zh-hk' => 'hk',
+'variantname-zh-mo' => 'mo',
+'variantname-zh-sg' => 'sg',
+'variantname-zh-my' => 'my',
+'variantname-zh' => 'zh',
+
+# Variants for Gan language
+'variantname-gan-hans' => 'hans',
+'variantname-gan-hant' => 'hant',
+'variantname-gan' => 'gan',
+
+# Variants for Serbian language
+'variantname-sr-ec' => 'sr-ec',
+'variantname-sr-el' => 'sr-el',
+'variantname-sr' => 'sr',
+
+# Variants for Kazakh language
+'variantname-kk-kz' => 'kk-kz',
+'variantname-kk-tr' => 'kk-tr',
+'variantname-kk-cn' => 'kk-cn',
+'variantname-kk-cyrl' => 'kk-cyrl',
+'variantname-kk-arab' => 'kk-arab',
+'variantname-kk' => 'kk',
+
# Metadata
'metadata' => 'مېتا سانلىق مەلۇماتى',
'metadata-help' => 'بۇ ھۆججەت كېڭەيتىلگەن تەپسىلاتنى ئۆز ئىچىگە ئالغان. بۇ ئۇچۇرلارنى رەقەملىك ئاپپارات ياكى سكاننېر قۇرغان ياكى رەقەملەشتۈرۈش جەريانىدا قوشۇلغان بولۇشى مۇمكىن.
'qbbrowse' => 'Переглянути',
'qbedit' => 'Редагувати',
'qbpageoptions' => 'Налаштування сторінки',
-'qbpageinfo' => 'Інформація про сторінку',
'qbmyoptions' => 'Мої налаштування',
'qbspecialpages' => 'Спеціальні сторінки',
'faq' => 'Часті питання',
'sp-contributions-newbies' => 'Показати лише внесок з нових облікових записів',
'sp-contributions-newbies-sub' => 'Внесок новачків',
'sp-contributions-newbies-title' => 'Внесок з нових облікових записів',
-'sp-contributions-blocklog' => 'пÑ\80оÑ\82окол блокувань',
+'sp-contributions-blocklog' => 'жÑ\83Ñ\80нал блокувань',
'sp-contributions-deleted' => 'вилучені редагування користувача',
'sp-contributions-uploads' => 'завантаження',
'sp-contributions-logs' => 'журнали',
'qbbrowse' => "Ko'rish",
'qbedit' => 'Tahrirlash',
'qbpageoptions' => 'Ushbu sahifa',
-'qbpageinfo' => "Sahifa haqida ma'lumot",
'qbmyoptions' => 'Mening sahifalarim',
'qbspecialpages' => 'Maxsus sahifalar',
'faq' => 'TSS',
'listgrouprights-members' => '(a’zolar ro‘yxati)',
# E-mail user
-'emailuser' => 'Bu foydalanuvchiga e-maktub',
+'emailuser' => 'Foydalanuvchiga maktub',
+'defemailsubject' => '{{SITENAME}} — $1 tomonidan maktub',
'noemailtitle' => 'Elektron pochta manzili mavjud emas',
'noemailtext' => "Bu foydalanuvchi e-mail manzil ko'rsatgani yo'q.",
'emailtarget' => 'Oluvchi ishtirokchining ismini kiriting',
'emailsubject' => 'Sarlavha:',
'emailmessage' => 'Xabar',
'emailsend' => 'Joʻnatish',
+'emailccsubject' => '$1ga maktubingizning nusxasi: $2',
'emailsent' => "Xat jo'natildi",
+'emailsenttext' => "Sizning elektron maktubingiz jo'natildi.",
# User Messenger
'usermessage-summary' => 'Tizimli xabar qoldirish.',
# New logging system
'logentry-move-move' => '$1 $3 sahifasini $4ga koʻchirdi',
+'logentry-patrol-patrol-auto' => '$1 $3 sahifasining $4 versiyasini avtomatik patrulladi',
'logentry-newusers-newusers' => '$1 ishtirokchisining hisob yozuvi yaratildi',
'logentry-newusers-create' => '$1 ishtirokchisining hisob yozuvi yaratildi',
'qbbrowse' => 'Sfoja',
'qbedit' => 'Canbia',
'qbpageoptions' => 'Opsion pajina',
-'qbpageinfo' => 'Informasion so ła pajina',
'qbmyoptions' => 'Łe me pajine',
'qbspecialpages' => 'Pagine speciali',
'faq' => 'Domande frequenti',
'qbbrowse' => 'Kacelta',
'qbedit' => 'Redaktiruida',
'qbpageoptions' => 'Necen lehtpolen järgendused',
-'qbpageinfo' => 'Andmused lehtpoles',
'qbmyoptions' => 'Minun järgendused',
'qbspecialpages' => 'Specialižed lehtpoled',
'faq' => 'PPK',
'qbbrowse' => 'Duyệt',
'qbedit' => 'Sửa đổi',
'qbpageoptions' => 'Trang này',
-'qbpageinfo' => 'Ngữ cảnh',
'qbmyoptions' => 'Trang cá nhân',
'qbspecialpages' => 'Trang đặc biệt',
'faq' => 'Câu hỏi thường gặp',
'vector-action-protect' => 'Khóa',
'vector-action-undelete' => 'Phục hồi',
'vector-action-unprotect' => 'Đổi mức khóa',
-'vector-simplesearch-preference' => 'Gợi ý tìm kiếm nâng cao (cần bề ngoài Vectơ)',
+'vector-simplesearch-preference' => 'Hộp tìm kiếm đơn giản (cần bề ngoài Vectơ)',
'vector-view-create' => 'Tạo',
'vector-view-edit' => 'Sửa',
'vector-view-history' => 'Xem lịch sử',
'edit-already-exists' => 'Không thể tạo trang mới.
Nó đã tồn tại.',
'defaultmessagetext' => 'Nội dung mặc định',
+'content-failed-to-parse' => 'Thất bại phân tích nội dung $2 cho mô hình $1: $3',
+'invalid-content-data' => 'Dữ liệu nội dung không hợp lệ',
+'content-not-allowed-here' => 'Không cho phép đưa nội dung “$1” vào trang [[$2]]',
+
+# Content models
+'content-model-wikitext' => 'mã wiki',
+'content-model-text' => 'văn bản thuần',
+'content-model-javascript' => 'JavaScript',
+'content-model-css' => 'CSS',
# Parser/template warnings
'expensive-parserfunction-warning' => 'Cảnh báo: Trang này có quá nhiều lần gọi hàm cú pháp cần mức độ xử lý cao.
'undeletedrevisions' => '$1 {{PLURAL:$1|bản|bản}} được phục hồi',
'undeletedrevisions-files' => '$1 {{PLURAL:$1|bản|bản}} và $2 {{PLURAL:$2|tập tin|tập tin}} đã được phục hồi',
'undeletedfiles' => '$1 {{PLURAL:$1|tập tin|tập tin}} đã được phục hồi',
-'cannotundelete' => 'Phục hồi thất bại;
-một người nào khác đã phục hồi trang này rồi.',
+'cannotundelete' => 'Phục hồi thất bại:
+$1',
'undeletedpage' => "'''$1 đã được khôi phục'''
Xem nhật trình xóa và phục hồi các trang gần đây tại [[Special:Log/delete|nhật trình xóa]].",
'immobile-target-namespace-iw' => 'Không cho phép di chuyển trang đến một liên kết liên wiki.',
'immobile-source-page' => 'Bạn không thể di chuyển trang này.',
'immobile-target-page' => 'Không thể di chuyển đến tựa đề đích.',
+'bad-target-model' => 'Trang đích sử dụng mô hình nội dung khác. Không thể chuyển đổi nội dung từ $1 đến $2.',
'imagenocrossnamespace' => 'Không thể di chuyển tập tin ra khỏi không gian tên Tập tin',
'nonfile-cannot-move-to-file' => 'Không thể di chuyển những gì không phải là tập tin vào không gian tên Tập tin',
'imagetypemismatch' => 'Phần mở rộng trong tên tập tin mới không hợp dạng của tập tin',
# Info page
'pageinfo-title' => 'Thông tin về “$1”',
+'pageinfo-not-current' => 'Thông tin được hiển thị có thể chỉ có liên quan đến phiên bản hiện hành.',
'pageinfo-header-basic' => 'Thông tin cơ bản',
'pageinfo-header-edits' => 'Lịch sử sửa đổi',
'pageinfo-header-restrictions' => 'Mức khóa trang',
'pageinfo-magic-words' => 'Từ thần chú ($1)',
'pageinfo-hidden-categories' => 'Thể loại ẩn ($1)',
'pageinfo-templates' => 'Bản mẫu được nhúng ($1)',
+'pageinfo-toolboxlink' => 'Thông tin trang',
# Skin names
'skinname-standard' => 'Cổ điển',
# Scary transclusion
'scarytranscludedisabled' => '[Nhúng giữa các wiki bị tắt]',
-'scarytranscludefailed' => '[Truy xuất bản mẫu cho $1 thất bại]',
+'scarytranscludefailed' => '[Truy xuất bản mẫu $1 bị thất bại]',
+'scarytranscludefailed-httpstatus' => '[Truy xuất bản mẫu $1 bị thất bại: HTTP $2]',
'scarytranscludetoolong' => '[Địa chỉ URL quá dài]',
# Delete conflict
'qbbrowse' => 'Padön',
'qbedit' => 'Redakön',
'qbpageoptions' => 'Pad at',
-'qbpageinfo' => 'Yumed',
'qbmyoptions' => 'Pads obik',
'qbspecialpages' => 'Pads patik',
'faq' => 'Säks suvo pasäköls',
# Groups
'group-user' => 'Сäüttijäd',
'group-sysop' => 'Praviťeľad',
-'group-all' => '{kõik)',
+'group-all' => '(kõik)',
'group-user-member' => 'cäüttijä',
'qbbrowse' => 'Kaeq',
'qbedit' => 'Toimõndaq',
'qbpageoptions' => 'Leheküle säädmine',
-'qbpageinfo' => 'Leheküle teedüs',
'qbmyoptions' => 'Mu säädmiseq',
'qbspecialpages' => 'Tallitusleheküleq',
'faq' => 'Sagõhõhe küsüdüq küsümiseq',
'qbbrowse' => 'Foyter',
'qbedit' => 'Candjî',
'qbpageoptions' => 'Cisse pådje ci',
-'qbpageinfo' => 'Contecse',
'qbmyoptions' => 'Mes pådjes',
'qbspecialpages' => 'Pådjes sipeciåles',
'qbbrowse' => 'Igdalikyat',
'qbedit' => 'Igliwat',
'qbpageoptions' => 'Ini nga pakli',
-'qbpageinfo' => 'Kontexto',
'qbmyoptions' => 'Akon mga pakli',
'qbspecialpages' => 'Mga pinaurog nga pakli',
'faq' => 'AGG',
'youhavenewmessages' => 'Mayda ka $1 ($2).',
'newmessageslink' => 'bag-o nga mga mensahe',
'newmessagesdifflink' => 'kataposan nga pagbag-o',
+'youhavenewmessagesfromusers' => 'May-ada ka $1 tikang ha {{PLURAL:$3|iba nga gumaramit|$3 mga gumaramit}} ($2).',
+'youhavenewmessagesmanyusers' => 'May-ada ka $1 tikang ha damo nga mga gumaramit ($2).',
+'newmessageslinkplural' => '{{PLURAL:$1|uska bag-o nga mensahe|bag-o nga mga mensahe}}',
+'newmessagesdifflinkplural' => '$1 {{PLURAL:$1|nga pagbag-o|nga mga pagbag-o}}',
'youhavenewmessagesmulti' => 'Mayda ka mga bag-o nga mensahe ha $1',
'editsection' => 'igliwat',
'editsection-brackets' => '[$1]',
'page-rss-feed' => '"$1" RSS nga feed',
'page-atom-feed' => '"$1" Atom nga feed',
'red-link-title' => '$1 (waray dida ini nga pakli)',
+'sort-descending' => 'Igpasunodsunod paubos',
+'sort-ascending' => 'Igpasunodsunod paigbaw',
# Short words for each namespace, by default used in the namespace tab in monobook
'nstab-main' => 'Pakli',
'directorycreateerror' => 'Waray makahimo han direktoryo nga "$1".',
'filenotfound' => 'Diri nabibilngan an paypay nga "$1"',
'fileexistserror' => "Diri nasusuratan ha paypay nga ''$1'': Aada na an paypay.",
+'unexpected' => 'Diri ginlalauman nga balor: "$1"="$2".',
'formerror' => 'Sayop: Diri nasusumite an porma.',
'badarticleerror' => 'Ini nga pagbuhat diri mahihimo dinhi nga pakli',
'cannotdelete' => 'An pakli o an fayl nga "$1" diri napapara.
Bangin na ini ginpara hin iba.',
+'cannotdelete-title' => 'diri nakakapara han pakli "$1"',
+'delete-hook-aborted' => 'Pagpara ginpugngan han kawil. Waray eksplenasyon an ginhatag.',
'badtitle' => 'Maraot nga titulo',
'badtitletext' => 'An ginhangyo nga pakli diri puyde, waray sulod, o sayop nga nasumpay nga inter-pinunongan o inter-wiki nga titulo.
Bangin mayda usa o damo nga mga agi nga diri puyde magamit ha mga titulo.',
Funsyon: $1<br />
Kweri: $2',
'viewsource' => 'Kitaa an ginkuhaan',
+'viewsource-title' => 'Kitaa an tinikangan para han $1',
+'actionthrottled' => 'Ginpahinay an ginbuhat',
+'actionthrottledtext' => 'Komo uska pangontra ha spam, ikaw in ginlilimitaran paghimo hini nga pagbuhat hin sobra kadamo ha sulod hin gutiay nga oras, ngan ikaw in naglapos hini nga katubtuban.
+Alayon pagutro kahuman hin pipira ka mga minuto.',
+'protectedpagetext' => 'Ini nga pakli in pinasaliporan para mapugngan an mga pagliwat.',
'viewsourcetext' => 'Puydi ka kinmita ngan kinmopya han gintikangan han pakli:',
+'viewyourtext' => "Puydi nim makit-an ngan makopya an tinikangan han '''imo mga pagliwat''' ha dinhi nga pakli:",
+'sqlhidden' => '(nakatago an SQL query)',
'namespaceprotected' => "Diri ka gintutugutan pagliwat han mga pakli ha ngaran-lat'ang nga '''$1'''.",
+'customcssprotected' => 'Diri ka gintutugotan pagliwat hini nga CSS nga pakli, tungod nga nagsusulod ini hin kanan iba nga tawo personal nga karuyagon.',
+'customjsprotected' => 'Diri ka gintutugotan pagliwat hini nga JavaScript nga pakli, tungod nga nagsusulod ini hin kanan iba nga tawo personal nga karuyagon.',
'ns-specialprotected' => 'Diri maliliwat an mga ispisyal nga pakli.',
'titleprotected' => 'Ini nga titulo pinasalipod ha paghimo ni [[User:$1|$1]].
An katadungan nga ginhatag amo in "\'\'$2\'\'".',
+'filereadonlyerror' => 'Diri maliliwat ini nga paypay "$1" tungod an ginsusudlan han paypay nga "$2" in aada la ha pagbasa-la nga kahimtang.
+
+An magdudurmara nga nagtrangka hini in naghatag hini nga eksplenasyon: "$3".',
+'exception-nologin' => 'Diri nakalog-in',
+'exception-nologin-text' => 'Ini nga pakli o pagbuhat in nagkikinahanglan nga ikaw in mag-log-in ha dinhi nga wiki.',
# Virus scanner
'virus-unknownscanner' => 'diri-nasasabtan nga antivirus:',
'yourpassword' => 'Tigaman-pagsulod:',
'yourpasswordagain' => 'Utroha pagbutang an tigaman-han-pagsakob:',
'remembermypassword' => "Hinumdumi an akon pan-sakob dinhi nga panngaykay ''(browser)'' (para ha pinakamaiha $1 {{PLURAL:$1|ka adlaw|ka mga adlaw}})",
+'securelogin-stick-https' => 'Nagpapabilin nga masumpay ha HTTPS kahuman makalog-in',
'yourdomainname' => 'Imo dominyo:',
+'password-change-forbidden' => 'Diri ka makakabalyo hin pulong-pagsulod ha dinhi nga wiki.',
'login' => 'Sakob',
'nav-login-createaccount' => 'Magpalista nga masakob / paghimo hin bag-o nga akawnt',
'loginprompt' => "Kinahanglan mo hin mga kuki (''cookie'') para makapag log-in ha {{SITENAME}}.",
Alayon pagpili hin lain nga ngaran.',
'loginerror' => 'Sayop hin pagsakob',
'createaccounterror' => 'Diri makakahimo hin akawnt: $1',
+'nocookieslogin' => '{{SITENAME}} in nagkikinahanglan hin mga kuki para makapagpalog-in hin mga gumaramit. An im mga kuki in diri nagana.
+Alayon paganaha hira ngan utro liwat.',
'loginsuccesstitle' => 'Malinamposon an pagsulod',
+'loginsuccess' => "'''Ikaw in nakalog-in ha {{SITENAME}} komo \"\$1\".'''",
+'nosuchuser' => 'Waray gumaramit an may-ada ngaran nga "$1".
+It mga agnay-hi-gumaramit in case sensitive.
+Panginano-a it imo pagbaybay, o [[Special:UserLogin/signup|paghimo hin bag-o nga akawnt]].',
'nosuchusershort' => 'Waray nagamit it may ngaran nga "$1".
Kitaa kun amo it im pagbaybay.',
'nouserspecified' => 'Dapat nim magbutang hin agnay hit gumaramit.',
Alayon pagutro pagbutang.',
'passwordtooshort' => 'An tigaman-pagsulod dapat diri maubos hit {{PLURAL:$1|1 nga agi|$1 nga agi}}.',
'password-name-match' => 'An imo tigaman-pagsulod in kinahanglan iba ha imo agnay-hiton-gumaramit.',
+'password-login-forbidden' => 'An paggamit hini nga agnay-hit-gumaramit ngan tigaman-pagsulod in diri gintutugotan.',
'mailmypassword' => 'Ig-e-mail an bag-o nga tigaman-pagsulod',
'passwordremindertitle' => 'Bag-o nga diri-pirmihan nga tigaman-pagsulod para han {{SITENAME}}',
'noemail' => 'Waray e-mail nga adres nga ginrekord para han nágámit "$1".',
'mailerror' => 'Sayop han pagpadangat hin surat: $1',
'emailauthenticated' => 'Ginpamatuod an imo e-mail adres han $2 ha $3.',
'emailconfirmlink' => 'Igkompirma an imo e-mail address',
+'emaildisabled' => 'Ini nga sityo in diri nakakapadangat hin mga e-mail.',
'accountcreated' => 'Nahimo an akawnt',
'accountcreatedtext' => 'An akwant han gumaramit para kan $1 in ginhimo.',
'createaccount-title' => 'Paghimo hin akawnt para han {{SITENAME}}',
+'usernamehasherror' => 'Agnay-hin-gumaramit in diri puydi magkamay-ada hin mga hash karakter',
+'login-abort-generic' => 'An imo paglog-in in diri malinamposon - Naundang',
'loginlanguagelabel' => 'Pinulongan: $1',
# Change password dialog
'newpassword' => 'Bag-o nga tigaman-pagsulod:',
'retypenew' => 'Utroha pagbutang an bag-o nga tigaman-pagsulod:',
'resetpass_forbidden' => 'Diri mababalyoan an mga tigaman-pagsulod',
+'resetpass-no-info' => 'Kinahanglan mo paglog-in para direkta ka makasakob dinhi nga pakli.',
'resetpass-submit-loggedin' => 'Igbal-iw an tigaman-pagsulod',
'resetpass-submit-cancel' => 'Pasagdi',
'resetpass-temp-password' => 'Temporaryo nga tigaman-pagsakob:',
# Special:PasswordReset
'passwordreset-username' => 'Agnay hiton gumaramit:',
'passwordreset-domain' => 'Dominyo:',
+'passwordreset-capture' => 'Kikitaon mo an resulta nga e-mail?',
'passwordreset-email' => 'E-mail adres:',
'passwordreset-emailtitle' => 'Mga detalye han akawnt ha {{SITENAME}}',
'passwordreset-emailelement' => 'Agnay han gumaramit: $1
'passwordreset-emailsent' => 'Ginpadara hin usa ka pahinumdom nga e-mail.',
# Special:ChangeEmail
+'changeemail' => 'Igliwan an e-mail address',
+'changeemail-header' => 'Igliwan an e-mail address akawnt',
+'changeemail-text' => 'Igkompleto ini nga porma para makapagliwan han imo e-mail address. Kinahanglanon mo igbutang an imo tigaman-pagsulod para makompirma ini nga pagbag-o.',
+'changeemail-no-info' => 'Kinahanglanon mo mag-log-in para ka direkta makasakob hini nga pakli.',
+'changeemail-oldemail' => 'Yana nga e-mail address:',
+'changeemail-newemail' => 'Bag-o nga e-mail address:',
+'changeemail-none' => '(waray)',
+'changeemail-submit' => 'Igbalyo an e-mail',
'changeemail-cancel' => 'Pasagdi',
# Edit page toolbar
'note' => "'''Pahibaro:'''",
'previewnote' => "'''Hinumdumi nga pahiuna-nga-paggawas pa la ini.'''
¡Waray pa katipig an imo mga ginbag-o!",
+'continue-editing' => 'Padayon pagliwat',
'editing' => 'Ginliliwat an $1',
'creating' => 'Ginhihimo an $1',
'editingsection' => 'Ginliliwat an $1 (bahin)',
'edit-no-change' => 'Ginpabay-an an im pagliwat, mahitungod nga waray pagbalyo nga nabuhat ha nakasurat.',
'edit-already-exists' => 'Diri nakakahimo hin bag-o nga pakli.
Aada na ito.',
+'defaultmessagetext' => 'Aada-nga-daan nga teksto han mensahe',
+
+# Content models
+'content-model-wikitext' => 'wikiteksto',
+'content-model-text' => 'yano nga teksto',
+'content-model-javascript' => 'JavaScript',
+'content-model-css' => 'CSS',
# Parser/template warnings
'post-expand-template-inclusion-warning' => "'''Pahimatngon:''' An batakan nga ginlakip in sobra kadako.
'history-feed-item-nocomment' => '$1 ha $2',
# Revision deletion
+'rev-deleted-comment' => '(gintanggal an kaagi han dalikyat nga sumat)',
'rev-deleted-user' => '(gintanggal an agnay hiton gumaramit)',
+'rev-deleted-event' => '(gintanggal an talaan han mga buhat)',
+'rev-deleted-user-contribs' => '[gintanggal an agnay-hit-gumaramit o IP address - an pagliwat in gintago tikang han mga amot]',
+'rev-suppressed-no-diff' => "Diri mo makikita ini nga kaibhan tungod nga usa ha mga rebisyon in '''ginpara'''.",
'rev-delundel' => 'igpakita/igtago',
'rev-showdeleted' => 'igpakita',
'revdelete-show-file-confirm' => 'Sigurado ka nga gusto mo makita an ginpara nga pagliwat han file "<nowiki>$1</nowiki>" tikang $2 ha $3?',
'revdel-restore' => 'igliwat an nakikit-an',
'revdel-restore-deleted' => 'napara nga mga pagbag-o',
'revdel-restore-visible' => 'Mga nakikit-an nga pagbabag-o',
+'pagehist' => 'Kaagi han pakli',
+'deletedhist' => 'Ginpara nga kaagi',
+'revdelete-hide-current' => 'Sayop in natago ha butang nga may petsa $2, $1: Ini an yana nga rebisyon.
+Diri ini matatago.',
+'revdelete-show-no-access' => 'May-ada sayo nga nagpapakita ha butang nga may petsa $2, $1. Ini nga butang in nakamarka nga "diri malalabtan".
+Diri mo ini malalabtan.',
'revdelete-otherreason' => 'Lain/dugang nga katadungan:',
'revdelete-reasonotherlist' => 'Lain nga katadongan',
'revdelete-edit-reasonlist' => 'Igliwat an mga katadungan han pagpara',
'search-suggest' => 'Buot sidngon mo ba: $1',
'search-interwiki-caption' => 'Mga bugto nga proyekto',
'search-interwiki-default' => '$1 nga resulta:',
+'search-relatedarticle' => 'kasumapy',
'searchrelated' => 'kadugtong',
'searchall' => 'ngatanan',
'showingresultsheader' => "{{PLURAL:$5|Resulta '''$1''' han '''$3'''|Mga resulta '''$1 - $2''' han '''$3'''}} para ha '''$4'''",
'powersearch' => 'Abansado nga pagbiling',
'powersearch-legend' => 'Abansado nga pagbiling',
'powersearch-field' => 'Bilnga an',
+'powersearch-togglelabel' => 'Panginano-a:',
'powersearch-toggleall' => 'Ngatanan',
'powersearch-togglenone' => 'Waray',
'search-external' => 'Gawas nga pamiling',
+'searchdisabled' => '{{SITENAME}} nga pamiling in ginparong.
+Pamilnga la anay pinaagi ha Google ha pagkayana.
+Ginpapasabot nga an sulod han mga panudlok han {{SITENAME}} in bangin daan an.',
# Quickbar
+'qbsettings' => 'Quickbar',
'qbsettings-none' => 'Waray',
'qbsettings-fixedleft' => 'Ginayad an wala',
'qbsettings-fixedright' => 'Gin-ayad an to-o',
'prefs-rc' => 'Kalalabay la nga mga pagbabag-o',
'prefs-watchlist-days' => 'Mga adlaw nga makikita ha barantayan:',
'prefs-resetpass' => 'Igliwan an tigaman-pagsulod',
+'prefs-rendering' => 'Hitsura',
+'saveprefs' => 'Igtipig',
+'resetprefs' => 'Pabay-i an diri nakatipig nga mga pagbabag-o',
+'restoreprefs' => 'Igbalik ngatanan ngada ha kahimtang nga aada-nga-daan',
'prefs-editing' => 'Ginliliwat',
+'prefs-edit-boxsize' => 'Kadako han bintana han pagliwat.',
'rows' => 'Mga rumbay pahigda:',
'columns' => 'Mga rumbay patindog:',
'searchresultshead' => 'Bilnga',
'savedprefs' => 'Gintipig an im karuyag.',
'timezonelegend' => 'Zona hin oras',
'localtime' => 'Oras nga lokal',
+'servertime' => 'Oras han serbidor:',
+'guesstimezone' => 'Butanga tikang han panngaykay(browser)',
'timezoneregion-africa' => 'Aprika',
'timezoneregion-america' => 'Amerika',
'timezoneregion-antarctica' => 'Antarktika',
'timezoneregion-europe' => 'Europa',
'timezoneregion-indian' => 'Kalawdan Indyana',
'timezoneregion-pacific' => 'Kalawdan Pasipiko',
-'prefs-searchoptions' => 'Mga pagpipilian han pamiling',
+'prefs-searchoptions' => 'Pamilnga',
'prefs-namespaces' => "Ngaran-lat'ang",
+'default' => 'aada-nga-daan',
+'prefs-files' => 'Mga paypay',
'youremail' => 'E-mail:',
'username' => 'Agnay hiton gumaramit:',
'uid' => 'ID han gumaramit:',
+'prefs-memberingroups' => 'Api han {{PLURAL:$1| nga hugpo|nga mga hugpo}}:',
+'prefs-registration' => 'Oras han pagrehistro:',
'yourrealname' => 'Tinuod nga ngaran:',
'yourlanguage' => 'Yinaknan:',
'yournick' => 'Bag-o nga pirma:',
'badsiglength' => 'Hilaba hin duro it im pirma.
Dapat diri malabaw ha $1 {{PLURAL:$1|agi|mga agi}} nga kahilaba.',
+'yourgender' => 'Henero:',
+'gender-unknown' => 'Waray ginpasabot',
'gender-male' => 'Lalaki',
'gender-female' => 'Babaye',
'email' => 'E-mail',
An imo e-mail address in diri makikit-an kun an iba nga mga gumaramit in makontak ha imo.',
'prefs-help-email-required' => 'Kinahanglanon it e-mail address.',
'prefs-info' => 'Panguna nga pananabotan',
+'prefs-i18n' => 'Internasyonalisasyon',
'prefs-signature' => 'Pirma',
+'prefs-advancedediting' => 'Abansado nga mga pagpipilian',
+'prefs-advancedrc' => 'Abansado nga mga pagpipilian',
+'prefs-advancedrendering' => 'Abansado nga mga pagpipilian',
+'prefs-advancedsearchoptions' => 'Abansado nga mga pagpipilian',
+'prefs-advancedwatchlist' => 'Abansado nga mga pagpipilian',
'prefs-diffs' => 'Mga kaibhan',
+# User preference: e-mail validation using jQuery
+'email-address-validity-valid' => 'E-mail address in baga puydi',
+
# User rights
+'userrights' => 'Pagdudumara hin mga katungod han gumaramit',
+'userrights-lookup-user' => 'Pagdumaraa han mga hugpo han gumaramit',
+'userrights-user-editname' => 'Igbutang an agnay han gumaramit:',
+'editusergroup' => 'Igliwat han mga hugpo han gumaramit',
+'editinguser' => "Igliliwat an mga katungod han gumaramit han gumaramit '''[[Gumaramit:$1|$1]]''' $2",
+'userrights-editusergroup' => 'Igliwat an mga hugpo hin gumaramit',
+'saveusergroups' => 'Igtipig an mga hugpo han gumaramit',
'userrights-groupsmember' => 'Api han:',
'userrights-reason' => 'Katadungan:',
+'userrights-no-interwiki' => '
+Diri ka gintutugotan pagliwat han mga katungod han gumaramit ha iba nga mga wiki.',
+'userrights-nodatabase' => 'Waray kaaagii an Database $1 o diri ini aada ha lokal.',
+'userrights-changeable-col' => 'Mga hugpo nga puydi mo labtan',
+'userrights-unchangeable-col' => 'Mga hugpo nga diri mo puydi labtan',
# Groups
'group' => 'Hugpo:',
'group-user' => 'Mga gumaramit',
+'group-autoconfirmed' => 'Mga gumaramit nga lugaring nakokonpirma',
'group-bot' => 'Mga bot',
'group-sysop' => 'Mga magdudumara',
'group-bureaucrat' => 'Mga burokrata',
'group-user-member' => '{{HENERO:$1|gumaramit}}',
'group-bot-member' => 'bot',
'group-sysop-member' => 'magdudumara',
+'group-bureaucrat-member' => '{{GENDER:$1|burokrata}}',
'grouppage-user' => '{{ns:project}}:Mga gumaramit',
+'grouppage-bot' => '{{ns:project}}:Mga bot',
'grouppage-sysop' => '{{ns:project}}:Mga magdudumara',
+'grouppage-bureaucrat' => '{{ns:project}}:Mga burokrata',
'grouppage-suppress' => '{{ns:project}}:Nanginginano',
# Rights
'right-edit' => 'Igliwat an mga pakli',
'right-createpage' => 'Paghimo hin mga pakli (nga diri an mga hiruhimangraw nga mga pakli)',
'right-createtalk' => 'Paghimo hin hiruhimangraw nga mga pakli',
+'right-createaccount' => 'Paghimo hin bag-o nga mga akawnt hin gumaramit',
'right-minoredit' => 'Igmarka an mga ginliwat komo gutiay la',
'right-move' => 'Igbalhin an mga pakli',
+'right-move-subpages' => 'Igbalhin an pakli lakip an ira mga bahinpakli',
+'right-move-rootuserpages' => 'Igbalhin an gamot nga mga pakli han gumaramit',
'right-movefile' => 'Balhina an mga paypay',
+'right-upload' => 'Igkarga paigbaw an mga paypay',
+'right-reupload' => 'Sapawa an mga aada nga mga paypay',
+'right-upload_by_url' => 'Igkarga paigbaw an mga paypay tikang ha uska URL',
+'right-bot' => 'Igtrato komo uska naglulugaring nga proseso',
'right-delete' => 'Igpara an mga pakli',
+'right-bigdelete' => 'Igpara an mga pakli nga may-ada dagko nga mga kaagi',
+'right-browsearchive' => 'Pamiling hin mga ginpara nga mga pakli',
'right-undelete' => 'Igpawara an pagpara han pakli',
+'right-suppressionlog' => 'Kitaa an mga pribado nga mga talaan',
+'right-block' => 'Pugnga an iba nga mga gumaramit ha pagliwat',
+'right-blockemail' => 'Pugnga an uska gumaramit tikang ha pagpadangat hin e-mail',
+'right-hideuser' => 'Pugnga an uska agnay-hin-gumaramit, tago-a ito tikang ha publiko',
# User rights log
'rightsnone' => '(waray)',
'action-createpage' => 'pahimo hin mga pakli',
'action-minoredit' => 'butanga hin tigaman hinin nga pagliwat komo gutiay',
'action-move' => 'balhina ini nga pakli',
+'action-movefile' => 'igbalhin ini nga paypay',
+'action-upload' => 'igkarga-pasaka ini nga paypay',
+'action-reupload' => 'igsapaw ini nga aanhi nga paypay',
'action-delete' => 'paraa ini nga pakli',
'action-deleterevision' => 'igpara ini nga pagbag-o',
# Upload
'upload' => 'Pagkarga hin file',
'uploadbtn' => 'Igkarga an file',
+'uploadnologin' => 'Diri nakalog-in',
'upload-recreate-warning' => "'''Pahimatngon: An fayl nga may-ada hiton nga ngaran in ginpara o ginbalhin.'''
An taramdan han pagpara ngan pagbalhin para hini nga pakli in ginhahatag para han imo kamurayaw:",
'illegal-filename' => 'An ngaran han fayl in diri gintutugutan.',
'unknown-error' => 'Nahitabo an waray kasasabti nga sayop.',
'uploadwarning' => 'Pahimatngon han pagkarga paigbaw',
+'savefile' => 'Igtipig an paypay',
'uploadedimage' => 'ginkarga-paigbaw "[[$1]]"',
+'overwroteimage' => 'Ginkaraga an bag-o nga bersyon han "[[$1]]"',
+'uploaddisabled' => 'Waray ginpagana an pagkarga paigbaw',
+'copyuploaddisabled' => 'An pagkarga paigbaw pinaagi hin URL in waray ginpagana',
+'uploadfromurl-queued' => 'An imo ginkarga-paigbaw hin nakapila.',
+'uploaddisabledtext' => 'An mga pagkarga-paigbaw in diri ginpapagana.',
+'php-uploaddisabledtext' => 'An mga pagkarga-paigbaw han paypay in diri ginpapagana ha PHP.
+Alayon panginanoi an kahimtang han file_uploads.',
+'uploadscripted' => 'Ini nga paypay in nagsusulod hin HTML o script code nga puydi masayopan paghubad an web browser.',
'uploadvirus' => 'Ini nga fayl may-ada sulod nga bayrus!
Mga detalye: $1',
'upload-source' => 'Tinikangan nga fayl',
'sourcefilename' => 'Tinikangan nga ngaran han fayl:',
+'sourceurl' => 'Tinikangan nga URL:',
+'destfilename' => 'Kakadtoan nga ngaran-hin-paypay:',
+'upload-maxfilesize' => 'Pinakadamo nga kadako han paypay: $1',
+'upload-description' => 'Pangilal-an han paypay',
+'upload-options' => 'Mga pirilion han pagkarga paigbaw',
+'watchthisupload' => 'Bantayi ini nga paypay',
+'upload-success-subj' => 'Malinamposan an imo pagkarga-paigbaw.',
+'upload-success-msg' => 'An imo pagkarga-paigbaw tikang ha [$2] in malinamposon. Ini in aada dinhi: [[:{{ns:file}}:$1]]',
+'upload-failure-subj' => 'May-ada problema an pagkarga-paigbaw',
+'upload-failure-msg' => 'May-ada problema an imo pagkarga-paigbaw tikang ha [$2]:
+
+$1',
'upload-warning-subj' => 'Pahimatngon han pagkarga paigbaw',
+'upload-proto-error' => 'Sayop nga protocol',
+'upload-file-error' => 'Sayop ha sulod',
'upload-unknown-size' => 'Waray kasabti an kadako',
+# File backend
+'backend-fail-delete' => 'Diri nakakapara han paypay nga "$1".',
+'backend-fail-alreadyexists' => 'May-ada na paypay nga "$1".',
+'backend-fail-store' => 'Diri nakakatipig han paypay "$1" ha "$".',
+'backend-fail-copy' => 'Diri nakakakopya han paypay nga "$1" ngada "$2".',
+'backend-fail-move' => 'Diri nakakabalhin han paypay "$1" ngada "$2".',
+'backend-fail-opentemp' => 'Diri nakakaabre han temporaryo nga paypay.',
+'backend-fail-writetemp' => 'Diri nakakasurat ngada ha temporaryo nga paypay.',
+'backend-fail-closetemp' => 'Diri nasasara an temporaryo nga paypay.',
+'backend-fail-read' => 'Diri nababasahan han paypay nga "$1".',
+'backend-fail-create' => 'Diri nasusuratan an paypay nga "$1".',
+'backend-fail-maxsize' => 'Diri nasusuratan an paypay nga "$1" tungod nga mas dako ini kaysa hin {{PLURAL:"$2|usa nga byte|$2 nga mga byte}}.',
+'backend-fail-readonly' => 'An panluyo nga tiripigan nga "$1" in ha pagkayana in panbasa-la. An rason nga ginhatag in: "\'\'$2\'\'"',
+
# img_auth script messages
'img-auth-accessdenied' => 'Diri gintutugutan makasulod',
'restriction-move' => 'Balhina',
'restriction-create' => 'Himo-a',
+# Restriction levels
+'restriction-level-all' => 'bisan ano nga katupngan',
+
# Undelete
+'undelete' => 'Igpakita an mga ginpara nga mga pakli',
'undeletelink' => 'igpakita/igbalik',
'undeleteviewlink' => 'kitaa',
'undeletecomment' => 'Katadungan:',
'blockip' => 'Pugngi an gumaramit',
'blockip-title' => 'Pugngi an gumaramit',
'blockip-legend' => 'Pugngi an gumaramit',
+'ipbreason' => 'Katadungan:',
+'ipbreasonotherlist' => 'Lain nga katadungan',
'ipbreason-dropdown' => '*Agsob nga mga rason hit pagpugong
** Pagsusuksok hin sayop nga pananabutan
** Pagtatangtang hin sulod tikang ha mga pakli
'ipbotherreason' => 'Lain/dugang nga katadungan:',
'blockipsuccesssub' => 'Malinamposon an pagpugong',
'ipblocklist' => 'Mga ginpugngan nga gumaramit',
+'blocklist-target' => 'Gin-iigo',
+'blocklist-expiry' => 'Napan-os',
+'blocklist-by' => 'Ginpupugngan an admin',
+'blocklist-reason' => 'Rason:',
'ipblocklist-submit' => 'Bilnga',
+'ipblocklist-localblock' => 'Lokal nga pagpugong',
'blocklink' => 'igpugong',
'unblocklink' => 'igtanggal an pagpugong',
'change-blocklink' => 'igliwan an papugong',
'blocklogpage' => 'Talaan han pagpugong',
'blocklogentry' => 'ginpugngan hi [[$1]] nga natatapos ha takna hin $2 $3',
'block-log-flags-nocreate' => 'diri gintutugutan an paghimo hin akawnt',
+'blockme' => 'Pugngi ako',
'proxyblocksuccess' => 'Human na.',
# Developer tools
# Move page
'move-page' => 'Mabalhin an $1',
+'move-page-legend' => 'Balhina an pakli:',
'movearticle' => 'Balhina an pakli:',
'moveuserpage-warning' => "'''Pahimatngon:''' Tibalhin ka hin pakli hin gumaramit. Alayon pagtigaman nga an pakli là an mababalhin ngan an gumaramit in ''diri'' mababalyoan hin ngaran.",
'newtitle' => 'Para ha bag-o nga titulo:',
'export-addcattext' => 'Igdugang an mga pakli tikang ha kaarangay:',
'export-addcat' => 'Dugngi',
'export-addnstext' => "Igdugang an mga pakli tikang ha ngaran-lat'ang:",
+'export-addns' => 'Dugngi',
# Namespace 8 related
'allmessagesname' => 'Ngaran',
'thumbnail_image-type' => 'An klase han hulagway in diri suportado',
# Special:Import
+'import-upload-filename' => 'Ngaran han paypay:',
'import-comment' => 'Komento:',
+'importnopages' => 'Waray pakli nga ginpapaangbit.',
# Tooltip help for the actions
'tooltip-pt-userpage' => 'An imo pakli hin gumaramit',
# Attribution
'othercontribs' => 'Ginbasihan ha binuhat ni $1.',
+'others' => 'mga iba',
+'siteusers' => '{{SITENAME}} {{PLURAL:$2|gumaramit|mga gumaramit}} $1',
+
+# Info page
+'pageinfo-header-basic' => 'Panguna nga pananabotan',
+'pageinfo-header-edits' => 'Kaagi han pagliwat',
+'pageinfo-header-restrictions' => 'Panalipod han pakli',
+'pageinfo-length' => 'Kahilaba han pakli (ha mga byte)',
+'pageinfo-article-id' => 'ID han pakli',
+'pageinfo-robot-policy' => 'Pamilnga an kahimtang han makina',
+'pageinfo-robot-index' => 'Matutudlok',
+'pageinfo-robot-noindex' => 'Diri matutudlok',
+'pageinfo-views' => 'Ihap han mga naglantaw',
+'pageinfo-watchers' => 'Ihap han nangingita hin pakli',
+'pageinfo-redirects-name' => 'Nairedirekta ha dinhi nga pakli',
+'pageinfo-subpages-name' => 'Mga bahinpakli hin nga pakli',
+'pageinfo-subpages-value' => '$1 ($2 {{PLURAL:$2|redirekta|mga redirekta}}; $3 {{PLURAL:$3|diri redirekta|mga diri redirekta}})',
+'pageinfo-firstuser' => 'Naghimo han pakli',
+'pageinfo-firsttime' => 'Adlaw han pagkahimo han pakli',
+'pageinfo-lastuser' => 'Giurhii nga nagliwat',
+'pageinfo-lasttime' => 'Petsa han kataposan nga pagliwat',
+'pageinfo-edits' => 'Ngatanan nga ihap han mga pakli',
# Browsing diffs
'previousdiff' => '← Durudaan nga pagliwat',
'file-nohires' => 'Waray mas hiruhitaas nga resolusyon.',
'svg-long-desc' => 'SVG nga fayl, ginbabanabanahan nga $1 × $2 nga mga pixel, kadako han fayl: $3',
'show-big-image' => 'Bug-os nga resolusyon',
+'show-big-image-size' => '$1 × $2 nga mga pixel',
# Special:NewFiles
'newimages-legend' => 'Panara',
'exif-sharpness' => 'Pagkatarom',
'exif-gpstimestamp' => 'GPS nga oras (atomiko nga relo)',
'exif-gpsspeedref' => 'Sukol han kalaksi',
+'exif-countrydest' => 'Ginpapakita an nasod',
+'exif-countrycodedest' => 'Ginpapakita an kodigo han nasod',
+'exif-provinceorstatedest' => 'Ginpapakita an lalawigan o estado',
+'exif-citydest' => 'Ginpapakita an syudad',
+'exif-sublocationdest' => 'Ginpapakita an bahin-lokasyon han syudad',
+'exif-objectname' => 'Halipot nga titulo',
'exif-headline' => 'Katukiban',
'exif-source' => 'Tinikangan',
'exif-writer' => 'Manunurat',
'exif-languagecode' => 'Yinaknan',
'exif-iimcategory' => 'Kaarangay',
+'exif-datetimeexpires' => 'Ayaw gamita kahuman han',
+'exif-datetimereleased' => 'Ginpagawas han',
+'exif-cameraownername' => 'Tag-iya han kamera',
'exif-exposureprogram-1' => 'Mano-mano',
'exif-subjectdistance-value' => '$1 ka mga metro',
+'exif-meteringmode-0' => 'Waray kasabti',
+
'exif-lightsource-0' => 'Waray kasabti',
'exif-lightsource-9' => 'Maupay nga panahon',
'exif-lightsource-10' => 'Madampog nga panahon',
'exif-focalplaneresolutionunit-2' => 'pulgadas',
+'exif-gaincontrol-0' => 'Waray',
+
'exif-contrast-1' => 'Mahumok',
'exif-contrast-2' => 'Matig-a',
'exif-gpsaltitude-above-sealevel' => '$1 {{PLURAL:$1|metro|mga metro}} bawbaw han katupngan ha dagat',
'exif-gpsaltitude-below-sealevel' => '$1 {{PLURAL:$1|metro|mga metro}} ubos han katupngan ha dagat',
+# Pseudotags used for GPSSpeedRef
+'exif-gpsspeed-k' => 'Mga kilometro kada oras',
+'exif-gpsspeed-m' => 'Mga milya kada oras',
+
+# Pseudotags used for GPSDestDistanceRef
+'exif-gpsdestdistance-k' => 'Mga kilometro',
+'exif-gpsdestdistance-m' => 'Mga milya',
+
'exif-objectcycle-a' => 'Aga la',
'exif-objectcycle-p' => 'Gab-i la',
# Special:Version
'version' => 'Bersyon',
+'version-skins' => 'Mga panit',
'version-version' => '(Bersion $1)',
'version-license' => 'Lisensya',
'version-software-product' => 'Produkto',
'version-software-version' => 'Bersyon',
# Special:FilePath
+'filepath' => 'Aragian han paypay',
'filepath-page' => 'Paypay:',
'filepath-submit' => 'Kadto-a',
# Special:FileDuplicateSearch
'fileduplicatesearch-submit' => 'Pamilnga',
-'fileduplicatesearch-noresults' => 'Waray nabilngan nga paypay nga an ngaran "$".',
+'fileduplicatesearch-noresults' => 'Waray nabilngan nga paypay nga an ngaran in "$".',
# Special:SpecialPages
'specialpages' => 'Mga pinaurog nga pakli',
+'specialpages-group-login' => 'Magpalista nga masakob / paghimo hin bag-o nga akawnt',
+'specialpages-group-users' => 'Mga gumaramit ngan mga katungod',
+'specialpages-group-highuse' => 'Mga pakli nga damo nagamit',
+'specialpages-group-pages' => 'Talaan hin mga pakli',
'specialpages-group-pagetools' => 'Mga higamit han pakli',
+'specialpages-group-wiki' => 'Datos ngan mga higamit han Wiki',
+'specialpages-group-redirects' => 'Ginreredirek an mga pakli nga pinaurog',
+'specialpages-group-spam' => 'Mga higamit han spam',
# Special:BlankPage
'blankpage' => 'Blanko nga pakli',
'htmlform-selectorother-other' => 'iba',
# New logging system
+'logentry-newusers-newusers' => '$1 in naghimo hin gumaramit nga akawnt',
+'logentry-newusers-create' => '$1 in naghimo hin gumaramit nga akawnt',
+'logentry-newusers-create2' => '$1 in naghimo hin gumaramit nga akawnt $3',
+'logentry-newusers-autocreate' => 'An akawnt nga $1 in lugaring nga nahimo',
'newuserlog-byemail' => 'Ginpadangat an tigaman-pagsulod pinaagi han e-mail',
# Feedback
'feedback-close' => 'Human na.',
+# Search suggestions
+'searchsuggest-search' => 'Pamilnga',
+'searchsuggest-containing' => 'nagsusulod. . .',
+
+# API errors
+'api-error-badaccess-groups' => 'Diri ka gintutugotan pagkarga paigbaw ha dinhi nga wiki.',
+'api-error-badtoken' => 'Sayop ha sulod: Maraot nga token.',
+'api-error-copyuploaddisabled' => 'Pagkarga paigbaw pinaagi han URL in diri mahihimo ha dinhi nga serbidor.',
+'api-error-filename-tooshort' => 'An ngaran han paypay in halipot hin duro.',
+'api-error-filetype-banned' => 'Diri gintutugotan ini nga klase nga paypay.',
+'api-error-filetype-missing' => 'Ini nga ngaran han paypay in nawawad-an hin ekstensyon.',
+'api-error-http' => 'Sayop ha sulod: Diri nakakasumpay ha serbidor.',
+'api-error-illegal-filename' => 'Diri gintutugotan an ngaran-han-paypay.',
+'api-error-overwrite' => 'Pagsasapaw in aada nga paypay in diri gintutugotan.',
+'api-error-stashfailed' => 'Sayop ha sulod: An serbidor in waray makatipig han temporaryo nga paypay.',
+'api-error-timeout' => 'An serbidor in diri nabaton ha sulod han ginaasahan nga oras.',
+'api-error-unclassified' => 'Nahitabo an waray kasabti nga sayop.',
+'api-error-unknown-code' => 'Waray kasabti nga sayop: "$1".',
+'api-error-unknown-error' => 'Sayop ha sulod: May-ada nagkasayop han pagkakarga paigbaw han imo paypay.',
+'api-error-unknown-warning' => 'Waray kasabti nga pahimatngon: "$1".',
+'api-error-unknownerror' => 'Waray kasabti nga sayop: "$1".',
+'api-error-uploaddisabled' => 'Diri ginpapakarga paigbaw ha dinhi nga wiki.',
+'api-error-verification-error' => 'Ini nga paypay in bangin naraot, o may-ada iba nga ekstensyon.',
+
+# Durations
+'duration-seconds' => '$1 {{PLURAL:$1|segundo|mga segundo}}',
+'duration-minutes' => '$1 {{PLURAL:$1|minuto|mga minuto}}',
+'duration-hours' => '$1 {{PLURAL:$1|oras|mga oras}}',
+'duration-days' => '$1 {{PLURAL:$1|adlaw|mga adlaw}}',
+'duration-weeks' => '$1 {{PLURAL:$1|semana|mga semana}}',
+'duration-years' => '$1 {{PLURAL:$1|tuig|mga tuig}}',
+'duration-decades' => '$1 {{PLURAL:$1|dekada|mga dekada}}',
+'duration-centuries' => '$1 {{PLURAL:$1|gatostuig|mga gatostuig}}',
+'duration-millennia' => '$1 {{PLURAL:$1|yukottuig|mga yukottuig}}',
+
);
'qbbrowse' => 'Lemmi',
'qbedit' => 'Soppi',
'qbpageoptions' => 'Xëtuw tànneef',
-'qbpageinfo' => 'Xëtuw xibaar',
'qbmyoptions' => 'Samay tànneef',
'qbspecialpages' => 'Xëti jagleel',
'faq' => 'Laaj yi ëpp',
'qbbrowse' => 'בלעטערט',
'qbedit' => 'ענדערן',
'qbpageoptions' => 'דער בלאט',
-'qbpageinfo' => 'קאנטעקסט',
'qbmyoptions' => 'מיינע בלעטער',
'qbspecialpages' => 'ספעציעלע בלעטער',
'faq' => 'מערסטע געפרעגטע פראגעס',
'vector-action-protect' => 'שיצן',
'vector-action-undelete' => 'מבטל זיין אויסמעקן',
'vector-action-unprotect' => 'ענדערונג באַשיצונג',
-'vector-simplesearch-preference' => '×\90ַק×\98×\99×\95×\95×\99ר×\9f פֿ×\90ַר×\91ר×\99×\99×\98ער×\98×¢ ×\96×\95×\9a פֿ×\90רש×\9c×\90Ö¸×\92×\9f (נאר וועקטאר)',
+'vector-simplesearch-preference' => '×\93ער×\9e×¢×\92×\9c×¢×\9b×\9f פֿ×\90ַרפש×\95×\98ער×\98×\9f ×\96×\95×\9a פ×\90ַס (נאר וועקטאר)',
'vector-view-create' => 'שאַפֿן',
'vector-view-edit' => 'רעדאַקטירן',
'vector-view-history' => 'ווײַזן היסטאָריע',
'edit-already-exists' => 'נישט מעגליך צו שאַפֿן נייע בלאט.
ער עקזיסטירט שוין.',
'defaultmessagetext' => 'גרונטלעכער מעלדונג טעקסט',
+'invalid-content-data' => 'אומגילטיקע אינהאלט דאטן',
# Parser/template warnings
'expensive-parserfunction-warning' => "'''אזהרה:''' דער בלאט אנטהאלט צופיל טייערע פארזירער רופן.
'right-upload' => 'ארויפלאדן טעקעס',
'right-reupload' => 'איבערשרײַבן עקסיסטירנדע טעקע',
'right-reupload-own' => "איבערשרײַבן עקזיסטירנדע טעקעס וואָס מ'האט אַליין אַרויפֿגעלאָדן",
+'right-reupload-shared' => 'אריבערשרייבן טעקעס אויפן געמיינזאם מעדיע רעפאזיטאריום',
'right-upload_by_url' => 'ארויפֿלאָדן טעקעס פֿון אַ URL',
'right-purge' => 'ליידיקן דעם זייטל־זאפאס פאר א בלאט אן באשטעטיקונג',
'right-autoconfirmed' => 'רעדאקטירן האלב-געשיצטע בלעטער',
'badfilename' => 'טעקע נאמען איז געטוישט צו "$1".',
'filetype-mime-mismatch' => 'טעקע סופֿיקס ".$1" שטימט נישט מיטן MIME טיפ פון דער טעקע($2).',
'filetype-badmime' => 'טעקעס מיטן MIME טיפ "$1" טאר מען נישט ארויפלאדן.',
+'filetype-bad-ie-mime' => 'נישט מעגלעך ארויפלאד די טעקע ווייל אינטערנעץ עקספלארער וועט זי דערקענען ווי "$1", וואס איז א נישט דערלויבטער און פאטענציאעל געפערליכער טעקע טיפ.',
'filetype-unwanted-type' => "'''\".\$1\"''' איז אן אומרעקאמענדירטער טעקע־טיפ. {{PLURAL:\$3|רעקאמענדירטער טעקע־טיפ איז|רעקאמענדירטע טעקע־טיפן זענען}} \$2.",
'filetype-banned-type' => '\'\'\'".$1"\'\'\' {{PLURAL:$4|איז נישט קיין דערלויבטער טעקע־טיפ |זענען נישט קיין דערלויבטע טעקע־טיפן}}. {{PLURAL:$3|דערלויבטער טעקע־טיפ איז|דערלויבטע טעקע־טיפן זענען}} $2.',
'filetype-missing' => 'די טעקע האט נישט קיין פארברייטערונג (למשל ".jpg").',
'file-too-large' => 'די טעקע וואָס איר האט אײַנגעגעבן איז צו גרויס.',
'filename-tooshort' => 'דער טעקענאמען איז צו קורץ',
'filetype-banned' => 'דער טיפ טעקע איז געאַסרט',
+'verification-error' => 'די טעקע איז נישט אדורכגעגאנגען טעקע פרואוואונג.',
+'hookaborted' => 'די מאדיפיצירונג איר האט פרובירט קען נישט ווערן דורכגעפירט צוליב א פארברייטערונג.',
'illegal-filename' => 'דער טעקע־נאָמען איז נישט ערלויבט',
'overwrite' => 'מען טאָר נישט איבערשרײַבן אַן עקזיסטירנדע טעקע.',
'unknown-error' => 'אַן אומבאַקאַנט טעות איז פֿארגעקומען.',
* נאמען פון דער טעקע וואס ווערט ארויפגעלאָדן: <strong>[[:$1]]</strong>
* נאמען פון דער פֿאראנענער טעקע: <strong>[[:$2]]</strong>
זײַט אזוי גוט און קלויבט אן אנדער נאמען.',
+'file-thumbnail-no' => "דער טעקע־נאמען הייבט אן מיט <strong>$1</strong>.
+זי זעט אויס ווי א פארקלענערט בילד ''(מיניאטור)''.
+טאמער האט איר דאס בילד אין פולער רעזאלוציע טוט עס ארויפלאדן, אנדערשט זייט אזוי גוט און ענדערט דעם טעקע־נאמען.",
'fileexists-forbidden' => 'א טעקע מיט דעם נאָמען עקזיסטירט שוין, און מען קען זי נישט אַריבערשרײַבן.
אויב איר ווילט דאך אַרויפֿלאָדן אײַער טעקע, ביטע גיין צוריק און ניצן אַן אַנדער נאָמען.
[[File:$1|thumb|center|$1]]',
'license-header' => 'ליצענץ:',
'nolicense' => 'גארנישט',
'license-nopreview' => '(פֿאראויסקוק נישט פֿאַראַן)',
+'upload_source_url' => ' (א גילטיקע , צוגעגנלעכער URL)',
'upload_source_file' => '(א טעקע אויף אײַער קאמפיוטער)',
# Special:ListFiles
'filerevert-defaultcomment' => 'צוריקגעשטעלט צו דער ווערסיע פֿון $2, $1',
'filerevert-submit' => 'צוריקדרייען',
'filerevert-success' => "'''[[Media:$1|$1]]''' צוריקגשטעלט צו דער [$4 ווערסיע פֿון $3, $2].",
+'filerevert-badversion' => 'נישט פאראן קיין פריערדיקע לאקאלע ווערסיע פון דער טעקע מיטן געזוכטן צייטשטעמפל.',
# File deletion
'filedelete' => 'מעק אויס $1',
'undeletedrevisions' => '{{PLURAL:$1|1 רעוויזיע|$1 רעוויזיעס}} צוריקגעשטעלט',
'undeletedrevisions-files' => '{{PLURAL:$1|1 רעוויזיע|$1 רעוויזיעס}} און {{PLURAL:$2|1 טעקע|$2 טעקעס}} צוריקגעשטעלט',
'undeletedfiles' => '{{PLURAL:$1|1 טעקע|$1 טעקעס}} צוריקגעשטעלט',
-'cannotundelete' => 'צוריקשטעלונג איז דורכגעפאלן; עס איז מעגליך אז אן אנדערע האט דאס שוין צוריקגעשטעלט.',
+'cannotundelete' => 'צוריקשטעלונג איז דורכגעפאלן: $1',
'undeletedpage' => "'''דער בלאט $1 איז געווארן צוריקגעשטעלט.'''
זעט דעם [[Special:Log/delete| אויסמעקן לאג]] פֿאר א ליסטע פון די לעצטע אויסגעמעקטע און צוריקגעשטעלטע בלעטער.",
'undelete-search-box' => 'זוכן אויסגעמעקטע בלעטער',
'undelete-search-prefix' => 'ווײַז בלעטער וואס הייבן אן מיט:',
'undelete-search-submit' => 'זוכן',
+'undelete-no-results' => 'נישט געטראפן קיין צוגעפאסטע בלעטער אין אויסמעקונג ארכיוו.',
'undelete-error' => 'גרייז ביים צוריקשטעלן בלאט',
'undelete-error-short' => 'טעות ביים צוריקשטעלן טעקע: $1',
'undelete-error-long' => 'גרײַזן געטראפֿן בײַם ווידערשטעלן די טעקע:
'namespace' => 'נאמענטייל:',
'invert' => 'ווײַז אַלע אויסער די',
'namespace_association' => 'אָנגעבונדענער נאָמענטייל',
+'tooltip-namespace_association' => 'צייכנט דאס קעסטל כדי איינשליסן דעם שמועס אדער סוביעקט נאמענטייל וואס געהערט צום אויסגעקליבענעם נאמענטייל',
'blanknamespace' => '(הויפט)',
# Contributions
'imageinvalidfilename' => 'דער ציל טעקע נאָמען איז נישט גילטיק.',
'fix-double-redirects' => 'דערהײַנטיקן ווײַטערפֿירונגען צום ארגינעלן טיטל',
'move-leave-redirect' => 'איבערלאזן א ווײַטערפֿירונג',
+'move-over-sharedrepo' => '== טעקע עקזיסטירט ==
+[[:$1]] עקזיסטירט אויף א געטיילטן רעפאזיטאריום. ווען מען באוועגט א טעקע צו דעם טיטל וועט דאס איבערשרייבן די געטיילטע טעקע.',
+'file-exists-sharedrepo' => "ס'איז שוין פאראן א טעקע מיטן געקליבענעם נאמען אויף א געמיינזאם רעפאזיטאריום.
+זייט אזוי גוט קלייבט אן אנדער נאמען.",
# Export
'export' => 'עקספארטירן בלעטער',
'import-interwiki-templates' => 'איינשילסן אלע מוסטערן',
'import-interwiki-submit' => 'אימפארט',
'import-interwiki-namespace' => 'ציל נאמענטייל:',
+'import-interwiki-rootpage' => 'ציל שטאמבלאט (אפציאנאל):',
'import-upload-filename' => 'טעקע נאמען:',
'import-comment' => 'הערה:',
'importtext' => 'ביטע עקספארטירט די טעקע פון דער מקור וויקי ניצנדיג דאס [[Special:Export|עקספארט הילפמיטל]], שפייכלט אײַן אויף אײַער קאמפיוטער און לאדט אַרויף דא.',
'import-error-interwiki' => 'דעם בלאט "$1" קען מען נישט אימפארטירן ווייל זיין נאמען איז רעזערווירט פאר דרויסנדיקער פארבינדונג (אינטערוויקי).',
'import-error-special' => 'דעם בלאט "$1" קען מען נישט אימפארטירן ווייל ער געהערט צו א באזונדערן נאמענטייל וואס אנטהאלט נישט קיין בלעטער.',
'import-error-invalid' => 'דעם בלאט "$1" קען מען נישט אימפארטירן ווייל זיין נאמען איז אומגילטיק.',
+'import-options-wrong' => '{{PLURAL:$2|פאלשער אויסקלייב|פאלשע אויסקלייבן}}: <nowiki>$1</nowiki>',
+'import-rootpage-invalid' => 'געגעבענער שטאמבלאט איז אן אומגילטיקער טיטל.',
+'import-rootpage-nosubpage' => 'נאמענטייל "$1" פונעם שטאמבלאט ערלויבט נישט קיין אונטערבלעטער.',
# Import log
'importlogpage' => 'אימפארט לאגבוך',
'exif-saturation' => 'זעטיקונג',
'exif-sharpness' => 'שארף',
'exif-devicesettingdescription' => 'אפאראט שטעלונגען אראפמאלונג',
+'exif-imageuniqueid' => 'בילד־ID',
'exif-gpslatituderef' => 'צפון אדער דרום גארטל־ליניע',
'exif-gpslatitude' => 'גארטל־ליניע',
'exif-gpslongituderef' => 'מזרח אדער מערב לענג',
'exif-keywords' => 'שליסלווערטער',
'exif-worldregioncreated' => "וועלטראיאן וואו מ'האט גענומען דאס בילד",
'exif-countrycreated' => "לאנד וואו מ'האט געמאכט דאס בילד",
+'exif-countrycodecreated' => "קאד פארן לאנד וואו מ'האט געמאכט דאס בילד",
'exif-provinceorstatecreated' => "פראווינץ אדער שטאַט וואו מ'האט גענומען דאס בילד",
'exif-citycreated' => "שטאָט וואו מ'האט געמאכט דאס בילד",
'exif-worldregiondest' => 'וועלטראיאן געוויזן',
'exif-cameraownername' => 'אייגנטימער פון קאמערע',
'exif-label' => 'צעטל',
'exif-datetimemetadata' => 'דאטע ווען מעטאדאטן זענען געווען לעצט געענדערט',
+'exif-nickname' => 'אויספארמעלער נאמען פון בילד',
'exif-rating' => 'שאצונג (פֿון 5)',
'exif-rightscertificate' => 'רעכטן פארוואלטונג צערטיפיקאט',
'exif-copyrighted' => 'קאפירעכט סטאַטוס',
# EXIF attributes
'exif-compression-1' => 'אומ-צאמגעקוועטשט',
+'exif-copyrighted-true' => 'געשיצט מיט קאפירעכט',
+'exif-copyrighted-false' => 'פובליקער געביט',
+
'exif-unknowndate' => 'אומבאַוואוסטע דאַטע',
'exif-orientation-1' => 'נארמאַל',
'qbbrowse' => 'Ìṣíwò',
'qbedit' => 'Àtúnṣe',
'qbpageoptions' => 'Ojúewé yi',
-'qbpageinfo' => 'Àjọwípọ̀',
'qbmyoptions' => 'Àwọn ojúewé mi',
'qbspecialpages' => 'Àwọn ojúewé pàtàkì',
'faq' => 'FAQ',
'qbbrowse' => '瀏覽',
'qbedit' => '編輯',
'qbpageoptions' => '呢一頁',
-'qbpageinfo' => '附近文字',
'qbmyoptions' => '我嘅選項',
'qbspecialpages' => '特別頁',
'faq' => 'FAQ',
'qbbrowse' => 'Blaeren',
'qbedit' => 'Bewerk',
'qbpageoptions' => 'Paginaopties',
-'qbpageinfo' => 'Pagina-informaotie',
'qbmyoptions' => 'Mien opties',
'qbspecialpages' => 'Speciaole pahina’s',
'faq' => 'FAQ (veehestelde vraehen)',
'qbbrowse' => '浏览',
'qbedit' => '编辑',
'qbpageoptions' => '页面选项',
-'qbpageinfo' => '页面信息',
'qbmyoptions' => '我的选项',
'qbspecialpages' => '特殊页面',
'faq' => '常见问题',
'spam_deleting' => '正在删除所有包含至$1的版本',
# Info page
-'pageinfo-title' => '"$1" 的信息',
+'pageinfo-title' => '“$1”的信息',
'pageinfo-header-basic' => '基本信息',
'pageinfo-header-edits' => '编辑历史',
'pageinfo-header-restrictions' => '页面保护',
'pageinfo-robot-policy' => '搜索引擎状态',
'pageinfo-robot-index' => '可索引',
'pageinfo-robot-noindex' => '不可索引',
-'pageinfo-views' => '视图的数量',
+'pageinfo-views' => '查看次数',
'pageinfo-watchers' => '页面监视者人数',
'pageinfo-redirects-name' => '重定向到本页',
'pageinfo-subpages-name' => '本页的子页面',
'pageinfo-lasttime' => '最后编辑的日期',
'pageinfo-edits' => '总编辑次数',
'pageinfo-authors' => '不同编者总计',
-'pageinfo-recent-edits' => '最近的编辑数 ($1天内)',
+'pageinfo-recent-edits' => '最近的编辑数($1内)',
'pageinfo-recent-authors' => '最近的不同编者数',
'pageinfo-magic-words' => '魔术字 ($1)',
'pageinfo-hidden-categories' => '隐藏分类 ($1)',
'qbbrowse' => '瀏覽',
'qbedit' => '編輯',
'qbpageoptions' => '頁面選項',
-'qbpageinfo' => '頁面訊息',
'qbmyoptions' => '我的選項',
'qbspecialpages' => '特殊頁面',
'faq' => '常見問題解答',
'edit-already-exists' => '不可以建立一個新頁面。
它已經存在。',
'defaultmessagetext' => '預設訊息文字',
+'content-failed-to-parse' => '未能轉換$2 內容成為$1:$3',
+'invalid-content-data' => '內容資料無效',
+'content-not-allowed-here' => '[[$2]]不允許"$1"頁上的內容',
+
+# Content models
+'content-model-text' => '純文字',
+'content-model-javascript' => 'JavaScript',
+'content-model-css' => 'CSS',
# Parser/template warnings
'expensive-parserfunction-warning' => '警告: 這個頁面有太多耗費的語法功能呼叫。
'undeletedrevisions' => '$1個修訂版本已經恢復',
'undeletedrevisions-files' => '$1 個版本和 $2 個檔案被恢復',
'undeletedfiles' => '$1 個檔案被恢復',
-'cannotundelete' => 'æ\81¢å¾©å¤±æ\95\97ï¼\9bå\8f¯è\83½ä¹\8bå\89\8då·²ç¶\93被å\85¶ä»\96人æ\81¢å¾©ã\80\82',
+'cannotundelete' => 'æ\81¢å¾©å¤±æ\95\97ï¼\9a$1',
'undeletedpage' => "'''$1已經被恢復''' 請參考[[Special:Log/delete|刪除日誌]]來查詢刪除及恢復記錄。",
'undelete-header' => '如要查詢最近的記錄請參閱[[Special:Log/delete|刪除日誌]]。',
'undelete-search-title' => '搜索已刪除頁面',
'immobile-target-namespace-iw' => '垮維基連結在移動頁面中是無效的目標。',
'immobile-source-page' => '這個頁面不能移動。',
'immobile-target-page' => '無法移動至目標標題中。',
+'bad-target-model' => '所需的目的地使用不同的內容模式。不可以從$1轉換到 $2 。',
'imagenocrossnamespace' => '不可以移動檔案到非檔案名字空間',
'nonfile-cannot-move-to-file' => '不可以移動非檔案到檔案名字空間',
'imagetypemismatch' => '該新副檔名不匹配它的類型',
'spam_deleting' => '所有包含連結至$1的修訂,刪除中',
# Info page
-'pageinfo-title' => '" $1 "的信息',
+'pageinfo-title' => '“$1”的信息',
+'pageinfo-not-current' => '資訊可能只顯示在當前的修訂版本。',
'pageinfo-header-basic' => '基本資料',
'pageinfo-header-edits' => '編輯歷史',
'pageinfo-header-restrictions' => '保護頁面',
'pageinfo-magic-words' => '魔術{{PLURAL:$1|字|字}} ( $1 )',
'pageinfo-hidden-categories' => '隱藏{{PLURAL:$1|分類|分類}} ( $1 )',
'pageinfo-templates' => '被引用的{{PLURAL:$1|模版|模版}} ( $1 )',
+'pageinfo-toolboxlink' => '頁面資訊',
# Skin names
'skinname-standard' => '標準',
# Scary transclusion
'scarytranscludedisabled' => '[跨wiki轉換代碼不可用]',
'scarytranscludefailed' => '[模板$1讀取失敗]',
+'scarytranscludefailed-httpstatus' => '[模板$1讀取失敗:HTTP$2]',
'scarytranscludetoolong' => '[URL 地址太長]',
# Delete conflict
* @param $token string The token string
* @param $left The left operand. If it is an object, its state may be destroyed.
* @param $right The right operand
+ * @throws CLDRPluralRuleError
* @return mixed
*/
private static function doOperation( $token, $left, $right ) {
$title = $titleObj->getPrefixedDBkey();
$this->output( "$title..." );
# Update searchindex
- $u = new SearchUpdate( $pageId, $titleObj->getText(), $rev->getText() );
+ # TODO: pass the Content object to SearchUpdate, let the search engine decide how to deal with it.
+ $u = new SearchUpdate( $pageId, $titleObj->getText(), $rev->getContent()->getTextForSearchIndex() );
$u->doUpdate();
$this->output( "\n" );
}
--- /dev/null
+ALTER TABLE /*$wgDBprefix*/archive
+ ADD ar_content_format varbinary(64) DEFAULT NULL;
--- /dev/null
+ALTER TABLE /*$wgDBprefix*/archive
+ ADD ar_content_model varbinary(32) DEFAULT NULL;
--- /dev/null
+-- Add fa_sha1 and related index
+ALTER TABLE /*$wgDBprefix*/filearchive
+ ADD COLUMN fa_sha1 varbinary(32) NOT NULL default '';
+CREATE INDEX /*i*/fa_sha1 ON /*$wgDBprefix*/filearchive (fa_sha1(10));
--- /dev/null
+ALTER TABLE /*_*/job
+ ADD COLUMN job_random integer unsigned NOT NULL default 0,
+ ADD COLUMN job_token varbinary(32) NOT NULL default '',
+ ADD COLUMN job_token_timestamp varbinary(14) NULL default NULL,
+ ADD COLUMN job_sha1 varbinary(32) NOT NULL default '';
+
+CREATE INDEX /*i*/job_sha1 ON /*_*/job (job_sha1);
+CREATE INDEX /*i*/job_cmd_token ON /*_*/job (job_cmd,job_token,job_random);
+
--- /dev/null
+ALTER TABLE /*$wgDBprefix*/page
+ ADD page_content_model varbinary(32) DEFAULT NULL;
--- /dev/null
+ALTER TABLE /*$wgDBprefix*/revision
+ ADD rev_content_format varbinary(64) DEFAULT NULL;
--- /dev/null
+ALTER TABLE /*$wgDBprefix*/revision
+ ADD rev_content_model varbinary(32) DEFAULT NULL;
--- /dev/null
+-- Patch to add the sites and site_identifiers tables.
+-- Licence: GNU GPL v2+
+-- Author: Jeroen De Dauw < jeroendedauw@gmail.com >
+
+
+-- Holds all the sites known to the wiki.
+CREATE TABLE IF NOT EXISTS /*_*/sites (
+-- Numeric id of the site
+ site_id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
+
+ -- Global identifier for the site, ie 'enwiktionary'
+ site_global_key varbinary(32) NOT NULL,
+
+ -- Type of the site, ie 'mediawiki'
+ site_type varbinary(32) NOT NULL,
+
+ -- Group of the site, ie 'wikipedia'
+ site_group varbinary(32) NOT NULL,
+
+ -- Source of the site data, ie 'local', 'wikidata', 'my-magical-repo'
+ site_source varbinary(32) NOT NULL,
+
+ -- Language code of the sites primary language.
+ site_language varbinary(32) NOT NULL,
+
+ -- Protocol of the site, ie 'http://', 'irc://', '//'
+ -- This field is an index for lookups and is build from type specific data in site_data.
+ site_protocol varbinary(32) NOT NULL,
+
+ -- Domain of the site in reverse order, ie 'org.mediawiki.www.'
+ -- This field is an index for lookups and is build from type specific data in site_data.
+ site_domain VARCHAR(255) NOT NULL,
+
+ -- Type dependent site data.
+ site_data BLOB NOT NULL,
+
+ -- If site.tld/path/key:pageTitle should forward users to the page on
+ -- the actual site, where "key" is the local identifier.
+ site_forward bool NOT NULL,
+
+ -- Type dependent site config.
+ -- For instance if template transclusion should be allowed if it's a MediaWiki.
+ site_config BLOB NOT NULL
+) /*$wgDBTableOptions*/;
+
+CREATE UNIQUE INDEX /*i*/sites_global_key ON /*_*/sites (site_global_key);
+CREATE INDEX /*i*/sites_type ON /*_*/sites (site_type);
+CREATE INDEX /*i*/sites_group ON /*_*/sites (site_group);
+CREATE INDEX /*i*/sites_source ON /*_*/sites (site_source);
+CREATE INDEX /*i*/sites_language ON /*_*/sites (site_language);
+CREATE INDEX /*i*/sites_protocol ON /*_*/sites (site_protocol);
+CREATE INDEX /*i*/sites_domain ON /*_*/sites (site_domain);
+CREATE INDEX /*i*/sites_forward ON /*_*/sites (site_forward);
+
+
+
+-- Links local site identifiers to their corresponding site.
+CREATE TABLE IF NOT EXISTS /*_*/site_identifiers (
+ -- Key on site.site_id
+ si_site INT UNSIGNED NOT NULL,
+
+ -- local key type, ie 'interwiki' or 'langlink'
+ si_type varbinary(32) NOT NULL,
+
+ -- local key value, ie 'en' or 'wiktionary'
+ si_key varbinary(32) NOT NULL
+) /*$wgDBTableOptions*/;
+
+CREATE UNIQUE INDEX /*i*/site_ids_type ON /*_*/site_identifiers (si_type, si_key);
+CREATE INDEX /*i*/site_ids_site ON /*_*/site_identifiers (si_site);
+CREATE INDEX /*i*/site_ids_key ON /*_*/site_identifiers (si_key);
\ No newline at end of file
$this->xmlwriterobj = new XmlDumpWriter();
$input = fopen( $this->input, "rt" );
- $result = $this->readDump( $input );
+ $this->readDump( $input );
if ( $this->spawnProc ) {
$this->closeSpawn();
$title = Title::makeTitle( $row->page_namespace, $row->page_title );
$rev = Revision::newFromId( $row->page_latest );
if ( $rev ) {
- $target = Title::newFromRedirect( $rev->getText() );
+ $target = $rev->getContent()->getRedirectTarget();
if ( !$target ) {
$this->output( $title->getPrefixedText() . "\n" );
}
$rev = Revision::newFromTitle( $title );
$currentRevId = $rev->getId();
- while ( $rev && ( $rev->isDeleted( Revision::DELETED_TEXT ) || LinkFilter::matchEntry( $rev->getText() , $domain ) ) ) {
+ while ( $rev && ( $rev->isDeleted( Revision::DELETED_TEXT )
+ || LinkFilter::matchEntry( $rev->getContent( Revision::RAW ), $domain ) ) ) {
$rev = $rev->getPrevious();
}
$page = WikiPage::factory( $title );
if ( $rev ) {
// Revert to this revision
+ $content = $rev->getContent( Revision::RAW );
+
$this->output( "reverting\n" );
- $page->doEdit( $rev->getText(), wfMessage( 'spam_reverting', $domain )->inContentLanguage()->text(),
+ $page->doEditContent( $content, wfMessage( 'spam_reverting', $domain )->inContentLanguage()->text(),
EDIT_UPDATE, $rev->getId() );
} elseif ( $this->hasOption( 'delete' ) ) {
// Didn't find a non-spammy revision, blank the page
$page->doDeleteArticle( wfMessage( 'spam_deleting', $domain )->inContentLanguage()->text() );
} else {
// Didn't find a non-spammy revision, blank the page
+ $handler = ContentHandler::getForTitle( $title );
+ $content = $handler->makeEmptyContent();
+
$this->output( "blanking\n" );
- $page->doEdit( '', wfMessage( 'spam_blanking', $domain )->inContentLanguage()->text() );
+ $page->doEditContent( $content, wfMessage( 'spam_blanking', $domain )->inContentLanguage()->text() );
}
$dbw->commit( __METHOD__ );
}
$parser1 = new $parser1Name();
$parser2 = new $parser2Name();
- $output1 = $parser1->parse( $rev->getText(), $title, $this->options );
- $output2 = $parser2->parse( $rev->getText(), $title, $this->options );
+ $content = $rev->getContent();
+
+ if ( $content->getModel() !== CONTENT_MODEL_WIKITEXT ) {
+ $this->error( "Page {$title->getPrefixedText()} does not contain wikitext but {$content->getModel()}\n" );
+ return;
+ }
+
+ $text = strval( $content->getNativeData() );
+
+ $output1 = $parser1->parse( $text, $title, $this->options );
+ $output2 = $parser2->parse( $text, $title, $this->options );
if ( $output1->getText() != $output2->getText() ) {
$this->failed++;
$this->error( "Parsing for {$title->getPrefixedText()} differs\n" );
if ( $this->saveFailed ) {
- file_put_contents( $this->saveFailed . '/' . rawurlencode( $title->getPrefixedText() ) . ".txt", $rev->getText());
+ file_put_contents( $this->saveFailed . '/' . rawurlencode( $title->getPrefixedText() ) . ".txt", $text );
}
if ( $this->showDiff ) {
$this->output( wfDiff( $this->stripParameters( $output1->getText() ), $this->stripParameters( $output2->getText() ), '' ) );
$repo = RepoGroup::singleton()->getLocalRepo();
# Get "active" revisions from the filearchive table
$output->handleOutput( "Searching for and deleting archived files...\n" );
- $res = $dbw->query( "SELECT fa_id,fa_storage_group,fa_storage_key FROM $tbl_arch" );
+ $res = $dbw->query( "SELECT fa_id,fa_storage_group,fa_storage_key,fa_sha1 FROM $tbl_arch" );
$count = 0;
foreach ( $res as $row ) {
$key = $row->fa_storage_key;
$group = $row->fa_storage_group;
$id = $row->fa_id;
$path = $repo->getZonePath( 'deleted' ) . '/' . $repo->getDeletedHashPath( $key ) . $key;
- $sha1 = substr( $key, 0, strcspn( $key, '.' ) );
+ if( isset( $row->fa_sha1 ) ) {
+ $sha1 = $row->fa_sha1;
+ } else {
+ // old row, populate from key
+ $sha1 = LocalRepo::getHashFromKey( $key );
+ }
// Check if the file is used anywhere...
$inuse = $dbw->selectField( 'oldimage', '1',
array( 'oi_sha1' => $sha1,
* @param $rev Revision
*/
public function processRevision( $rev ) {
- if ( preg_match( $this->getOption( 'regex' ), $rev->getText() ) ) {
+ if ( preg_match( $this->getOption( 'regex' ), $rev->getContent()->getTextForSearchIndex() ) ) {
$this->output( $rev->getTitle() . " matches at edit from " . $rev->getTimestamp() . "\n" );
}
}
# Read the text
$text = $this->getStdin( Maintenance::STDIN_ALL );
+ $content = ContentHandler::makeContent( $text, $wgTitle );
# Do the edit
$this->output( "Saving... " );
- $status = $page->doEdit( $text, $summary,
+ $status = $page->doEditContent( $content, $summary,
( $minor ? EDIT_MINOR : 0 ) |
( $bot ? EDIT_FORCE_BOT : 0 ) |
( $autoSummary ? EDIT_AUTOSUMMARY : 0 ) |
}
if ( $d > 1 ) {
$lb = wfGetLB();
- $serverCount = $lb->getServerCount();
+ $serverCount = $lb->getServerCount();
for ( $i = 0; $i < $serverCount; $i++ ) {
$server = $lb->getServerInfo( $i );
$server['flags'] |= DBO_DEBUG;
$IP . '/includes/actions/',
$IP . '/includes/api/',
$IP . '/includes/cache/',
+ $IP . '/includes/content/',
$IP . '/includes/context/',
$IP . '/includes/db/',
$IP . '/includes/diff/',
private function getHooksFromFile( $file ) {
$content = file_get_contents( $file );
$m = array();
- preg_match_all( '/(?:wfRunHooks|Hooks\:\:run)\(\s*([\'"])(.*?)\1/', $content, $m );
+ preg_match_all( '/(?:wfRunHooks|Hooks\:\:run|ContentHandler\:\:runLegacyHooks)\(\s*([\'"])(.*?)\1/', $content, $m );
return $m[2];
}
$titleText = $title->getPrefixedText();
$this->error( "Page $titleText does not exist.\n", true );
}
- $text = $rev->getText( $this->hasOption( 'show-private' ) ? Revision::RAW : Revision::FOR_PUBLIC );
- if ( $text === false ) {
+ $content = $rev->getContent( $this->hasOption( 'show-private' ) ? Revision::RAW : Revision::FOR_PUBLIC );
+ if ( $content === false ) {
$titleText = $title->getPrefixedText();
$this->error( "Couldn't extract the text from $titleText.\n", true );
}
- $this->output( $text );
+ $this->output( $content->serialize() );
}
}
$text = Http::get( $url );
$wikiPage = WikiPage::factory( $title );
- $wikiPage->doEdit( $text, "Importing from $url", 0, false, $user );
+ $content = ContentHandler::makeContent( $text, $wikiPage->getTitle() );
+ $wikiPage->doEditContent( $content, "Importing from $url", 0, false, $user );
}
}
echo( "\nPerforming edit..." );
$page = WikiPage::factory( $title );
- $page->doEdit( $text, $comment, $flags, false, $user );
+ $content = ContentHandler::makeContent( $text, $title );
+ $page->doEditContent( $content, $comment, $flags, false, $user );
echo( "done.\n" );
} else {
/**
* Check a language.
* @param $code string The language code.
+ * @throws MWException
* @return array The results.
*/
protected function checkLanguage( $code ) {
/**
* Check a language and show the results.
* @param $code string The language code.
+ * @throws MWException
*/
protected function checkLanguage( $code ) {
foreach( $this->extensions as $extension ) {
'qbbrowse',
'qbedit',
'qbpageoptions',
- 'qbpageinfo',
'qbmyoptions',
'qbspecialpages',
'faq',
'addsection-preload',
'addsection-editintro',
'defaultmessagetext',
+ 'content-failed-to-parse',
+ 'invalid-content-data',
+ 'content-not-allowed-here',
+ ),
+ 'contentmodels' => array(
+ 'content-model-wikitext',
+ 'content-model-text',
+ 'content-model-javascript',
+ 'content-model-css',
),
'parserwarnings' => array(
'expensive-parserfunction-warning',
'immobile-target-namespace-iw',
'immobile-source-page',
'immobile-target-page',
+ 'bad-target-model',
'immobile_namespace',
'imagenocrossnamespace',
'nonfile-cannot-move-to-file',
'info' => array(
'pageinfo-header',
'pageinfo-title',
+ 'pageinfo-not-current',
'pageinfo-header-basic',
'pageinfo-header-edits',
'pageinfo-header-restrictions',
'toolbar' => 'Edit page toolbar',
'edit' => 'Edit pages',
'parserwarnings' => 'Parser/template warnings',
+ 'contentmodels' => 'Content models',
'undo' => '"Undo" feature',
'cantcreateaccount' => 'Account creation failure',
'history' => 'History pages',
/**
* @params $config Array
+ * @param array $config
+ * @throws Exception
* @return LockServerDaemon
*/
public static function init( array $config ) {
}
/**
+ * @throws Exception
* @return void
*/
protected function setupServerSocket() {
--- /dev/null
+define mw_prefix='{$wgDBprefix}';
+
+ALTER TABLE &mw_prefix.archive ADD ar_content_format VARCHAR2(64);
--- /dev/null
+define mw_prefix='{$wgDBprefix}';
+
+ALTER TABLE &mw_prefix.archive ADD ar_content_model VARCHAR2(32);
--- /dev/null
+define mw_prefix='{$wgDBprefix}';
+
+ALTER TABLE &mw_prefix.category DROP COLUMN cat_hidden;
+
--- /dev/null
+define mw_prefix='{$wgDBprefix}';
+
+ALTER TABLE &mw_prefix.page ADD page_content_model VARCHAR2(32);
--- /dev/null
+define mw_prefix='{$wgDBprefix}';
+
+ALTER TABLE &mw_prefix.recentchanges DROP ( rc_moved_to_ns, rc_moved_to_title );
+
--- /dev/null
+define mw_prefix='{$wgDBprefix}';
+
+ALTER TABLE &mw_prefix.revision ADD rev_content_format VARCHAR2(64);
--- /dev/null
+define mw_prefix='{$wgDBprefix}';
+
+ALTER TABLE &mw_prefix.revision ADD rev_content_model VARCHAR2(32);
--- /dev/null
+define mw_prefix='{$wgDBprefix}';
+
+ALTER TABLE &mw_prefix.site_stats DROP COLUMN ss_admins;
+
page_random NUMBER(15,14) NOT NULL,
page_touched TIMESTAMP(6) WITH TIME ZONE,
page_latest NUMBER DEFAULT 0 NOT NULL, -- FK?
- page_len NUMBER DEFAULT 0 NOT NULL
+ page_len NUMBER DEFAULT 0 NOT NULL,
+ page_content_model VARCHAR2(32)
);
ALTER TABLE &mw_prefix.page ADD CONSTRAINT &mw_prefix.page_pk PRIMARY KEY (page_id);
CREATE UNIQUE INDEX &mw_prefix.page_u01 ON &mw_prefix.page (page_namespace,page_title);
-- Create a dummy page to satisfy fk contraints especially with revisions
INSERT INTO &mw_prefix.page
- VALUES (0, 0, ' ', NULL, 0, 0, 0, 0, current_timestamp, 0, 0);
+ VALUES (0, 0, ' ', NULL, 0, 0, 0, 0, current_timestamp, 0, 0, NULL);
/*$mw$*/
CREATE TRIGGER &mw_prefix.page_set_random BEFORE INSERT ON &mw_prefix.page
rev_deleted CHAR(1) DEFAULT '0' NOT NULL,
rev_len NUMBER NULL,
rev_parent_id NUMBER DEFAULT NULL,
- rev_sha1 VARCHAR2(32) NULL
+ rev_sha1 VARCHAR2(32) NULL,
+ rev_content_model VARCHAR2(32),
+ rev_content_format VARCHAR2(64)
);
ALTER TABLE &mw_prefix.revision ADD CONSTRAINT &mw_prefix.revision_pk PRIMARY KEY (rev_id);
ALTER TABLE &mw_prefix.revision ADD CONSTRAINT &mw_prefix.revision_fk1 FOREIGN KEY (rev_page) REFERENCES &mw_prefix.page(page_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
ar_len NUMBER,
ar_page_id NUMBER,
ar_parent_id NUMBER,
- ar_sha1 VARCHAR2(32) NULL
+ ar_sha1 VARCHAR2(32),
+ ar_content_model VARCHAR2(32),
+ ar_content_format VARCHAR2(64)
);
ALTER TABLE &mw_prefix.archive ADD CONSTRAINT &mw_prefix.archive_fk1 FOREIGN KEY (ar_user) REFERENCES &mw_prefix.mwuser(user_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED;
CREATE INDEX &mw_prefix.archive_i01 ON &mw_prefix.archive (ar_namespace,ar_title,ar_timestamp);
cat_title VARCHAR2(255) NOT NULL,
cat_pages NUMBER DEFAULT 0 NOT NULL,
cat_subcats NUMBER DEFAULT 0 NOT NULL,
- cat_files NUMBER DEFAULT 0 NOT NULL,
- cat_hidden NUMBER DEFAULT 0 NOT NULL
+ cat_files NUMBER DEFAULT 0 NOT NULL
);
ALTER TABLE &mw_prefix.category ADD CONSTRAINT &mw_prefix.category_pk PRIMARY KEY (cat_id);
CREATE UNIQUE INDEX &mw_prefix.category_u01 ON &mw_prefix.category (cat_title);
ss_total_pages NUMBER DEFAULT -1,
ss_users NUMBER DEFAULT -1,
ss_active_users NUMBER DEFAULT -1,
- ss_admins NUMBER DEFAULT -1,
ss_images NUMBER DEFAULT 0
);
CREATE UNIQUE INDEX &mw_prefix.site_stats_u01 ON &mw_prefix.site_stats (ss_row_id);
--- /dev/null
+<?php
+/**
+ * Optional upgrade script to populate the fa_sha1 field
+ *
+ * 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 Maintenance
+ */
+
+require_once( dirname( __FILE__ ) . '/Maintenance.php' );
+
+/**
+ * Maintenance script to populate the fa_sha1 field.
+ *
+ * @ingroup Maintenance
+ * @since 1.21
+ */
+class PopulateFilearchiveSha1 extends LoggedUpdateMaintenance {
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Populate the fa_sha1 field from fa_storage_key";
+ }
+
+ protected function getUpdateKey() {
+ return 'populate fa_sha1';
+ }
+
+ protected function updateSkippedMessage() {
+ return 'fa_sha1 column of filearchive table already populated.';
+ }
+
+ public function doDBUpdates() {
+ $startTime = microtime( true );
+ $dbw = wfGetDB( DB_MASTER );
+ $table = 'filearchive';
+ $conds = array( 'fa_sha1' => '', 'fa_storage_key IS NOT NULL' );
+ $this->output( "Populating fa_sha1 field from fa_storage_key\n" );
+ $endId = $dbw->selectField( $table, 'MAX(fa_id)', false, __METHOD__ );\r
+
+ $batchSize = $this->mBatchSize;
+ $done = 0;
+
+ do {
+ $res = $dbw->select(
+ $table,
+ array( 'fa_id', 'fa_storage_key' ),
+ $conds,
+ __METHOD__,
+ array( 'LIMIT' => $batchSize )
+ );
+
+ $i = 0;
+ foreach ( $res as $row ) {
+ $sha1 = LocalRepo::getHashFromKey( $row->fa_storage_key );
+ $dbw->update( $table,
+ array( 'fa_sha1' => $sha1 ),
+ array( 'fa_id' => $row->fa_id ),
+ __METHOD__
+ );
+ $lastId = $row->fa_id;
+ $i++;
+ }
+
+ $done += $i;
+ if( $i !== $batchSize ) {
+ break;
+ }
+
+ // print status and let slaves catch up
+ $this->output( sprintf(
+ "id %d done (up to %d), %5.3f%% \r", $lastId, $endId, $lastId / $endId * 100 ) );
+ wfWaitForSlaves();
+ } while( true );
+
+ $processingTime = microtime( true ) - $startTime;
+ $this->output( sprintf( "\nDone %d files in %.1f seconds\n", $done, $processingTime ) );
+
+ return true; // we only updated *some* files, don't log
+ }
+}
+
+$maintClass = "PopulateFilearchiveSha1";
+require_once( RUN_MAINTENANCE_IF_MAIN );
# Go through and update rev_len from these rows.
foreach ( $res as $row ) {
$rev = new Revision( $row );
- $text = $rev->getRawText();
- if ( !is_string( $text ) ) {
+ $content = $rev->getContent();
+ if ( !$content ) {
# This should not happen, but sometimes does (bug 20757)
- $this->output( "Text of revision {$row->rev_id} unavailable!\n" );
+ $this->output( "Content of revision {$row->rev_id} unavailable!\n" );
$missing++;
}
else {
# Update the row...
$db->update( 'revision',
- array( 'rev_len' => strlen( $text ) ),
+ array( 'rev_len' => $content->getSize() ),
array( 'rev_id' => $row->rev_id ),
__METHOD__ );
$count++;
$rev = ( $table === 'archive' )
? Revision::newFromArchiveRow( $row )
: new Revision( $row );
- $text = $rev->getRawText();
+ $text = $rev->getSerializedData();
} catch ( MWException $e ) {
- $this->output( "Text of revision with {$idCol}={$row->$idCol} unavailable!\n" );
+ $this->output( "Data of revision with {$idCol}={$row->$idCol} unavailable!\n" );
return false; // bug 22624?
}
if ( !is_string( $text ) ) {
# This should not happen, but sometimes does (bug 20757)
- $this->output( "Text of revision with {$idCol}={$row->$idCol} unavailable!\n" );
+ $this->output( "Data of revision with {$idCol}={$row->$idCol} unavailable!\n" );
return false;
} else {
$db->update( $table,
$this->output( "Text of revision with timestamp {$row->ar_timestamp} unavailable!\n" );
return false; // bug 22624?
}
- $text = $rev->getRawText();
+ $text = $rev->getSerializedData();
if ( !is_string( $text ) ) {
# This should not happen, but sometimes does (bug 20757)
- $this->output( "Text of revision with timestamp {$row->ar_timestamp} unavailable!\n" );
+ $this->output( "Data of revision with timestamp {$row->ar_timestamp} unavailable!\n" );
return false;
} else {
# Archive table as no PK, but (NS,title,time) should be near unique.
page_random NUMERIC(15,14) NOT NULL DEFAULT RANDOM(),
page_touched TIMESTAMPTZ,
page_latest INTEGER NOT NULL, -- FK?
- page_len INTEGER NOT NULL
+ page_len INTEGER NOT NULL,
+ page_content_model TEXT
);
CREATE UNIQUE INDEX page_unique_name ON page (page_namespace, page_title);
CREATE INDEX page_main_title ON page (page_title text_pattern_ops) WHERE page_namespace = 0;
CREATE SEQUENCE revision_rev_id_seq;
CREATE TABLE revision (
- rev_id INTEGER NOT NULL UNIQUE DEFAULT nextval('revision_rev_id_seq'),
- rev_page INTEGER NULL REFERENCES page (page_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
- rev_text_id INTEGER NULL, -- FK
- rev_comment TEXT,
- rev_user INTEGER NOT NULL REFERENCES mwuser(user_id) ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED,
- rev_user_text TEXT NOT NULL,
- rev_timestamp TIMESTAMPTZ NOT NULL,
- rev_minor_edit SMALLINT NOT NULL DEFAULT 0,
- rev_deleted SMALLINT NOT NULL DEFAULT 0,
- rev_len INTEGER NULL,
- rev_parent_id INTEGER NULL,
- rev_sha1 TEXT NOT NULL DEFAULT ''
+ rev_id INTEGER NOT NULL UNIQUE DEFAULT nextval('revision_rev_id_seq'),
+ rev_page INTEGER NULL REFERENCES page (page_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
+ rev_text_id INTEGER NULL, -- FK
+ rev_comment TEXT,
+ rev_user INTEGER NOT NULL REFERENCES mwuser(user_id) ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED,
+ rev_user_text TEXT NOT NULL,
+ rev_timestamp TIMESTAMPTZ NOT NULL,
+ rev_minor_edit SMALLINT NOT NULL DEFAULT 0,
+ rev_deleted SMALLINT NOT NULL DEFAULT 0,
+ rev_len INTEGER NULL,
+ rev_parent_id INTEGER NULL,
+ rev_sha1 TEXT NOT NULL DEFAULT '',
+ rev_content_model TEXT,
+ rev_content_format TEXT
);
CREATE UNIQUE INDEX revision_unique ON revision (rev_page, rev_id);
CREATE INDEX rev_text_id_idx ON revision (rev_text_id);
CREATE INDEX page_props_propname ON page_props (pp_propname);
CREATE TABLE archive (
- ar_namespace SMALLINT NOT NULL,
- ar_title TEXT NOT NULL,
- ar_text TEXT, -- technically should be bytea, but not used anymore
- ar_page_id INTEGER NULL,
- ar_parent_id INTEGER NULL,
- ar_sha1 TEXT NOT NULL DEFAULT '',
- ar_comment TEXT,
- ar_user INTEGER NULL REFERENCES mwuser(user_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED,
- ar_user_text TEXT NOT NULL,
- ar_timestamp TIMESTAMPTZ NOT NULL,
- ar_minor_edit SMALLINT NOT NULL DEFAULT 0,
- ar_flags TEXT,
- ar_rev_id INTEGER,
- ar_text_id INTEGER,
- ar_deleted SMALLINT NOT NULL DEFAULT 0,
- ar_len INTEGER NULL
+ ar_namespace SMALLINT NOT NULL,
+ ar_title TEXT NOT NULL,
+ ar_text TEXT, -- technically should be bytea, but not used anymore
+ ar_page_id INTEGER NULL,
+ ar_parent_id INTEGER NULL,
+ ar_sha1 TEXT NOT NULL DEFAULT '',
+ ar_comment TEXT,
+ ar_user INTEGER NULL REFERENCES mwuser(user_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED,
+ ar_user_text TEXT NOT NULL,
+ ar_timestamp TIMESTAMPTZ NOT NULL,
+ ar_minor_edit SMALLINT NOT NULL DEFAULT 0,
+ ar_flags TEXT,
+ ar_rev_id INTEGER,
+ ar_text_id INTEGER,
+ ar_deleted SMALLINT NOT NULL DEFAULT 0,
+ ar_len INTEGER NULL,
+ ar_content_model TEXT,
+ ar_content_format TEXT
);
CREATE INDEX archive_name_title_timestamp ON archive (ar_namespace,ar_title,ar_timestamp);
CREATE INDEX archive_user_text ON archive (ar_user_text);
* @param $rev Revision
*/
public function processRevision( $rev ) {
+ $content = $rev->getContent( Revision::RAW );
+
+ if ( $content->getModel() !== CONTENT_MODEL_WIKITEXT ) {
+ return;
+ }
+
try {
- $this->mPreprocessor->preprocessToObj( $rev->getText(), 0 );
+ $this->mPreprocessor->preprocessToObj( strval( $content->getNativeData() ), 0 );
}
catch(Exception $e) {
$this->error("Caught exception " . $e->getMessage() . " in " . $rev->getTitle()->getPrefixedText() );
$this->error( "Invalid username", true );
}
+ // @todo FIXME: This is reset 7 lines down.
$restrictions = array( 'edit' => $protection, 'move' => $protection );
$t = Title::newFromText( $this->getArg() );
return;
}
- $text = $page->getRawText();
- if ( $text === false ) {
+ $content = $page->getContent( REVISION::RAW );
+ if ( null === false ) {
return;
}
$dbw = wfGetDB( DB_MASTER );
$dbw->begin( __METHOD__ );
- $options = ParserOptions::newFromUserAndLang( new User, $wgContLang );
- $parserOutput = $wgParser->parse( $text, $page->getTitle(), $options, true, true, $page->getLatest() );
- $update = new LinksUpdate( $page->getTitle(), $parserOutput, false );
- $update->doUpdate();
+ $updates = $content->getSecondaryDataUpdates( $page->getTitle() );
+ DataUpdate::runUpdates( $updates );
$dbw->commit( __METHOD__ );
}
$this->output( sprintf( "%s\n", $filename, $display ) );
$user = new User();
- $parser = new $wgParserConf['class']();
$options = ParserOptions::newFromUser( $user );
- $output = $parser->parse( $rev->getText(), $title, $options );
+ $content = $rev->getContent();
+ $output = $content->getParserOutput( $title, null, $options );
file_put_contents( $filename,
"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" " .
public function execute() {
global $wgTitle;
+
if ( $this->hasOption( 'procs' ) ) {
$procs = intval( $this->getOption( 'procs' ) );
if ( $procs < 1 || $procs > 1000 ) {
$dbw = wfGetDB( DB_MASTER );
$n = 0;
- if ( $type === false ) {
- $conds = Job::defaultQueueConditions( );
- } else {
- $conds = array( 'job_cmd' => $type );
- }
-
- while ( $dbw->selectField( 'job', 'job_id', $conds, 'runJobs.php' ) ) {
- $offset = 0;
- for ( ; ; ) {
- $job = !$type ? Job::pop( $offset ) : Job::pop_type( $type );
-
- if ( !$job ) {
- break;
- }
-
- wfWaitForSlaves();
+ $group = JobQueueGroup::singleton();
+ do {
+ $job = ( $type === false )
+ ? $group->pop() // job from any queue
+ : $group->get( $type )->pop(); // job from a single queue
+ if ( $job ) { // found a job
+ // Perform the job (logging success/failure and runtime)...
$t = microtime( true );
- $offset = $job->id;
$this->runJobsLog( $job->toString() . " STARTING" );
$status = $job->run();
+ $group->ack( $job ); // done
$t = microtime( true ) - $t;
$timeMs = intval( $t * 1000 );
if ( !$status ) {
} else {
$this->runJobsLog( $job->toString() . " t=$timeMs good" );
}
-
- if ( $maxJobs && ++$n > $maxJobs ) {
+ // Break out if we hit the job count or wall time limits...
+ if ( $maxJobs && ++$n >= $maxJobs ) {
break 2;
}
- if ( $maxTime && time() - $startTime > $maxTime ) {
+ if ( $maxTime && ( time() - $startTime ) > $maxTime ) {
break 2;
}
+ // Don't let any slaves/backups fall behind...
+ $group->get( $type )->waitForBackups();
}
- }
+ } while ( $job ); // stop when there are no jobs
}
/**
* Checks given files for correctness of SQL syntax. MySQL DDL will be converted to
* SQLite-compatible during processing.
* Will throw exceptions on SQL errors
+ * @param $files
+ * @throws MWException
* @return mixed true if no error or error string in case of errors
*/
public static function checkSqlSyntax( $files ) {
--- /dev/null
+ALTER TABLE /*_*/job ADD COLUMN job_random integer unsigned NOT NULL default 0;
+ALTER TABLE /*_*/job ADD COLUMN job_token varbinary(32) NOT NULL default '';
+ALTER TABLE /*_*/job ADD COLUMN job_sha1 varbinary(32) NOT NULL default '';
+ALTER TABLE /*_*/job ADD COLUMN job_token_timestamp varbinary(14) NULL default NULL;
+
+CREATE INDEX /*i*/job_sha1 ON /*_*/job (job_sha1);
+CREATE INDEX /*i*/job_cmd_token ON /*_*/job (job_cmd,job_token,job_random);
+
--- /dev/null
+-- Patch to add the sites and site_identifiers tables.
+-- Licence: GNU GPL v2+
+-- Author: Jeroen De Dauw < jeroendedauw@gmail.com >
+
+
+-- Holds all the sites known to the wiki.
+CREATE TABLE IF NOT EXISTS /*_*/sites (
+-- Numeric id of the site
+ site_id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
+
+ -- Global identifier for the site, ie 'enwiktionary'
+ site_global_key varbinary(32) NOT NULL,
+
+ -- Type of the site, ie 'mediawiki'
+ site_type varbinary(32) NOT NULL,
+
+ -- Group of the site, ie 'wikipedia'
+ site_group varbinary(32) NOT NULL,
+
+ -- Source of the site data, ie 'local', 'wikidata', 'my-magical-repo'
+ site_source varbinary(32) NOT NULL,
+
+ -- Language code of the sites primary language.
+ site_language varbinary(32) NOT NULL,
+
+ -- Protocol of the site, ie 'http://', 'irc://', '//'
+ -- This field is an index for lookups and is build from type specific data in site_data.
+ site_protocol varbinary(32) NOT NULL,
+
+ -- Domain of the site in reverse order, ie 'org.mediawiki.www.'
+ -- This field is an index for lookups and is build from type specific data in site_data.
+ site_domain VARCHAR(255) NOT NULL,
+
+ -- Type dependent site data.
+ site_data BLOB NOT NULL,
+
+ -- If site.tld/path/key:pageTitle should forward users to the page on
+ -- the actual site, where "key" is the local identifier.
+ site_forward bool NOT NULL,
+
+ -- Type dependent site config.
+ -- For instance if template transclusion should be allowed if it's a MediaWiki.
+ site_config BLOB NOT NULL
+) /*$wgDBTableOptions*/;
+
+CREATE UNIQUE INDEX /*i*/sites_global_key ON /*_*/sites (site_global_key);
+CREATE INDEX /*i*/sites_type ON /*_*/sites (site_type);
+CREATE INDEX /*i*/sites_group ON /*_*/sites (site_group);
+CREATE INDEX /*i*/sites_source ON /*_*/sites (site_source);
+CREATE INDEX /*i*/sites_language ON /*_*/sites (site_language);
+CREATE INDEX /*i*/sites_protocol ON /*_*/sites (site_protocol);
+CREATE INDEX /*i*/sites_domain ON /*_*/sites (site_domain);
+CREATE INDEX /*i*/sites_forward ON /*_*/sites (site_forward);
+
+
+
+-- Links local site identifiers to their corresponding site.
+CREATE TABLE IF NOT EXISTS /*_*/site_identifiers (
+ -- Key on site.site_id
+ si_site INT UNSIGNED NOT NULL,
+
+ -- local key type, ie 'interwiki' or 'langlink'
+ si_type varbinary(32) NOT NULL,
+
+ -- local key value, ie 'en' or 'wiktionary'
+ si_key varbinary(32) NOT NULL
+) /*$wgDBTableOptions*/;
+
+CREATE UNIQUE INDEX /*i*/site_ids_type ON /*_*/site_identifiers (si_type, si_key);
+CREATE INDEX /*i*/site_ids_site ON /*_*/site_identifiers (si_site);
+CREATE INDEX /*i*/site_ids_key ON /*_*/site_identifiers (si_key);
\ No newline at end of file
--- /dev/null
+ALTER TABLE /*$wgDBprefix*/archive DROP COLUMN ar_content_model;
+ALTER TABLE /*$wgDBprefix*/archive DROP COLUMN ar_content_format;
+
+ALTER TABLE /*$wgDBprefix*/revision DROP COLUMN rev_content_model;
+ALTER TABLE /*$wgDBprefix*/revision DROP COLUMN rev_content_format;
+
+ALTER TABLE /*$wgDBprefix*/page DROP COLUMN page_content_model;
$t = -microtime( true );
foreach ( $res as $row ) {
$revision = new Revision( $row );
- $text = $revision->getText();
+ $text = $revision->getSerializedData();
$uncompressedSize += strlen( $text );
$hashes[$row->rev_id] = md5( $text );
$keys[$row->rev_id] = $blob->addItem( $text );
page_latest int unsigned NOT NULL,
-- Uncompressed length in bytes of the page's current source text.
- page_len int unsigned NOT NULL
+ page_len int unsigned NOT NULL,
+
+ -- content model, see CONTENT_MODEL_XXX constants
+ page_content_model varbinary(32) DEFAULT NULL
) /*$wgDBTableOptions*/;
CREATE UNIQUE INDEX /*i*/name_title ON /*_*/page (page_namespace,page_title);
rev_parent_id int unsigned default NULL,
-- SHA-1 text content hash in base-36
- rev_sha1 varbinary(32) NOT NULL default ''
+ rev_sha1 varbinary(32) NOT NULL default '',
+
+ -- content model, see CONTENT_MODEL_XXX constants
+ rev_content_model varbinary(32) DEFAULT NULL,
+
+ -- content format, see CONTENT_FORMAT_XXX constants
+ rev_content_format varbinary(64) DEFAULT NULL
) /*$wgDBTableOptions*/ MAX_ROWS=10000000 AVG_ROW_LENGTH=1024;
-- In case tables are created as MyISAM, use row hints for MySQL <5.0 to avoid 4GB limit
ar_parent_id int unsigned default NULL,
-- SHA-1 text content hash in base-36
- ar_sha1 varbinary(32) NOT NULL default ''
+ ar_sha1 varbinary(32) NOT NULL default '',
+
+ -- content model, see CONTENT_MODEL_XXX constants
+ ar_content_model varbinary(32) DEFAULT NULL,
+
+ -- content format, see CONTENT_FORMAT_XXX constants
+ ar_content_format varbinary(64) DEFAULT NULL
+
) /*$wgDBTableOptions*/;
CREATE INDEX /*i*/name_title_timestamp ON /*_*/archive (ar_namespace,ar_title,ar_timestamp);
fa_timestamp binary(14) default '',
-- Visibility of deleted revisions, bitfield
- fa_deleted tinyint unsigned NOT NULL default 0
+ fa_deleted tinyint unsigned NOT NULL default 0,
+
+ -- sha1 hash of file content
+ fa_sha1 varbinary(32) NOT NULL default ''
) /*$wgDBTableOptions*/;
-- pick out by image name
CREATE INDEX /*i*/fa_deleted_timestamp ON /*_*/filearchive (fa_deleted_timestamp);
-- sort by uploader
CREATE INDEX /*i*/fa_user_timestamp ON /*_*/filearchive (fa_user_text,fa_timestamp);
+-- find file by sha1, 10 bytes will be enough for hashes to be indexed
+CREATE INDEX /*i*/fa_sha1 ON /*_*/filearchive (fa_sha1(10));
--
-- Any other parameters to the command
-- Stored as a PHP serialized array, or an empty string if there are no parameters
- job_params blob NOT NULL
+ job_params blob NOT NULL,
+
+ -- Random, non-unique, number used for concurrent job acquisition
+ job_random integer unsigned NOT NULL default 0,
+
+ -- Field that conveys process locks on rows via process UUIDs
+ job_token varbinary(32) NOT NULL default '',
+
+ -- Timestamp when the job was locked
+ job_token_timestamp varbinary(14) NULL default NULL,
+
+ -- Base 36 SHA1 of the job parameters relevant to detecting duplicates
+ job_sha1 varbinary(32) NOT NULL default ''
) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/job_sha1 ON /*_*/job (job_sha1);
+CREATE INDEX /*i*/job_cmd_token ON /*_*/job (job_cmd,job_token,job_random);
CREATE INDEX /*i*/job_cmd ON /*_*/job (job_cmd, job_namespace, job_title, job_params(128));
CREATE INDEX /*i*/job_timestamp ON /*_*/job (job_timestamp);
-- Should cover *most* configuration - strings, ints, bools, etc.
CREATE INDEX /*i*/cf_name_value ON /*_*/config (cf_name,cf_value(255));
+-- Holds all the sites known to the wiki.
+CREATE TABLE /*_*/sites (
+-- Numeric id of the site
+ site_id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
+
+ -- Global identifier for the site, ie 'enwiktionary'
+ site_global_key varbinary(32) NOT NULL,
+
+ -- Type of the site, ie 'mediawiki'
+ site_type varbinary(32) NOT NULL,
+
+ -- Group of the site, ie 'wikipedia'
+ site_group varbinary(32) NOT NULL,
+
+ -- Source of the site data, ie 'local', 'wikidata', 'my-magical-repo'
+ site_source varbinary(32) NOT NULL,
+
+ -- Language code of the sites primary language.
+ site_language varbinary(32) NOT NULL,
+
+ -- Protocol of the site, ie 'http://', 'irc://', '//'
+ -- This field is an index for lookups and is build from type specific data in site_data.
+ site_protocol varbinary(32) NOT NULL,
+
+ -- Domain of the site in reverse order, ie 'org.mediawiki.www.'
+ -- This field is an index for lookups and is build from type specific data in site_data.
+ site_domain VARCHAR(255) NOT NULL,
+
+ -- Type dependent site data.
+ site_data BLOB NOT NULL,
+
+ -- If site.tld/path/key:pageTitle should forward users to the page on
+ -- the actual site, where "key" is the local identifier.
+ site_forward bool NOT NULL,
+
+ -- Type dependent site config.
+ -- For instance if template transclusion should be allowed if it's a MediaWiki.
+ site_config BLOB NOT NULL
+) /*$wgDBTableOptions*/;
+
+CREATE UNIQUE INDEX /*i*/sites_global_key ON /*_*/sites (site_global_key);
+CREATE INDEX /*i*/sites_type ON /*_*/sites (site_type);
+CREATE INDEX /*i*/sites_group ON /*_*/sites (site_group);
+CREATE INDEX /*i*/sites_source ON /*_*/sites (site_source);
+CREATE INDEX /*i*/sites_language ON /*_*/sites (site_language);
+CREATE INDEX /*i*/sites_protocol ON /*_*/sites (site_protocol);
+CREATE INDEX /*i*/sites_domain ON /*_*/sites (site_domain);
+CREATE INDEX /*i*/sites_forward ON /*_*/sites (site_forward);
+
+-- Links local site identifiers to their corresponding site.
+CREATE TABLE /*_*/site_identifiers (
+ -- Key on site.site_id
+ si_site INT UNSIGNED NOT NULL,
+
+ -- local key type, ie 'interwiki' or 'langlink'
+ si_type varbinary(32) NOT NULL,
+
+ -- local key value, ie 'en' or 'wiktionary'
+ si_key varbinary(32) NOT NULL
+) /*$wgDBTableOptions*/;
+
+CREATE UNIQUE INDEX /*i*/site_ids_type ON /*_*/site_identifiers (si_type, si_key);
+CREATE INDEX /*i*/site_ids_site ON /*_*/site_identifiers (si_site);
+CREATE INDEX /*i*/site_ids_key ON /*_*/site_identifiers (si_key);
+
-- vim: sw=2 sts=2 et
$updater->doUpdates( $updates );
foreach( $updater->getPostDatabaseUpdateMaintenance() as $maint ) {
- if ( $updater->updateRowExists( $maint ) ) {
+ $child = $this->runChild( $maint );
+
+ // LoggedUpdateMaintenance is checking the updatelog itself
+ $isLoggedUpdate = ( $child instanceof LoggedUpdateMaintenance );
+
+ if ( !$isLoggedUpdate && $updater->updateRowExists( $maint ) ) {
continue;
}
- $child = $this->runChild( $maint );
$child->execute();
- $updater->insertUpdateRow( $maint );
+ if ( !$isLoggedUpdate ) {
+ $updater->insertUpdateRow( $maint );
+ }
}
$this->output( "\nDone.\n" );
* @file
*/
-require './opensearch_desc.php';
+require './opensearch_desc.php';
'jquery.getAttrs' => array(
'scripts' => 'resources/jquery/jquery.getAttrs.js',
),
+ 'jquery.hidpi' => array(
+ 'scripts' => 'resources/jquery/jquery.hidpi.js',
+ ),
'jquery.highlightText' => array(
'scripts' => 'resources/jquery/jquery.highlightText.js',
'dependencies' => 'jquery.mwExtension',
'feedback-bugnew',
),
),
+ 'mediawiki.hidpi' => array(
+ 'scripts' => 'resources/mediawiki/mediawiki.hidpi.js',
+ 'dependencies' => array(
+ 'jquery.hidpi',
+ ),
+ ),
'mediawiki.htmlform' => array(
'scripts' => 'resources/mediawiki/mediawiki.htmlform.js',
),
/*!
- * jQuery UI Effects Blind 1.8.23
+ * jQuery UI Effects Blind 1.8.24
*
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
/*!
- * jQuery UI Effects Bounce 1.8.23
+ * jQuery UI Effects Bounce 1.8.24
*
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
/*!
- * jQuery UI Effects Clip 1.8.23
+ * jQuery UI Effects Clip 1.8.24
*
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
/*!
- * jQuery UI Effects 1.8.23
+ * jQuery UI Effects 1.8.24
*
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
/******************************************************************************/
$.extend($.effects, {
- version: "1.8.23",
+ version: "1.8.24",
// Saves a set of properties in a data storage
save: function(element, set) {
/*!
- * jQuery UI Effects Drop 1.8.23
+ * jQuery UI Effects Drop 1.8.24
*
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
/*!
- * jQuery UI Effects Explode 1.8.23
+ * jQuery UI Effects Explode 1.8.24
*
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
/*!
- * jQuery UI Effects Fade 1.8.23
+ * jQuery UI Effects Fade 1.8.24
*
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
/*!
- * jQuery UI Effects Fold 1.8.23
+ * jQuery UI Effects Fold 1.8.24
*
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
/*!
- * jQuery UI Effects Highlight 1.8.23
+ * jQuery UI Effects Highlight 1.8.24
*
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
/*!
- * jQuery UI Effects Pulsate 1.8.23
+ * jQuery UI Effects Pulsate 1.8.24
*
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
/*!
- * jQuery UI Effects Scale 1.8.23
+ * jQuery UI Effects Scale 1.8.24
*
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
/*!
- * jQuery UI Effects Shake 1.8.23
+ * jQuery UI Effects Shake 1.8.24
*
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
/*!
- * jQuery UI Effects Slide 1.8.23
+ * jQuery UI Effects Slide 1.8.24
*
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
/*!
- * jQuery UI Effects Transfer 1.8.23
+ * jQuery UI Effects Transfer 1.8.24
*
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
currentText: '今天',
monthNames: ['一月','二月','三月','四月','五月','六月',
'七月','八月','九月','十月','十一月','十二月'],
- monthNamesShort: ['一','二','三','四','五','六',
- '七','八','九','十','十一','十二'],
+ monthNamesShort: ['一月','二月','三月','四月','五月','六月',
+ '七月','八月','九月','十月','十一月','十二月'],
dayNames: ['星期日','星期一','星期二','星期三','星期四','星期五','星期六'],
dayNamesShort: ['周日','周一','周二','周三','周四','周五','周六'],
dayNamesMin: ['日','一','二','三','四','五','六'],
currentText: '今天',
monthNames: ['一月','二月','三月','四月','五月','六月',
'七月','八月','九月','十月','十一月','十二月'],
- monthNamesShort: ['一','二','三','四','五','六',
- '七','八','九','十','十一','十二'],
+ monthNamesShort: ['一月','二月','三月','四月','五月','六月',
+ '七月','八月','九月','十月','十一月','十二月'],
dayNames: ['星期日','星期一','星期二','星期三','星期四','星期五','星期六'],
dayNamesShort: ['周日','周一','周二','周三','周四','周五','周六'],
dayNamesMin: ['日','一','二','三','四','五','六'],
currentText: '今天',
monthNames: ['一月','二月','三月','四月','五月','六月',
'七月','八月','九月','十月','十一月','十二月'],
- monthNamesShort: ['一','二','三','四','五','六',
- '七','八','九','十','十一','十二'],
+ monthNamesShort: ['一月','二月','三月','四月','五月','六月',
+ '七月','八月','九月','十月','十一月','十二月'],
dayNames: ['星期日','星期一','星期二','星期三','星期四','星期五','星期六'],
dayNamesShort: ['周日','周一','周二','周三','周四','周五','周六'],
dayNamesMin: ['日','一','二','三','四','五','六'],
/*!
- * jQuery UI Accordion 1.8.23
+ * jQuery UI Accordion 1.8.24
*
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
});
$.extend( $.ui.accordion, {
- version: "1.8.23",
+ version: "1.8.24",
animations: {
slide: function( options, additions ) {
options = $.extend({
/*!
- * jQuery UI Autocomplete 1.8.23
+ * jQuery UI Autocomplete 1.8.24
*
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
/*!
- * jQuery UI Button 1.8.23
+ * jQuery UI Button 1.8.24
*
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
/*!
- * jQuery UI 1.8.23
+ * jQuery UI 1.8.24
*
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
}
$.extend( $.ui, {
- version: "1.8.23",
+ version: "1.8.24",
keyCode: {
ALT: 18,
/*!
- * jQuery UI Datepicker 1.8.23
+ * jQuery UI Datepicker 1.8.24
*
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
*/
(function( $, undefined ) {
-$.extend($.ui, { datepicker: { version: "1.8.23" } });
+$.extend($.ui, { datepicker: { version: "1.8.24" } });
var PROP_NAME = 'datepicker';
var dpuuid = new Date().getTime();
$.datepicker = new Datepicker(); // singleton instance
$.datepicker.initialized = false;
$.datepicker.uuid = new Date().getTime();
-$.datepicker.version = "1.8.23";
+$.datepicker.version = "1.8.24";
// Workaround for #4055
// Add another global to avoid noConflict issues with inline event handlers
/*!
- * jQuery UI Dialog 1.8.23
+ * jQuery UI Dialog 1.8.24
*
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
});
$.extend($.ui.dialog, {
- version: "1.8.23",
+ version: "1.8.24",
uuid: 0,
maxZ: 0,
/*!
- * jQuery UI Draggable 1.8.23
+ * jQuery UI Draggable 1.8.24
*
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
},
_mouseUp: function(event) {
- if (this.options.iframeFix === true) {
- $("div.ui-draggable-iframeFix").each(function() {
- this.parentNode.removeChild(this);
- }); //Remove frame helpers
- }
+ //Remove frame helpers
+ $("div.ui-draggable-iframeFix").each(function() {
+ this.parentNode.removeChild(this);
+ });
//If the ddmanager is used for droppables, inform the manager that dragging has stopped (see #5003)
if( $.ui.ddmanager ) $.ui.ddmanager.dragStop(this, event);
});
$.extend($.ui.draggable, {
- version: "1.8.23"
+ version: "1.8.24"
});
$.ui.plugin.add("draggable", "connectToSortable", {
/*!
- * jQuery UI Droppable 1.8.23
+ * jQuery UI Droppable 1.8.24
*
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
});
$.extend($.ui.droppable, {
- version: "1.8.23"
+ version: "1.8.24"
});
$.ui.intersect = function(draggable, droppable, toleranceMode) {
var parentInstance;
if (this.options.greedy) {
- var parent = this.element.parents(':data(droppable):eq(0)');
+ // find droppable parents with same scope
+ var scope = this.options.scope;
+ var parent = this.element.parents(':data(droppable)').filter(function () {
+ return $.data(this, 'droppable').options.scope === scope;
+ });
+
if (parent.length) {
parentInstance = $.data(parent[0], 'droppable');
parentInstance.greedyChild = (c == 'isover' ? 1 : 0);
/*!
- * jQuery UI Mouse 1.8.23
+ * jQuery UI Mouse 1.8.24
*
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
/*!
- * jQuery UI Position 1.8.23
+ * jQuery UI Position 1.8.24
*
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
/*!
- * jQuery UI Progressbar 1.8.23
+ * jQuery UI Progressbar 1.8.24
*
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
});
$.extend( $.ui.progressbar, {
- version: "1.8.23"
+ version: "1.8.24"
});
})( jQuery );
/*!
- * jQuery UI Resizable 1.8.23
+ * jQuery UI Resizable 1.8.24
*
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
});
$.extend($.ui.resizable, {
- version: "1.8.23"
+ version: "1.8.24"
});
/*
/*!
- * jQuery UI Selectable 1.8.23
+ * jQuery UI Selectable 1.8.24
*
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
});
$.extend($.ui.selectable, {
- version: "1.8.23"
+ version: "1.8.24"
});
})(jQuery);
/*!
- * jQuery UI Slider 1.8.23
+ * jQuery UI Slider 1.8.24
*
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
});
$.extend( $.ui.slider, {
- version: "1.8.23"
+ version: "1.8.24"
});
}(jQuery));
/*!
- * jQuery UI Sortable 1.8.23
+ * jQuery UI Sortable 1.8.24
*
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
var item = this.items[i], itemElement = item.item[0], intersection = this._intersectsWithPointer(item);
if (!intersection) continue;
- if(itemElement != this.currentItem[0] //cannot intersect with itself
+ // Only put the placeholder inside the current Container, skip all
+ // items form other containers. This works because when moving
+ // an item from one container to another the
+ // currentContainer is switched before the placeholder is moved.
+ //
+ // Without this moving items in "sub-sortables" can cause the placeholder to jitter
+ // beetween the outer and inner container.
+ if (item.instance !== this.currentContainer) continue;
+
+ if (itemElement != this.currentItem[0] //cannot intersect with itself
&& this.placeholder[intersection == 1 ? "next" : "prev"]()[0] != itemElement //no useless actions that have been done before
&& !$.ui.contains(this.placeholder[0], itemElement) //no action if the item moved is the parent of the item checked
&& (this.options.type == 'semi-dynamic' ? !$.ui.contains(this.element[0], itemElement) : true)
if(this.fromOutside && !noPropagation) delayedTriggers.push(function(event) { this._trigger("receive", event, this._uiHash(this.fromOutside)); });
if((this.fromOutside || this.domPosition.prev != this.currentItem.prev().not(".ui-sortable-helper")[0] || this.domPosition.parent != this.currentItem.parent()[0]) && !noPropagation) delayedTriggers.push(function(event) { this._trigger("update", event, this._uiHash()); }); //Trigger update callback if the DOM position has changed
- if(!$.ui.contains(this.element[0], this.currentItem[0])) { //Node was moved out of the current element
- if(!noPropagation) delayedTriggers.push(function(event) { this._trigger("remove", event, this._uiHash()); });
- for (var i = this.containers.length - 1; i >= 0; i--){
- if($.ui.contains(this.containers[i].element[0], this.currentItem[0]) && !noPropagation) {
- delayedTriggers.push((function(c) { return function(event) { c._trigger("receive", event, this._uiHash(this)); }; }).call(this, this.containers[i]));
- delayedTriggers.push((function(c) { return function(event) { c._trigger("update", event, this._uiHash(this)); }; }).call(this, this.containers[i]));
- }
- };
- };
+
+ // Check if the items Container has Changed and trigger appropriate
+ // events.
+ if (this !== this.currentContainer) {
+ if(!noPropagation) {
+ delayedTriggers.push(function(event) { this._trigger("remove", event, this._uiHash()); });
+ delayedTriggers.push((function(c) { return function(event) { c._trigger("receive", event, this._uiHash(this)); }; }).call(this, this.currentContainer));
+ delayedTriggers.push((function(c) { return function(event) { c._trigger("update", event, this._uiHash(this)); }; }).call(this, this.currentContainer));
+ }
+ }
//Post events to containers
for (var i = this.containers.length - 1; i >= 0; i--){
});
$.extend($.ui.sortable, {
- version: "1.8.23"
+ version: "1.8.24"
});
})(jQuery);
/*!
- * jQuery UI Tabs 1.8.23
+ * jQuery UI Tabs 1.8.24
*
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
});
$.extend( $.ui.tabs, {
- version: "1.8.23"
+ version: "1.8.24"
});
/*
/*!
- * jQuery UI Widget 1.8.23
+ * jQuery UI Widget 1.8.24
*
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
/*!
- * jQuery UI Accordion 1.8.23
+ * jQuery UI Accordion 1.8.24
*
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
/*!
- * jQuery UI Autocomplete 1.8.23
+ * jQuery UI Autocomplete 1.8.24
*
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* html .ui-autocomplete { width:1px; } /* without this, the menu expands to 100% in IE6 */
/*
- * jQuery UI Menu 1.8.23
+ * jQuery UI Menu 1.8.24
*
* Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
/*!
- * jQuery UI Button 1.8.23
+ * jQuery UI Button 1.8.24
*
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
/*!
- * jQuery UI CSS Framework 1.8.23
+ * jQuery UI CSS Framework 1.8.24
*
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
/*!
- * jQuery UI Datepicker 1.8.23
+ * jQuery UI Datepicker 1.8.24
*
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
/*!
- * jQuery UI Dialog 1.8.23
+ * jQuery UI Dialog 1.8.24
*
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
/*!
- * jQuery UI Progressbar 1.8.23
+ * jQuery UI Progressbar 1.8.24
*
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
/*!
- * jQuery UI Resizable 1.8.23
+ * jQuery UI Resizable 1.8.24
*
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
/*!
- * jQuery UI Selectable 1.8.23
+ * jQuery UI Selectable 1.8.24
*
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
/*!
- * jQuery UI Slider 1.8.23
+ * jQuery UI Slider 1.8.24
*
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
/*!
- * jQuery UI Tabs 1.8.23
+ * jQuery UI Tabs 1.8.24
*
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
/*!
- * jQuery UI CSS Framework 1.8.23
+ * jQuery UI CSS Framework 1.8.24
*
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
/**
- * Plugin that automatically truncates the plain text contents of an element and adds an ellipsis
+ * Plugin that automatically truncates the plain text contents of an element
+ * and adds an ellipsis.
*/
( function ( $ ) {
var
// Cache ellipsed substrings for every string-width-position combination
- cache = { },
+ cache = {},
// Use a separate cache when match highlighting is enabled
- matchTextCache = { };
+ matchTextCache = {};
$.fn.autoEllipsis = function ( options ) {
options = $.extend( {
hasSpan: false,
matchText: null
}, options );
- $(this).each( function () {
- var $container, $trimmableText,
+
+ return this.each( function () {
+ var $trimmableText,
text, trimmableText, w, pw,
l, r, i, side, m,
- $el = $(this);
+ // container element - used for measuring against
+ $container = $(this);
+
if ( options.restoreText ) {
- if ( !$el.data( 'autoEllipsis.originalText' ) ) {
- $el.data( 'autoEllipsis.originalText', $el.text() );
+ if ( !$container.data( 'autoEllipsis.originalText' ) ) {
+ $container.data( 'autoEllipsis.originalText', $container.text() );
} else {
- $el.text( $el.data( 'autoEllipsis.originalText' ) );
+ $container.text( $container.data( 'autoEllipsis.originalText' ) );
}
}
- // container element - used for measuring against
- $container = $el;
-
// trimmable text element - only the text within this element will be trimmed
if ( options.hasSpan ) {
- $trimmableText = $el.children( options.selector );
+ $trimmableText = $container.children( options.selector );
} else {
$trimmableText = $( '<span>' )
.css( 'whiteSpace', 'nowrap' )
- .text( $el.text() );
- $el
+ .text( $container.text() );
+ $container
.empty()
.append( $trimmableText );
}
--- /dev/null
+/**
+ * Responsive images based on 'srcset' and 'window.devicePixelRatio' emulation where needed.
+ *
+ * Call $().hidpi() on a document or part of a document to replace image srcs in that section.
+ *
+ * $.devicePixelRatio() can be used to supplement window.devicePixelRatio with support on
+ * some additional browsers.
+ */
+( function ( $ ) {
+
+/**
+ * Detect reported or approximate device pixel ratio.
+ * 1.0 means 1 CSS pixel is 1 hardware pixel
+ * 2.0 means 1 CSS pixel is 2 hardware pixels
+ * etc
+ *
+ * Uses window.devicePixelRatio if available, or CSS media queries on IE.
+ *
+ * @method
+ * @returns {number} Device pixel ratio
+ */
+$.devicePixelRatio = function () {
+ if ( window.devicePixelRatio !== undefined ) {
+ // Most web browsers:
+ // * WebKit (Safari, Chrome, Android browser, etc)
+ // * Opera
+ // * Firefox 18+
+ return window.devicePixelRatio;
+ } else if ( window.msMatchMedia !== undefined ) {
+ // Windows 8 desktops / tablets, probably Windows Phone 8
+ //
+ // IE 10 doesn't report pixel ratio directly, but we can get the
+ // screen DPI and divide by 96. We'll bracket to [1, 1.5, 2.0] for
+ // simplicity, but you may get different values depending on zoom
+ // factor, size of screen and orientation in Metro IE.
+ if ( window.msMatchMedia( '(min-resolution: 192dpi)' ).matches ) {
+ return 2;
+ } else if ( window.msMatchMedia( '(min-resolution: 144dpi)' ).matches ) {
+ return 1.5;
+ } else {
+ return 1;
+ }
+ } else {
+ // Legacy browsers...
+ // Assume 1 if unknown.
+ return 1;
+ }
+};
+
+/**
+ * Implement responsive images based on srcset attributes, if browser has no
+ * native srcset support.
+ *
+ * @method
+ * @returns {jQuery} This selection
+ */
+$.fn.hidpi = function () {
+ var $target = this,
+ // @todo add support for dpi media query checks on Firefox, IE
+ devicePixelRatio = $.devicePixelRatio(),
+ testImage = new Image();
+
+ if ( devicePixelRatio > 1 && testImage.srcset === undefined ) {
+ // No native srcset support.
+ $target.find( 'img' ).each( function () {
+ var $img = $( this ),
+ srcset = $img.attr( 'srcset' ),
+ match;
+ if ( typeof srcset === 'string' && srcset !== '' ) {
+ match = $.matchSrcSet( devicePixelRatio, srcset );
+ if (match !== null ) {
+ $img.attr( 'src', match );
+ }
+ }
+ });
+ }
+
+ return $target;
+};
+
+/**
+ * Match a srcset entry for the given device pixel ratio
+ *
+ * @param {number} devicePixelRatio
+ * @param {string} srcset
+ * @return {mixed} null or the matching src string
+ *
+ * Exposed for testing.
+ */
+$.matchSrcSet = function ( devicePixelRatio, srcset ) {
+ var candidates,
+ candidate,
+ bits,
+ src,
+ i,
+ ratioStr,
+ ratio,
+ selectedRatio = 1,
+ selectedSrc = null;
+ candidates = srcset.split( / *, */ );
+ for ( i = 0; i < candidates.length; i++ ) {
+ candidate = candidates[i];
+ bits = candidate.split( / +/ );
+ src = bits[0];
+ if ( bits.length > 1 && bits[1].charAt( bits[1].length - 1 ) === 'x' ) {
+ ratioStr = bits[1].substr( 0, bits[1].length - 1 );
+ ratio = parseFloat( ratioStr );
+ if ( ratio > devicePixelRatio ) {
+ // Too big, skip!
+ } else if ( ratio > selectedRatio ) {
+ selectedRatio = ratio;
+ selectedSrc = src;
+ }
+ }
+ }
+ return selectedSrc;
+};
+
+}( jQuery ) );
};
$.fn.highlightText = function ( matchString ) {
- return $( this ).each( function () {
+ return this.each( function () {
var $el = $( this );
$el.data( 'highlightText', { originalText: $el.text() } );
$.highlightText.splitAndHighlight( this, matchString );
/*!
- * jQuery JavaScript Library v1.8.1
+ * jQuery JavaScript Library v1.8.2
* http://jquery.com/
*
* Includes Sizzle.js
* Released under the MIT license
* http://jquery.org/license
*
- * Date: Thu Aug 30 2012 17:17:22 GMT-0400 (Eastern Daylight Time)
+ * Date: Thu Sep 20 2012 21:13:05 GMT-0400 (Eastern Daylight Time)
*/
(function( window, undefined ) {
var
selector: "",
// The current version of jQuery being used
- jquery: "1.8.1",
+ jquery: "1.8.2",
// The default length of a jQuery object is 0
length: 0,
},
nodeName: function( elem, name ) {
- return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase();
+ return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();
},
// args is for internal usage only
function( text ) {
return text == null ?
"" :
- text.toString().replace( rtrim, "" );
+ ( text + "" ).replace( rtrim, "" );
},
// results is for internal usage only
};
// Set the guid of unique handler to the same of original handler, so it can be removed
- proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++;
+ proxy.guid = fn.guid = fn.guid || jQuery.guid++;
return proxy;
},
// Get a promise for this deferred
// If obj is provided, the promise aspect is added to the object
promise: function( obj ) {
- return typeof obj === "object" ? jQuery.extend( obj, promise ) : promise;
+ return obj != null ? jQuery.extend( obj, promise ) : promise;
}
},
deferred = {};
a.style.cssText = "top:1px;float:left;opacity:.5";
// Can't get basic test support
- if ( !all || !all.length || !a ) {
+ if ( !all || !all.length ) {
return {};
}
deletedIds: [],
- // Please use with caution
+ // Remove at next major release (1.9/2.0)
uuid: 0,
// Unique for each copy of jQuery on the page
// Only DOM nodes need a new unique ID for each element since their data
// ends up in the global cache
if ( isNode ) {
- elem[ internalKey ] = id = jQuery.deletedIds.pop() || ++jQuery.uuid;
+ elem[ internalKey ] = id = jQuery.deletedIds.pop() || jQuery.guid++;
} else {
id = internalKey;
}
for ( l = attr.length; i < l; i++ ) {
name = attr[i].name;
- if ( name.indexOf( "data-" ) === 0 ) {
+ if ( !name.indexOf( "data-" ) ) {
name = jQuery.camelCase( name.substring(5) );
dataAttr( elem, name, data[ name ] );
setClass = " " + elem.className + " ";
for ( c = 0, cl = classNames.length; c < cl; c++ ) {
- if ( !~setClass.indexOf( " " + classNames[ c ] + " " ) ) {
+ if ( setClass.indexOf( " " + classNames[ c ] + " " ) < 0 ) {
setClass += classNames[ c ] + " ";
}
}
// loop over each item in the removal list
for ( c = 0, cl = removes.length; c < cl; c++ ) {
// Remove until there is nothing to remove,
- while ( className.indexOf(" " + removes[ c ] + " ") > -1 ) {
+ while ( className.indexOf(" " + removes[ c ] + " ") >= 0 ) {
className = className.replace( " " + removes[ c ] + " " , " " );
}
}
i = 0,
l = this.length;
for ( ; i < l; i++ ) {
- if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) {
+ if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) >= 0 ) {
return true;
}
}
return ret;
} else {
- elem.setAttribute( name, "" + value );
+ elem.setAttribute( name, value + "" );
return value;
}
return elem.style.cssText.toLowerCase() || undefined;
},
set: function( elem, value ) {
- return ( elem.style.cssText = "" + value );
+ return ( elem.style.cssText = value + "" );
}
};
}
handler: handler,
guid: handler.guid,
selector: selector,
+ needsContext: selector && jQuery.expr.match.needsContext.test( selector ),
namespace: namespaces.join(".")
}, handleObjIn );
}
// Note that this is a bare JS function and not a jQuery handler
handle = ontype && cur[ ontype ];
- if ( handle && jQuery.acceptData( cur ) && handle.apply( cur, data ) === false ) {
+ if ( handle && jQuery.acceptData( cur ) && handle.apply && handle.apply( cur, data ) === false ) {
event.preventDefault();
}
}
var i, j, cur, ret, selMatch, matched, matches, handleObj, sel, related,
handlers = ( (jQuery._data( this, "events" ) || {} )[ event.type ] || []),
delegateCount = handlers.delegateCount,
- args = [].slice.call( arguments ),
+ args = core_slice.call( arguments ),
run_all = !event.exclusive && !event.namespace,
special = jQuery.event.special[ event.type ] || {},
handlerQueue = [];
sel = handleObj.selector;
if ( selMatch[ sel ] === undefined ) {
- selMatch[ sel ] = jQuery( sel, this ).index( cur ) >= 0;
+ selMatch[ sel ] = handleObj.needsContext ?
+ jQuery( sel, this ).index( cur ) >= 0 :
+ jQuery.find( sel, this, null, [ cur ] ).length;
}
if ( selMatch[ sel ] ) {
matches.push( handleObj );
},
undelegate: function( selector, types, fn ) {
// ( namespace ) or ( selector, types [, fn] )
- return arguments.length == 1? this.off( selector, "**" ) : this.off( types, selector || "**", fn );
+ return arguments.length === 1 ? this.off( selector, "**" ) : this.off( types, selector || "**", fn );
},
trigger: function( type, data ) {
});
/*!\r
* Sizzle CSS Selector Engine\r
- * Copyright 2012 jQuery Foundation and other contributors\r
- * Released under the MIT license\r
- * http://sizzlejs.com/\r
+ * Copyright 2012 jQuery Foundation and other contributors\r
+ * Released under the MIT license\r
+ * http://sizzlejs.com/\r
*/\r
(function( window, undefined ) {\r
\r
-var dirruns,\r
- cachedruns,\r
+var cachedruns,\r
assertGetIdNotName,\r
Expr,\r
getText,\r
compile,\r
sortOrder,\r
hasDuplicate,\r
+ outermostContext,\r
\r
baseHasDuplicate = true,\r
strundefined = "undefined",\r
\r
expando = ( "sizcache" + Math.random() ).replace( ".", "" ),\r
\r
+ Token = String,\r
document = window.document,\r
docElem = document.documentElement,\r
+ dirruns = 0,\r
done = 0,\r
- slice = [].slice,\r
+ pop = [].pop,\r
push = [].push,\r
+ slice = [].slice,\r
+ // Use a stripped-down indexOf if a native one is unavailable\r
+ indexOf = [].indexOf || function( elem ) {\r
+ var i = 0,\r
+ len = this.length;\r
+ for ( ; i < len; i++ ) {\r
+ if ( this[i] === elem ) {\r
+ return i;\r
+ }\r
+ }\r
+ return -1;\r
+ },\r
\r
// Augment a function for special use by Sizzle\r
markFunction = function( fn, value ) {\r
- fn[ expando ] = value || true;\r
+ fn[ expando ] = value == null || value;\r
return fn;\r
},\r
\r
pseudos = ":(" + characterEncoding + ")(?:\\((?:(['\"])((?:\\\\.|[^\\\\])*?)\\2|([^()[\\]]*|(?:(?:" + attributes + ")|[^:]|\\\\.)*|.*))\\)|)",\r
\r
// For matchExpr.POS and matchExpr.needsContext\r
- pos = ":(nth|eq|gt|lt|first|last|even|odd)(?:\\(((?:-\\d)?\\d*)\\)|)(?=[^-]|$)",\r
+ pos = ":(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace +\r
+ "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)",\r
\r
// Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter\r
rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ),\r
"TAG": new RegExp( "^(" + characterEncoding.replace( "w", "w*" ) + ")" ),\r
"ATTR": new RegExp( "^" + attributes ),\r
"PSEUDO": new RegExp( "^" + pseudos ),\r
- "CHILD": new RegExp( "^:(only|nth|last|first)-child(?:\\(" + whitespace +\r
+ "POS": new RegExp( pos, "i" ),\r
+ "CHILD": new RegExp( "^:(only|nth|first|last)-child(?:\\(" + whitespace +\r
"*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace +\r
"*(\\d+)|))" + whitespace + "*\\)|)", "i" ),\r
- "POS": new RegExp( pos, "ig" ),\r
// For use in libraries implementing .is()\r
"needsContext": new RegExp( "^" + whitespace + "*[>+~]|" + pos, "i" )\r
},\r
slice.call( docElem.childNodes, 0 )[0].nodeType;\r
} catch ( e ) {\r
slice = function( i ) {\r
- var elem, results = [];\r
+ var elem,\r
+ results = [];\r
for ( ; (elem = this[i]); i++ ) {\r
results.push( elem );\r
}\r
var match, elem, xml, m,\r
nodeType = context.nodeType;\r
\r
- if ( nodeType !== 1 && nodeType !== 9 ) {\r
- return [];\r
- }\r
-\r
if ( !selector || typeof selector !== "string" ) {\r
return results;\r
}\r
\r
+ if ( nodeType !== 1 && nodeType !== 9 ) {\r
+ return [];\r
+ }\r
+\r
xml = isXML( context );\r
\r
if ( !xml && !seed ) {\r
}\r
\r
// All others\r
- return select( selector, context, results, seed, xml );\r
+ return select( selector.replace( rtrim, "$1" ), context, results, seed, xml );\r
}\r
\r
Sizzle.matches = function( expr, elements ) {\r
};\r
}\r
\r
+// Returns a function to use in pseudos for positionals\r
+function createPositionalPseudo( fn ) {\r
+ return markFunction(function( argument ) {\r
+ argument = +argument;\r
+ return markFunction(function( seed, matches ) {\r
+ var j,\r
+ matchIndexes = fn( [], seed.length, argument ),\r
+ i = matchIndexes.length;\r
+\r
+ // Match elements found at the specified indexes\r
+ while ( i-- ) {\r
+ if ( seed[ (j = matchIndexes[i]) ] ) {\r
+ seed[j] = !(matches[j] = seed[j]);\r
+ }\r
+ }\r
+ });\r
+ });\r
+}\r
+\r
/**\r
* Utility function for retrieving the text value of an array of DOM nodes\r
* @param {Array|Element} elem\r
return ret;\r
};\r
\r
-isXML = Sizzle.isXML = function isXML( elem ) {\r
+isXML = Sizzle.isXML = function( elem ) {\r
// documentElement is verified for cases where it doesn't yet exist\r
// (such as loading iframes in IE - #4833)\r
var documentElement = elem && (elem.ownerDocument || elem).documentElement;\r
};\r
\r
Sizzle.attr = function( elem, name ) {\r
- var attr,\r
+ var val,\r
xml = isXML( elem );\r
\r
if ( !xml ) {\r
name = name.toLowerCase();\r
}\r
- if ( Expr.attrHandle[ name ] ) {\r
- return Expr.attrHandle[ name ]( elem );\r
+ if ( (val = Expr.attrHandle[ name ]) ) {\r
+ return val( elem );\r
}\r
- if ( assertAttributes || xml ) {\r
+ if ( xml || assertAttributes ) {\r
return elem.getAttribute( name );\r
}\r
- attr = elem.getAttributeNode( name );\r
- return attr ?\r
+ val = elem.getAttributeNode( name );\r
+ return val ?\r
typeof elem[ name ] === "boolean" ?\r
elem[ name ] ? name : null :\r
- attr.specified ? attr.value : null :\r
+ val.specified ? val.value : null :\r
null;\r
};\r
\r
\r
match: matchExpr,\r
\r
- order: new RegExp( "ID|TAG" +\r
- (assertUsableName ? "|NAME" : "") +\r
- (assertUsableClassName ? "|CLASS" : "")\r
- ),\r
-\r
// IE6/7 return a modified href\r
attrHandle: assertHrefNotNormalized ?\r
{} :\r
return results;\r
},\r
\r
- "NAME": function( tag, context ) {\r
+ "NAME": assertUsableName && function( tag, context ) {\r
if ( typeof context.getElementsByName !== strundefined ) {\r
return context.getElementsByName( name );\r
}\r
},\r
\r
- "CLASS": function( className, context, xml ) {\r
+ "CLASS": assertUsableClassName && function( className, context, xml ) {\r
if ( typeof context.getElementsByClassName !== strundefined && !xml ) {\r
return context.getElementsByClassName( className );\r
}\r
},\r
\r
"CHILD": function( match ) {\r
- /* matches from matchExpr.CHILD\r
+ /* matches from matchExpr["CHILD"]\r
1 type (only|nth|...)\r
2 argument (even|odd|\d*|\d*n([+-]\d+)?|...)\r
3 xn-component of xn+y argument ([+-]?\d*n|)\r
return match;\r
},\r
\r
- "PSEUDO": function( match, context, xml ) {\r
+ "PSEUDO": function( match ) {\r
var unquoted, excess;\r
if ( matchExpr["CHILD"].test( match[0] ) ) {\r
return null;\r
// Only check arguments that contain a pseudo\r
if ( rpseudo.test(unquoted) &&\r
// Get excess from tokenize (recursively)\r
- (excess = tokenize( unquoted, context, xml, true )) &&\r
+ (excess = tokenize( unquoted, true )) &&\r
// advance to the next closing parenthesis\r
(excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) {\r
\r
},\r
\r
"ATTR": function( name, operator, check ) {\r
- if ( !operator ) {\r
- return function( elem ) {\r
- return Sizzle.attr( elem, name ) != null;\r
- };\r
- }\r
-\r
- return function( elem ) {\r
- var result = Sizzle.attr( elem, name ),\r
- value = result + "";\r
+ return function( elem, context ) {\r
+ var result = Sizzle.attr( elem, name );\r
\r
if ( result == null ) {\r
return operator === "!=";\r
}\r
-\r
- switch ( operator ) {\r
- case "=":\r
- return value === check;\r
- case "!=":\r
- return value !== check;\r
- case "^=":\r
- return check && value.indexOf( check ) === 0;\r
- case "*=":\r
- return check && value.indexOf( check ) > -1;\r
- case "$=":\r
- return check && value.substr( value.length - check.length ) === check;\r
- case "~=":\r
- return ( " " + value + " " ).indexOf( check ) > -1;\r
- case "|=":\r
- return value === check || value.substr( 0, check.length + 1 ) === check + "-";\r
+ if ( !operator ) {\r
+ return true;\r
}\r
+\r
+ result += "";\r
+\r
+ return operator === "=" ? result === check :\r
+ operator === "!=" ? result !== check :\r
+ operator === "^=" ? check && result.indexOf( check ) === 0 :\r
+ operator === "*=" ? check && result.indexOf( check ) > -1 :\r
+ operator === "$=" ? check && result.substr( result.length - check.length ) === check :\r
+ operator === "~=" ? ( " " + result + " " ).indexOf( check ) > -1 :\r
+ operator === "|=" ? result === check || result.substr( 0, check.length + 1 ) === check + "-" :\r
+ false;\r
};\r
},\r
\r
"CHILD": function( type, argument, first, last ) {\r
\r
if ( type === "nth" ) {\r
- var doneName = done++;\r
-\r
return function( elem ) {\r
- var parent, diff,\r
- count = 0,\r
- node = elem;\r
+ var node, diff,\r
+ parent = elem.parentNode;\r
\r
if ( first === 1 && last === 0 ) {\r
return true;\r
}\r
\r
- parent = elem.parentNode;\r
-\r
- if ( parent && (parent[ expando ] !== doneName || !elem.sizset) ) {\r
+ if ( parent ) {\r
+ diff = 0;\r
for ( node = parent.firstChild; node; node = node.nextSibling ) {\r
if ( node.nodeType === 1 ) {\r
- node.sizset = ++count;\r
- if ( node === elem ) {\r
+ diff++;\r
+ if ( elem === node ) {\r
break;\r
}\r
}\r
}\r
-\r
- parent[ expando ] = doneName;\r
}\r
\r
- diff = elem.sizset - last;\r
-\r
- if ( first === 0 ) {\r
- return diff === 0;\r
-\r
- } else {\r
- return ( diff % first === 0 && diff / first >= 0 );\r
- }\r
+ // Incorporate the offset (or cast to NaN), then check against cycle size\r
+ diff -= last;\r
+ return diff === first || ( diff % first === 0 && diff / first >= 0 );\r
};\r
}\r
\r
};\r
},\r
\r
- "PSEUDO": function( pseudo, argument, context, xml ) {\r
+ "PSEUDO": function( pseudo, argument ) {\r
// pseudo-class names are case-insensitive\r
// http://www.w3.org/TR/selectors/#pseudo-classes\r
// Prioritize by case sensitivity in case custom pseudos are added with uppercase letters\r
+ // Remember that setFilters inherits from pseudos\r
var args,\r
- fn = Expr.pseudos[ pseudo ] || Expr.pseudos[ pseudo.toLowerCase() ];\r
-\r
- if ( !fn ) {\r
- Sizzle.error( "unsupported pseudo: " + pseudo );\r
- }\r
+ fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] ||\r
+ Sizzle.error( "unsupported pseudo: " + pseudo );\r
\r
// The user may use createPseudo to indicate that\r
// arguments are needed to create the filter function\r
// just as Sizzle does\r
- if ( !fn[ expando ] ) {\r
- if ( fn.length > 1 ) {\r
- args = [ pseudo, pseudo, "", argument ];\r
- return function( elem ) {\r
+ if ( fn[ expando ] ) {\r
+ return fn( argument );\r
+ }\r
+\r
+ // But maintain support for old signatures\r
+ if ( fn.length > 1 ) {\r
+ args = [ pseudo, pseudo, "", argument ];\r
+ return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ?\r
+ markFunction(function( seed, matches ) {\r
+ var idx,\r
+ matched = fn( seed, argument ),\r
+ i = matched.length;\r
+ while ( i-- ) {\r
+ idx = indexOf.call( seed, matched[i] );\r
+ seed[ idx ] = !( matches[ idx ] = matched[i] );\r
+ }\r
+ }) :\r
+ function( elem ) {\r
return fn( elem, 0, args );\r
};\r
- }\r
- return fn;\r
}\r
\r
- return fn( argument, context, xml );\r
+ return fn;\r
}\r
},\r
\r
pseudos: {\r
- "not": markFunction(function( selector, context, xml ) {\r
+ "not": markFunction(function( selector ) {\r
// Trim the selector passed to compile\r
// to avoid treating leading and trailing\r
// spaces as combinators\r
- var matcher = compile( selector.replace( rtrim, "$1" ), context, xml );\r
+ var input = [],\r
+ results = [],\r
+ matcher = compile( selector.replace( rtrim, "$1" ) );\r
+\r
+ return matcher[ expando ] ?\r
+ markFunction(function( seed, matches, context, xml ) {\r
+ var elem,\r
+ unmatched = matcher( seed, null, xml, [] ),\r
+ i = seed.length;\r
+\r
+ // Match elements unmatched by `matcher`\r
+ while ( i-- ) {\r
+ if ( (elem = unmatched[i]) ) {\r
+ seed[i] = !(matches[i] = elem);\r
+ }\r
+ }\r
+ }) :\r
+ function( elem, context, xml ) {\r
+ input[0] = elem;\r
+ matcher( input, null, xml, results );\r
+ return !results.pop();\r
+ };\r
+ }),\r
+\r
+ "has": markFunction(function( selector ) {\r
return function( elem ) {\r
- return !matcher( elem );\r
+ return Sizzle( selector, elem ).length > 0;\r
+ };\r
+ }),\r
+\r
+ "contains": markFunction(function( text ) {\r
+ return function( elem ) {\r
+ return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1;\r
};\r
}),\r
\r
return true;\r
},\r
\r
- "contains": markFunction(function( text ) {\r
- return function( elem ) {\r
- return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1;\r
- };\r
- }),\r
-\r
- "has": markFunction(function( selector ) {\r
- return function( elem ) {\r
- return Sizzle( selector, elem ).length > 0;\r
- };\r
- }),\r
-\r
"header": function( elem ) {\r
return rheader.test( elem.nodeName );\r
},\r
\r
"active": function( elem ) {\r
return elem === elem.ownerDocument.activeElement;\r
- }\r
- },\r
-\r
- setFilters: {\r
- "first": function( elements, argument, not ) {\r
- return not ? elements.slice( 1 ) : [ elements[0] ];\r
},\r
\r
- "last": function( elements, argument, not ) {\r
- var elem = elements.pop();\r
- return not ? elements : [ elem ];\r
- },\r
+ // Positional types\r
+ "first": createPositionalPseudo(function( matchIndexes, length, argument ) {\r
+ return [ 0 ];\r
+ }),\r
\r
- "even": function( elements, argument, not ) {\r
- var results = [],\r
- i = not ? 1 : 0,\r
- len = elements.length;\r
- for ( ; i < len; i = i + 2 ) {\r
- results.push( elements[i] );\r
- }\r
- return results;\r
- },\r
+ "last": createPositionalPseudo(function( matchIndexes, length, argument ) {\r
+ return [ length - 1 ];\r
+ }),\r
\r
- "odd": function( elements, argument, not ) {\r
- var results = [],\r
- i = not ? 0 : 1,\r
- len = elements.length;\r
- for ( ; i < len; i = i + 2 ) {\r
- results.push( elements[i] );\r
+ "eq": createPositionalPseudo(function( matchIndexes, length, argument ) {\r
+ return [ argument < 0 ? argument + length : argument ];\r
+ }),\r
+\r
+ "even": createPositionalPseudo(function( matchIndexes, length, argument ) {\r
+ for ( var i = 0; i < length; i += 2 ) {\r
+ matchIndexes.push( i );\r
}\r
- return results;\r
- },\r
+ return matchIndexes;\r
+ }),\r
\r
- "lt": function( elements, argument, not ) {\r
- return not ? elements.slice( +argument ) : elements.slice( 0, +argument );\r
- },\r
+ "odd": createPositionalPseudo(function( matchIndexes, length, argument ) {\r
+ for ( var i = 1; i < length; i += 2 ) {\r
+ matchIndexes.push( i );\r
+ }\r
+ return matchIndexes;\r
+ }),\r
\r
- "gt": function( elements, argument, not ) {\r
- return not ? elements.slice( 0, +argument + 1 ) : elements.slice( +argument + 1 );\r
- },\r
+ "lt": createPositionalPseudo(function( matchIndexes, length, argument ) {\r
+ for ( var i = argument < 0 ? argument + length : argument; --i >= 0; ) {\r
+ matchIndexes.push( i );\r
+ }\r
+ return matchIndexes;\r
+ }),\r
\r
- "eq": function( elements, argument, not ) {\r
- var elem = elements.splice( +argument, 1 );\r
- return not ? elements : elem;\r
- }\r
+ "gt": createPositionalPseudo(function( matchIndexes, length, argument ) {\r
+ for ( var i = argument < 0 ? argument + length : argument; ++i < length; ) {\r
+ matchIndexes.push( i );\r
+ }\r
+ return matchIndexes;\r
+ })\r
}\r
};\r
\r
throw new Error( "Syntax error, unrecognized expression: " + msg );\r
};\r
\r
-function tokenize( selector, context, xml, parseOnly ) {\r
- var matched, match, tokens, type,\r
- soFar, groups, group, i,\r
- preFilters, filters,\r
- checkContext = !xml && context !== document,\r
- // Token cache should maintain spaces\r
- key = ( checkContext ? "<s>" : "" ) + selector.replace( rtrim, "$1<s>" ),\r
- cached = tokenCache[ expando ][ key ];\r
+function tokenize( selector, parseOnly ) {\r
+ var matched, match, tokens, type, soFar, groups, preFilters,\r
+ cached = tokenCache[ expando ][ selector ];\r
\r
if ( cached ) {\r
- return parseOnly ? 0 : slice.call( cached, 0 );\r
+ return parseOnly ? 0 : cached.slice( 0 );\r
}\r
\r
soFar = selector;\r
groups = [];\r
- i = 0;\r
preFilters = Expr.preFilter;\r
- filters = Expr.filter;\r
\r
while ( soFar ) {\r
\r
if ( !matched || (match = rcomma.exec( soFar )) ) {\r
if ( match ) {\r
soFar = soFar.slice( match[0].length );\r
- tokens.selector = group;\r
}\r
groups.push( tokens = [] );\r
- group = "";\r
-\r
- // Need to make sure we're within a narrower context if necessary\r
- // Adding a descendant combinator will generate what is needed\r
- if ( checkContext ) {\r
- soFar = " " + soFar;\r
- }\r
}\r
\r
matched = false;\r
\r
// Combinators\r
if ( (match = rcombinators.exec( soFar )) ) {\r
- group += match[0];\r
- soFar = soFar.slice( match[0].length );\r
+ tokens.push( matched = new Token( match.shift() ) );\r
+ soFar = soFar.slice( matched.length );\r
\r
// Cast descendant combinators to space\r
- matched = tokens.push({\r
- part: match.pop().replace( rtrim, " " ),\r
- string: match[0],\r
- captures: match\r
- });\r
+ matched.type = match[0].replace( rtrim, " " );\r
}\r
\r
// Filters\r
- for ( type in filters ) {\r
+ for ( type in Expr.filter ) {\r
if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] ||\r
- ( match = preFilters[ type ](match, context, xml) )) ) {\r
+ // The last two arguments here are (context, xml) for backCompat\r
+ (match = preFilters[ type ]( match, document, true ))) ) {\r
\r
- group += match[0];\r
- soFar = soFar.slice( match[0].length );\r
- matched = tokens.push({\r
- part: type,\r
- string: match.shift(),\r
- captures: match\r
- });\r
+ tokens.push( matched = new Token( match.shift() ) );\r
+ soFar = soFar.slice( matched.length );\r
+ matched.type = type;\r
+ matched.matches = match;\r
}\r
}\r
\r
}\r
}\r
\r
- // Attach the full group as a selector\r
- if ( group ) {\r
- tokens.selector = group;\r
- }\r
-\r
// Return the length of the invalid excess\r
// if we're just parsing\r
// Otherwise, throw an error or return tokens\r
soFar ?\r
Sizzle.error( selector ) :\r
// Cache the tokens\r
- slice.call( tokenCache(key, groups), 0 );\r
+ tokenCache( selector, groups ).slice( 0 );\r
}\r
\r
-function addCombinator( matcher, combinator, context, xml ) {\r
+function addCombinator( matcher, combinator, base ) {\r
var dir = combinator.dir,\r
+ checkNonElements = base && combinator.dir === "parentNode",\r
doneName = done++;\r
\r
- if ( !matcher ) {\r
- // If there is no matcher to check, check against the context\r
- matcher = function( elem ) {\r
- return elem === context;\r
- };\r
- }\r
return combinator.first ?\r
- function( elem ) {\r
+ // Check against closest ancestor/preceding element\r
+ function( elem, context, xml ) {\r
while ( (elem = elem[ dir ]) ) {\r
- if ( elem.nodeType === 1 ) {\r
- return matcher( elem ) && elem;\r
+ if ( checkNonElements || elem.nodeType === 1 ) {\r
+ return matcher( elem, context, xml );\r
}\r
}\r
} :\r
- xml ?\r
- function( elem ) {\r
- while ( (elem = elem[ dir ]) ) {\r
- if ( elem.nodeType === 1 ) {\r
- if ( matcher( elem ) ) {\r
- return elem;\r
- }\r
- }\r
- }\r
- } :\r
- function( elem ) {\r
+\r
+ // Check against all ancestor/preceding elements\r
+ function( elem, context, xml ) {\r
+ // We can't set arbitrary data on XML nodes, so they don't benefit from dir caching\r
+ if ( !xml ) {\r
var cache,\r
- dirkey = doneName + "." + dirruns,\r
- cachedkey = dirkey + "." + cachedruns;\r
+ dirkey = dirruns + " " + doneName + " ",\r
+ cachedkey = dirkey + cachedruns;\r
while ( (elem = elem[ dir ]) ) {\r
- if ( elem.nodeType === 1 ) {\r
+ if ( checkNonElements || elem.nodeType === 1 ) {\r
if ( (cache = elem[ expando ]) === cachedkey ) {\r
return elem.sizset;\r
} else if ( typeof cache === "string" && cache.indexOf(dirkey) === 0 ) {\r
}\r
} else {\r
elem[ expando ] = cachedkey;\r
- if ( matcher( elem ) ) {\r
+ if ( matcher( elem, context, xml ) ) {\r
elem.sizset = true;\r
return elem;\r
}\r
}\r
}\r
}\r
- };\r
+ } else {\r
+ while ( (elem = elem[ dir ]) ) {\r
+ if ( checkNonElements || elem.nodeType === 1 ) {\r
+ if ( matcher( elem, context, xml ) ) {\r
+ return elem;\r
+ }\r
+ }\r
+ }\r
+ }\r
+ };\r
}\r
\r
-function addMatcher( higher, deeper ) {\r
- return higher ?\r
- function( elem ) {\r
- var result = deeper( elem );\r
- return result && higher( result === true ? elem : result );\r
+function elementMatcher( matchers ) {\r
+ return matchers.length > 1 ?\r
+ function( elem, context, xml ) {\r
+ var i = matchers.length;\r
+ while ( i-- ) {\r
+ if ( !matchers[i]( elem, context, xml ) ) {\r
+ return false;\r
+ }\r
+ }\r
+ return true;\r
} :\r
- deeper;\r
+ matchers[0];\r
}\r
\r
-// ["TAG", ">", "ID", " ", "CLASS"]\r
-function matcherFromTokens( tokens, context, xml ) {\r
- var token, matcher,\r
- i = 0;\r
+function condense( unmatched, map, filter, context, xml ) {\r
+ var elem,\r
+ newUnmatched = [],\r
+ i = 0,\r
+ len = unmatched.length,\r
+ mapped = map != null;\r
\r
- for ( ; (token = tokens[i]); i++ ) {\r
- if ( Expr.relative[ token.part ] ) {\r
- matcher = addCombinator( matcher, Expr.relative[ token.part ], context, xml );\r
- } else {\r
- matcher = addMatcher( matcher, Expr.filter[ token.part ].apply(null, token.captures.concat( context, xml )) );\r
+ for ( ; i < len; i++ ) {\r
+ if ( (elem = unmatched[i]) ) {\r
+ if ( !filter || filter( elem, context, xml ) ) {\r
+ newUnmatched.push( elem );\r
+ if ( mapped ) {\r
+ map.push( i );\r
+ }\r
+ }\r
}\r
}\r
\r
- return matcher;\r
+ return newUnmatched;\r
}\r
\r
-function matcherFromGroupMatchers( matchers ) {\r
- return function( elem ) {\r
- var matcher,\r
- j = 0;\r
- for ( ; (matcher = matchers[j]); j++ ) {\r
- if ( matcher(elem) ) {\r
- return true;\r
- }\r
+function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) {\r
+ if ( postFilter && !postFilter[ expando ] ) {\r
+ postFilter = setMatcher( postFilter );\r
+ }\r
+ if ( postFinder && !postFinder[ expando ] ) {\r
+ postFinder = setMatcher( postFinder, postSelector );\r
+ }\r
+ return markFunction(function( seed, results, context, xml ) {\r
+ // Positional selectors apply to seed elements, so it is invalid to follow them with relative ones\r
+ if ( seed && postFinder ) {\r
+ return;\r
}\r
- return false;\r
- };\r
-}\r
\r
-compile = Sizzle.compile = function( selector, context, xml ) {\r
- var group, i, len,\r
- cached = compilerCache[ expando ][ selector ];\r
+ var i, elem, postFilterIn,\r
+ preMap = [],\r
+ postMap = [],\r
+ preexisting = results.length,\r
\r
- // Return a cached group function if already generated (context dependent)\r
- if ( cached && cached.context === context ) {\r
- return cached;\r
- }\r
+ // Get initial elements from seed or context\r
+ elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [], seed ),\r
\r
- // Generate a function of recursive functions that can be used to check each element\r
- group = tokenize( selector, context, xml );\r
- for ( i = 0, len = group.length; i < len; i++ ) {\r
- group[i] = matcherFromTokens(group[i], context, xml);\r
- }\r
+ // Prefilter to get matcher input, preserving a map for seed-results synchronization\r
+ matcherIn = preFilter && ( seed || !selector ) ?\r
+ condense( elems, preMap, preFilter, context, xml ) :\r
+ elems,\r
\r
- // Cache the compiled function\r
- cached = compilerCache( selector, matcherFromGroupMatchers(group) );\r
- cached.context = context;\r
- cached.runs = cached.dirruns = 0;\r
- return cached;\r
-};\r
+ matcherOut = matcher ?\r
+ // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results,\r
+ postFinder || ( seed ? preFilter : preexisting || postFilter ) ?\r
\r
-function multipleContexts( selector, contexts, results, seed ) {\r
- var i = 0,\r
- len = contexts.length;\r
- for ( ; i < len; i++ ) {\r
- Sizzle( selector, contexts[i], results, seed );\r
- }\r
-}\r
+ // ...intermediate processing is necessary\r
+ [] :\r
\r
-function handlePOSGroup( selector, posfilter, argument, contexts, seed, not ) {\r
- var results,\r
- fn = Expr.setFilters[ posfilter.toLowerCase() ];\r
+ // ...otherwise use results directly\r
+ results :\r
+ matcherIn;\r
\r
- if ( !fn ) {\r
- Sizzle.error( posfilter );\r
- }\r
+ // Find primary matches\r
+ if ( matcher ) {\r
+ matcher( matcherIn, matcherOut, context, xml );\r
+ }\r
\r
- if ( selector || !(results = seed) ) {\r
- multipleContexts( selector || "*", contexts, (results = []), seed );\r
- }\r
+ // Apply postFilter\r
+ if ( postFilter ) {\r
+ postFilterIn = condense( matcherOut, postMap );\r
+ postFilter( postFilterIn, [], context, xml );\r
\r
- return results.length > 0 ? fn( results, argument, not ) : [];\r
-}\r
+ // Un-match failing elements by moving them back to matcherIn\r
+ i = postFilterIn.length;\r
+ while ( i-- ) {\r
+ if ( (elem = postFilterIn[i]) ) {\r
+ matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem);\r
+ }\r
+ }\r
+ }\r
\r
-function handlePOS( groups, context, results, seed ) {\r
- var group, part, j, groupLen, token, selector,\r
- anchor, elements, match, matched,\r
- lastIndex, currentContexts, not,\r
- i = 0,\r
- len = groups.length,\r
- rpos = matchExpr["POS"],\r
- // This is generated here in case matchExpr["POS"] is extended\r
- rposgroups = new RegExp( "^" + rpos.source + "(?!" + whitespace + ")", "i" ),\r
- // This is for making sure non-participating\r
- // matching groups are represented cross-browser (IE6-8)\r
- setUndefined = function() {\r
- var i = 1,\r
- len = arguments.length - 2;\r
- for ( ; i < len; i++ ) {\r
- if ( arguments[i] === undefined ) {\r
- match[i] = undefined;\r
+ // Keep seed and results synchronized\r
+ if ( seed ) {\r
+ // Ignore postFinder because it can't coexist with seed\r
+ i = preFilter && matcherOut.length;\r
+ while ( i-- ) {\r
+ if ( (elem = matcherOut[i]) ) {\r
+ seed[ preMap[i] ] = !(results[ preMap[i] ] = elem);\r
}\r
}\r
- };\r
+ } else {\r
+ matcherOut = condense(\r
+ matcherOut === results ?\r
+ matcherOut.splice( preexisting, matcherOut.length ) :\r
+ matcherOut\r
+ );\r
+ if ( postFinder ) {\r
+ postFinder( null, results, matcherOut, xml );\r
+ } else {\r
+ push.apply( results, matcherOut );\r
+ }\r
+ }\r
+ });\r
+}\r
+\r
+function matcherFromTokens( tokens ) {\r
+ var checkContext, matcher, j,\r
+ len = tokens.length,\r
+ leadingRelative = Expr.relative[ tokens[0].type ],\r
+ implicitRelative = leadingRelative || Expr.relative[" "],\r
+ i = leadingRelative ? 1 : 0,\r
+\r
+ // The foundational matcher ensures that elements are reachable from top-level context(s)\r
+ matchContext = addCombinator( function( elem ) {\r
+ return elem === checkContext;\r
+ }, implicitRelative, true ),\r
+ matchAnyContext = addCombinator( function( elem ) {\r
+ return indexOf.call( checkContext, elem ) > -1;\r
+ }, implicitRelative, true ),\r
+ matchers = [ function( elem, context, xml ) {\r
+ return ( !leadingRelative && ( xml || context !== outermostContext ) ) || (\r
+ (checkContext = context).nodeType ?\r
+ matchContext( elem, context, xml ) :\r
+ matchAnyContext( elem, context, xml ) );\r
+ } ];\r
\r
for ( ; i < len; i++ ) {\r
- group = groups[i];\r
- part = "";\r
- elements = seed;\r
- for ( j = 0, groupLen = group.length; j < groupLen; j++ ) {\r
- token = group[j];\r
- selector = token.string;\r
- if ( token.part === "PSEUDO" ) {\r
- // Reset regex index to 0\r
- rpos.exec("");\r
- anchor = 0;\r
- while ( (match = rpos.exec( selector )) ) {\r
- matched = true;\r
- lastIndex = rpos.lastIndex = match.index + match[0].length;\r
- if ( lastIndex > anchor ) {\r
- part += selector.slice( anchor, match.index );\r
- anchor = lastIndex;\r
- currentContexts = [ context ];\r
-\r
- if ( rcombinators.test(part) ) {\r
- if ( elements ) {\r
- currentContexts = elements;\r
- }\r
- elements = seed;\r
- }\r
+ if ( (matcher = Expr.relative[ tokens[i].type ]) ) {\r
+ matchers = [ addCombinator( elementMatcher( matchers ), matcher ) ];\r
+ } else {\r
+ // The concatenated values are (context, xml) for backCompat\r
+ matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches );\r
+\r
+ // Return special upon seeing a positional matcher\r
+ if ( matcher[ expando ] ) {\r
+ // Find the next relative operator (if any) for proper handling\r
+ j = ++i;\r
+ for ( ; j < len; j++ ) {\r
+ if ( Expr.relative[ tokens[j].type ] ) {\r
+ break;\r
+ }\r
+ }\r
+ return setMatcher(\r
+ i > 1 && elementMatcher( matchers ),\r
+ i > 1 && tokens.slice( 0, i - 1 ).join("").replace( rtrim, "$1" ),\r
+ matcher,\r
+ i < j && matcherFromTokens( tokens.slice( i, j ) ),\r
+ j < len && matcherFromTokens( (tokens = tokens.slice( j )) ),\r
+ j < len && tokens.join("")\r
+ );\r
+ }\r
+ matchers.push( matcher );\r
+ }\r
+ }\r
\r
- if ( (not = rendsWithNot.test( part )) ) {\r
- part = part.slice( 0, -5 ).replace( rcombinators, "$&*" );\r
- anchor++;\r
- }\r
+ return elementMatcher( matchers );\r
+}\r
\r
- if ( match.length > 1 ) {\r
- match[0].replace( rposgroups, setUndefined );\r
+function matcherFromGroupMatchers( elementMatchers, setMatchers ) {\r
+ var bySet = setMatchers.length > 0,\r
+ byElement = elementMatchers.length > 0,\r
+ superMatcher = function( seed, context, xml, results, expandContext ) {\r
+ var elem, j, matcher,\r
+ setMatched = [],\r
+ matchedCount = 0,\r
+ i = "0",\r
+ unmatched = seed && [],\r
+ outermost = expandContext != null,\r
+ contextBackup = outermostContext,\r
+ // We must always have either seed elements or context\r
+ elems = seed || byElement && Expr.find["TAG"]( "*", expandContext && context.parentNode || context ),\r
+ // Nested matchers should use non-integer dirruns\r
+ dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.E);\r
+\r
+ if ( outermost ) {\r
+ outermostContext = context !== document && context;\r
+ cachedruns = superMatcher.el;\r
+ }\r
+\r
+ // Add elements passing elementMatchers directly to results\r
+ for ( ; (elem = elems[i]) != null; i++ ) {\r
+ if ( byElement && elem ) {\r
+ for ( j = 0; (matcher = elementMatchers[j]); j++ ) {\r
+ if ( matcher( elem, context, xml ) ) {\r
+ results.push( elem );\r
+ break;\r
}\r
- elements = handlePOSGroup( part, match[1], match[2], currentContexts, elements, not );\r
}\r
- part = "";\r
+ if ( outermost ) {\r
+ dirruns = dirrunsUnique;\r
+ cachedruns = ++superMatcher.el;\r
+ }\r
}\r
\r
- }\r
+ // Track unmatched elements for set filters\r
+ if ( bySet ) {\r
+ // They will have gone through all possible matchers\r
+ if ( (elem = !matcher && elem) ) {\r
+ matchedCount--;\r
+ }\r
\r
- if ( !matched ) {\r
- part += selector;\r
+ // Lengthen the array for every element, matched or not\r
+ if ( seed ) {\r
+ unmatched.push( elem );\r
+ }\r
+ }\r
}\r
- matched = false;\r
- }\r
\r
- if ( part ) {\r
- if ( rcombinators.test(part) ) {\r
- multipleContexts( part, elements || [ context ], results, seed );\r
- } else {\r
- Sizzle( part, context, results, seed ? seed.concat(elements) : elements );\r
- }\r
- } else {\r
- push.apply( results, elements );\r
- }\r
- }\r
+ // Apply set filters to unmatched elements\r
+ matchedCount += i;\r
+ if ( bySet && i !== matchedCount ) {\r
+ for ( j = 0; (matcher = setMatchers[j]); j++ ) {\r
+ matcher( unmatched, setMatched, context, xml );\r
+ }\r
\r
- // Do not sort if this is a single filter\r
- return len === 1 ? results : Sizzle.uniqueSort( results );\r
-}\r
+ if ( seed ) {\r
+ // Reintegrate element matches to eliminate the need for sorting\r
+ if ( matchedCount > 0 ) {\r
+ while ( i-- ) {\r
+ if ( !(unmatched[i] || setMatched[i]) ) {\r
+ setMatched[i] = pop.call( results );\r
+ }\r
+ }\r
+ }\r
\r
-function select( selector, context, results, seed, xml ) {\r
- // Remove excessive whitespace\r
- selector = selector.replace( rtrim, "$1" );\r
- var elements, matcher, cached, elem,\r
- i, tokens, token, lastToken, findContext, type,\r
- match = tokenize( selector, context, xml ),\r
- contextNodeType = context.nodeType;\r
-\r
- // POS handling\r
- if ( matchExpr["POS"].test(selector) ) {\r
- return handlePOS( match, context, results, seed );\r
- }\r
+ // Discard index placeholder values to get only actual matches\r
+ setMatched = condense( setMatched );\r
+ }\r
\r
- if ( seed ) {\r
- elements = slice.call( seed, 0 );\r
+ // Add matches to results\r
+ push.apply( results, setMatched );\r
\r
- // To maintain document order, only narrow the\r
- // set if there is one group\r
- } else if ( match.length === 1 ) {\r
+ // Seedless set matches succeeding multiple successful matchers stipulate sorting\r
+ if ( outermost && !seed && setMatched.length > 0 &&\r
+ ( matchedCount + setMatchers.length ) > 1 ) {\r
\r
- // Take a shortcut and set the context if the root selector is an ID\r
- if ( (tokens = slice.call( match[0], 0 )).length > 2 &&\r
- (token = tokens[0]).part === "ID" &&\r
- contextNodeType === 9 && !xml &&\r
- Expr.relative[ tokens[1].part ] ) {\r
+ Sizzle.uniqueSort( results );\r
+ }\r
+ }\r
\r
- context = Expr.find["ID"]( token.captures[0].replace( rbackslash, "" ), context, xml )[0];\r
- if ( !context ) {\r
- return results;\r
+ // Override manipulation of globals by nested matchers\r
+ if ( outermost ) {\r
+ dirruns = dirrunsUnique;\r
+ outermostContext = contextBackup;\r
}\r
\r
- selector = selector.slice( tokens.shift().string.length );\r
- }\r
+ return unmatched;\r
+ };\r
+\r
+ superMatcher.el = 0;\r
+ return bySet ?\r
+ markFunction( superMatcher ) :\r
+ superMatcher;\r
+}\r
\r
- findContext = ( (match = rsibling.exec( tokens[0].string )) && !match.index && context.parentNode ) || context;\r
+compile = Sizzle.compile = function( selector, group /* Internal Use Only */ ) {\r
+ var i,\r
+ setMatchers = [],\r
+ elementMatchers = [],\r
+ cached = compilerCache[ expando ][ selector ];\r
\r
- // Reduce the set if possible\r
- lastToken = "";\r
- for ( i = tokens.length - 1; i >= 0; i-- ) {\r
- token = tokens[i];\r
- type = token.part;\r
- lastToken = token.string + lastToken;\r
- if ( Expr.relative[ type ] ) {\r
- break;\r
+ if ( !cached ) {\r
+ // Generate a function of recursive functions that can be used to check each element\r
+ if ( !group ) {\r
+ group = tokenize( selector );\r
+ }\r
+ i = group.length;\r
+ while ( i-- ) {\r
+ cached = matcherFromTokens( group[i] );\r
+ if ( cached[ expando ] ) {\r
+ setMatchers.push( cached );\r
+ } else {\r
+ elementMatchers.push( cached );\r
}\r
- if ( Expr.order.test(type) ) {\r
- elements = Expr.find[ type ]( token.captures[0].replace( rbackslash, "" ), findContext, xml );\r
- if ( elements == null ) {\r
- continue;\r
- } else {\r
- selector = selector.slice( 0, selector.length - lastToken.length ) +\r
- lastToken.replace( matchExpr[ type ], "" );\r
+ }\r
\r
- if ( !selector ) {\r
- push.apply( results, slice.call(elements, 0) );\r
- }\r
+ // Cache the compiled function\r
+ cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) );\r
+ }\r
+ return cached;\r
+};\r
\r
- break;\r
+function multipleContexts( selector, contexts, results, seed ) {\r
+ var i = 0,\r
+ len = contexts.length;\r
+ for ( ; i < len; i++ ) {\r
+ Sizzle( selector, contexts[i], results, seed );\r
+ }\r
+ return results;\r
+}\r
+\r
+function select( selector, context, results, seed, xml ) {\r
+ var i, tokens, token, type, find,\r
+ match = tokenize( selector ),\r
+ j = match.length;\r
+\r
+ if ( !seed ) {\r
+ // Try to minimize operations if there is only one group\r
+ if ( match.length === 1 ) {\r
+\r
+ // Take a shortcut and set the context if the root selector is an ID\r
+ tokens = match[0] = match[0].slice( 0 );\r
+ if ( tokens.length > 2 && (token = tokens[0]).type === "ID" &&\r
+ context.nodeType === 9 && !xml &&\r
+ Expr.relative[ tokens[1].type ] ) {\r
+\r
+ context = Expr.find["ID"]( token.matches[0].replace( rbackslash, "" ), context, xml )[0];\r
+ if ( !context ) {\r
+ return results;\r
}\r
+\r
+ selector = selector.slice( tokens.shift().length );\r
}\r
- }\r
- }\r
\r
- // Only loop over the given elements once\r
- if ( selector ) {\r
- matcher = compile( selector, context, xml );\r
- dirruns = matcher.dirruns++;\r
- if ( elements == null ) {\r
- elements = Expr.find["TAG"]( "*", (rsibling.test( selector ) && context.parentNode) || context );\r
- }\r
+ // Fetch a seed set for right-to-left matching\r
+ for ( i = matchExpr["POS"].test( selector ) ? -1 : tokens.length - 1; i >= 0; i-- ) {\r
+ token = tokens[i];\r
+\r
+ // Abort if we hit a combinator\r
+ if ( Expr.relative[ (type = token.type) ] ) {\r
+ break;\r
+ }\r
+ if ( (find = Expr.find[ type ]) ) {\r
+ // Search, expanding context for leading sibling combinators\r
+ if ( (seed = find(\r
+ token.matches[0].replace( rbackslash, "" ),\r
+ rsibling.test( tokens[0].type ) && context.parentNode || context,\r
+ xml\r
+ )) ) {\r
+\r
+ // If seed is empty or no tokens remain, we can return early\r
+ tokens.splice( i, 1 );\r
+ selector = seed.length && tokens.join("");\r
+ if ( !selector ) {\r
+ push.apply( results, slice.call( seed, 0 ) );\r
+ return results;\r
+ }\r
\r
- for ( i = 0; (elem = elements[i]); i++ ) {\r
- cachedruns = matcher.runs++;\r
- if ( matcher(elem) ) {\r
- results.push( elem );\r
+ break;\r
+ }\r
+ }\r
}\r
}\r
}\r
\r
+ // Compile and execute a filtering function\r
+ // Provide `match` to avoid retokenization if we modified the selector above\r
+ compile( selector, match )(\r
+ seed,\r
+ context,\r
+ xml,\r
+ results,\r
+ rsibling.test( selector )\r
+ );\r
return results;\r
}\r
\r
oldSelect = select,\r
rescape = /'|\\/g,\r
rattributeQuotes = /\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g,\r
- rbuggyQSA = [],\r
+\r
+ // qSa(:focus) reports false when true (Chrome 21),\r
+ // A support test would require too much code (would include document ready)\r
+ rbuggyQSA = [":focus"],\r
+\r
+ // matchesSelector(:focus) reports false when true (Chrome 21),\r
// matchesSelector(:active) reports false when true (IE9/Opera 11.5)\r
// A support test would require too much code (would include document ready)\r
// just skip matchesSelector for :active\r
- rbuggyMatches = [":active"],\r
+ rbuggyMatches = [ ":active", ":focus" ],\r
matches = docElem.matchesSelector ||\r
docElem.mozMatchesSelector ||\r
docElem.webkitMatchesSelector ||\r
}\r
});\r
\r
- rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") );\r
+ // rbuggyQSA always contains :focus, so no need for a length check\r
+ rbuggyQSA = /* rbuggyQSA.length && */ new RegExp( rbuggyQSA.join("|") );\r
\r
select = function( selector, context, results, seed, xml ) {\r
// Only use querySelectorAll when not filtering,\r
// when this is not xml,\r
// and when no QSA bugs apply\r
if ( !seed && !xml && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) {\r
- if ( context.nodeType === 9 ) {\r
- try {\r
- push.apply( results, slice.call(context.querySelectorAll( selector ), 0) );\r
- return results;\r
- } catch(qsaError) {}\r
+ var groups, i,\r
+ old = true,\r
+ nid = expando,\r
+ newContext = context,\r
+ newSelector = context.nodeType === 9 && selector;\r
+\r
// qSA works strangely on Element-rooted queries\r
// We can work around this by specifying an extra ID on the root\r
// and working up from there (Thanks to Andrew Dupont for the technique)\r
// IE 8 doesn't work on object elements\r
- } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {\r
- var groups, i, len,\r
- old = context.getAttribute("id"),\r
- nid = old || expando,\r
- newContext = rsibling.test( selector ) && context.parentNode || context;\r
-\r
- if ( old ) {\r
- nid = nid.replace( rescape, "\\$&" );\r
+ if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {\r
+ groups = tokenize( selector );\r
+\r
+ if ( (old = context.getAttribute("id")) ) {\r
+ nid = old.replace( rescape, "\\$&" );\r
} else {\r
context.setAttribute( "id", nid );\r
}\r
+ nid = "[id='" + nid + "'] ";\r
\r
- groups = tokenize(selector, context, xml);\r
- // Trailing space is unnecessary\r
- // There is always a context check\r
- nid = "[id='" + nid + "']";\r
- for ( i = 0, len = groups.length; i < len; i++ ) {\r
- groups[i] = nid + groups[i].selector;\r
+ i = groups.length;\r
+ while ( i-- ) {\r
+ groups[i] = nid + groups[i].join("");\r
}\r
+ newContext = rsibling.test( selector ) && context.parentNode || context;\r
+ newSelector = groups.join(",");\r
+ }\r
+\r
+ if ( newSelector ) {\r
try {\r
push.apply( results, slice.call( newContext.querySelectorAll(\r
- groups.join(",")\r
+ newSelector\r
), 0 ) );\r
return results;\r
} catch(qsaError) {\r
// Gecko does not error, returns false instead\r
try {\r
matches.call( div, "[test!='']:sizzle" );\r
- rbuggyMatches.push( matchExpr["PSEUDO"].source, matchExpr["POS"].source, "!=" );\r
+ rbuggyMatches.push( "!=", pseudos );\r
} catch ( e ) {}\r
});\r
\r
- // rbuggyMatches always contains :active, so no need for a length check\r
+ // rbuggyMatches always contains :active and :focus, so no need for a length check\r
rbuggyMatches = /* rbuggyMatches.length && */ new RegExp( rbuggyMatches.join("|") );\r
\r
Sizzle.matchesSelector = function( elem, expr ) {\r
}\r
\r
// Deprecated\r
-Expr.setFilters["nth"] = Expr.setFilters["eq"];\r
+Expr.pseudos["nth"] = Expr.pseudos["eq"];\r
\r
// Back-compat\r
-Expr.filters = Expr.pseudos;\r
+function setFilters() {}\r
+Expr.filters = setFilters.prototype = Expr.pseudos;\r
+Expr.setFilters = new setFilters();\r
\r
// Override sizzle attribute retrieval
Sizzle.attr = jQuery.attr;
add( prefix, obj );
}
}
-var // Document location
- ajaxLocation,
- // Document location segments
+var
+ // Document location
ajaxLocParts,
+ ajaxLocation,
rhash = /#.*$/,
rheaders = /^(.*?):[ \t]*([^\r\n]*)\r?$/mg, // IE leaves an \r character at EOL
// Set data for the fake xhr object
jqXHR.status = status;
- jqXHR.statusText = "" + ( nativeStatusText || statusText );
+ jqXHR.statusText = ( nativeStatusText || statusText ) + "";
// Success/Error
if ( isSuccess ) {
// Extract dataTypes list
s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().split( core_rspace );
- // Determine if a cross-domain request is in order
+ // A cross-domain request is in order when we have a protocol:host:port mismatch
if ( s.crossDomain == null ) {
- parts = rurl.exec( s.url.toLowerCase() );
- s.crossDomain = !!( parts &&
- ( parts[ 1 ] != ajaxLocParts[ 1 ] || parts[ 2 ] != ajaxLocParts[ 2 ] ||
- ( parts[ 3 ] || ( parts[ 1 ] === "http:" ? 80 : 443 ) ) !=
- ( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === "http:" ? 80 : 443 ) ) )
- );
+ parts = rurl.exec( s.url.toLowerCase() ) || false;
+ s.crossDomain = parts && ( parts.join(":") + ( parts[ 3 ] ? "" : parts[ 1 ] === "http:" ? 80 : 443 ) ) !==
+ ( ajaxLocParts.join(":") + ( ajaxLocParts[ 3 ] ? "" : ajaxLocParts[ 1 ] === "http:" ? 80 : 443 ) );
}
// Convert data if not already a string
animationPrefilters = [ defaultPrefilter ],
tweeners = {
"*": [function( prop, value ) {
- var end, unit, prevScale,
+ var end, unit,
tween = this.createTween( prop, value ),
parts = rfxnum.exec( value ),
target = tween.cur(),
start = +target || 0,
- scale = 1;
+ scale = 1,
+ maxIterations = 20;
if ( parts ) {
end = +parts[2];
do {
// If previous iteration zeroed out, double until we get *something*
// Use a string for doubling factor so we don't accidentally see scale as unchanged below
- prevScale = scale = scale || ".5";
+ scale = scale || ".5";
// Adjust and apply
start = start / scale;
jQuery.style( tween.elem, prop, start + unit );
- // Update scale, tolerating zeroes from tween.cur()
- scale = tween.cur() / target;
-
- // Stop looping if we've hit the mark or scale is unchanged
- } while ( scale !== 1 && scale !== prevScale );
+ // Update scale, tolerating zero or NaN from tween.cur()
+ // And breaking the loop if scale is unchanged or perfect, or if we've just had enough
+ } while ( scale !== (scale = tween.cur() / target) && scale !== 1 && --maxIterations );
}
tween.unit = unit;
});
}
- var box, docElem, body, win, clientTop, clientLeft, scrollTop, scrollLeft, top, left,
+ var docElem, body, win, clientTop, clientLeft, scrollTop, scrollLeft,
+ box = { top: 0, left: 0 },
elem = this[ 0 ],
doc = elem && elem.ownerDocument;
docElem = doc.documentElement;
- // Make sure we're not dealing with a disconnected DOM node
+ // Make sure it's not a disconnected DOM node
if ( !jQuery.contains( docElem, elem ) ) {
- return { top: 0, left: 0 };
+ return box;
}
- box = elem.getBoundingClientRect();
+ // If we don't have gBCR, just use 0,0 rather than error
+ // BlackBerry 5, iOS 3 (original iPhone)
+ if ( typeof elem.getBoundingClientRect !== "undefined" ) {
+ box = elem.getBoundingClientRect();
+ }
win = getWindow( doc );
clientTop = docElem.clientTop || body.clientTop || 0;
clientLeft = docElem.clientLeft || body.clientLeft || 0;
scrollTop = win.pageYOffset || docElem.scrollTop;
scrollLeft = win.pageXOffset || docElem.scrollLeft;
- top = box.top + scrollTop - clientTop;
- left = box.left + scrollLeft - clientLeft;
-
- return { top: top, left: left };
+ return {
+ top: box.top + scrollTop - clientTop,
+ left: box.left + scrollLeft - clientLeft
+ };
};
jQuery.offset = {
* jQuery makeCollapsible
*
* This will enable collapsible-functionality on all passed elements.
- * Will prevent binding twice to the same element.
- * Initial state is expanded by default, this can be overriden by adding class
- * "mw-collapsed" to the "mw-collapsible" element.
- * Elements made collapsible have class "mw-made-collapsible".
- * Except for tables and lists, the inner content is wrapped in "mw-collapsible-content".
+ * - Will prevent binding twice to the same element.
+ * - Initial state is expanded by default, this can be overriden by adding class
+ * "mw-collapsed" to the "mw-collapsible" element.
+ * - Elements made collapsible have jQuery data "mw-made-collapsible" set to true.
+ * - The inner content is wrapped in a "div.mw-collapsible-content" (except for tables and lists).
*
- * @author Krinkle <krinklemail@gmail.com>
+ * @author Krinkle, 2011-2012
*
* Dual license:
* @license CC-BY 3.0 <http://creativecommons.org/licenses/by/3.0>
return this.each(function () {
// Define reused variables and functions
- var $toggle,
- lpx = 'jquery.makeCollapsible> ',
- $that = $(this).addClass( 'mw-collapsible' ), // case: $( '#myAJAXelement' ).makeCollapsible()
- that = this,
- collapsetext = $(this).attr( 'data-collapsetext' ),
- expandtext = $(this).attr( 'data-expandtext' ),
- toggleElement = function ( $collapsible, action, $defaultToggle, instantHide ) {
+ var lpx = 'jquery.makeCollapsible> ',
+ collapsible = this,
+ // Ensure class "mw-collapsible" is present in case .makeCollapsible()
+ // is called on element(s) that don't have it yet.
+ $collapsible = $(collapsible).addClass( 'mw-collapsible' ),
+ collapsetext = $collapsible.attr( 'data-collapsetext' ),
+ expandtext = $collapsible.attr( 'data-expandtext' ),
+ $toggle,
+ $toggleLink,
+ $firstItem,
+ collapsibleId,
+ $customTogglers,
+ firstval,
+ /**
+ * @param {jQuery} $collapsible
+ * @param {string} action The action this function will take ('expand' or 'collapse').
+ * @param {jQuery|null} [optional] $defaultToggle
+ * @param {Object|undefined} options
+ */
+ toggleElement = function ( $collapsible, action, $defaultToggle, options ) {
var $collapsibleContent, $containers;
+ options = options || {};
// Validate parameters
- if ( !$collapsible.jquery ) { // $collapsible must be an instance of jQuery
+
+ // $collapsible must be an instance of jQuery
+ if ( !$collapsible.jquery ) {
return;
}
if ( action !== 'expand' && action !== 'collapse' ) {
if ( $defaultToggle === undefined ) {
$defaultToggle = null;
}
- if ( $defaultToggle !== null && !($defaultToggle instanceof $) ) {
+ if ( $defaultToggle !== null && !$defaultToggle.jquery ) {
// is optional (may be undefined), but if defined it must be an instance of jQuery.
// If it's not, abort right away.
// After this $defaultToggle is either null or a valid jQuery instance.
// Hide all table rows of this table
// Slide doens't work with tables, but fade does as of jQuery 1.1.3
// http://stackoverflow.com/questions/467336#920480
- $containers = $collapsible.find( '>tbody>tr' );
+ $containers = $collapsible.find( '> tbody > tr' );
if ( $defaultToggle ) {
// Exclude tablerow containing togglelink
$containers.not( $defaultToggle.closest( 'tr' ) ).stop(true, true).fadeOut();
} else {
- if ( instantHide ) {
+ if ( options.instantHide ) {
$containers.hide();
} else {
$containers.stop( true, true ).fadeOut();
// Exclude list-item containing togglelink
$containers.not( $defaultToggle.parent() ).stop( true, true ).slideUp();
} else {
- if ( instantHide ) {
+ if ( options.instantHide ) {
$containers.hide();
} else {
$containers.stop( true, true ).slideUp();
}
}
- } else { // <div>, <p> etc.
+ } else {
+ // <div>, <p> etc.
$collapsibleContent = $collapsible.find( '> .mw-collapsible-content' );
// If a collapsible-content is defined, collapse it
if ( $collapsibleContent.length ) {
- if ( instantHide ) {
+ if ( options.instantHide ) {
$collapsibleContent.hide();
} else {
$collapsibleContent.slideUp();
// Exclude tablerow containing togglelink
$containers.not( $defaultToggle.parent().parent() ).stop(true, true).fadeIn();
} else {
- $containers.stop(true, true).fadeIn();
+ $containers.stop( true, true ).fadeIn();
}
} else if ( $collapsible.is( 'ul' ) || $collapsible.is( 'ol' ) ) {
$containers.stop( true, true ).slideDown();
}
- } else { // <div>, <p> etc.
+ } else {
+ // <div>, <p> etc.
$collapsibleContent = $collapsible.find( '> .mw-collapsible-content' );
// If a collapsible-content is defined, collapse it
}
}
},
- // Toggles collapsible and togglelink class and updates text label
- toggleLinkDefault = function ( that, e ) {
- var $that = $(that),
- $collapsible = $that.closest( '.mw-collapsible.mw-made-collapsible' ).toggleClass( 'mw-collapsed' );
+ /**
+ * Toggles collapsible and togglelink class and updates text label.
+ *
+ * @param {jQuery} $that
+ * @param {jQuery.Event} e
+ * @param {Object|undefined} options
+ */
+ toggleLinkDefault = function ( $that, e, options ) {
+ var $collapsible = $that.closest( '.mw-collapsible' ).toggleClass( 'mw-collapsed' );
e.preventDefault();
e.stopPropagation();
$that.text( expandtext );
}
// Collapse element
- toggleElement( $collapsible, 'collapse', $that );
+ toggleElement( $collapsible, 'collapse', $that, options );
// It's collapsed right now
} else {
$that.text( collapsetext );
}
// Expand element
- toggleElement( $collapsible, 'expand', $that );
+ toggleElement( $collapsible, 'expand', $that, options );
}
return;
},
- // Toggles collapsible and togglelink class
- toggleLinkPremade = function ( $that, e ) {
- var $collapsible = $that.eq(0).closest( '.mw-collapsible.mw-made-collapsible' ).toggleClass( 'mw-collapsed' );
- if ( $(e.target).is( 'a' ) ) {
+ /**
+ * Toggles collapsible and togglelink class.
+ *
+ * @param {jQuery} $that
+ * @param {jQuery.Event} e
+ * @param {Object|undefined} options
+ */
+ toggleLinkPremade = function ( $that, e, options ) {
+ var $collapsible = $that.eq( 0 ).closest( '.mw-collapsible' ).toggleClass( 'mw-collapsed' );
+ if ( $.nodeName( e.target, 'a' ) ) {
return true;
}
e.preventDefault();
// Change toggle to collapsed
$that.removeClass( 'mw-collapsible-toggle-expanded' ).addClass( 'mw-collapsible-toggle-collapsed' );
// Collapse element
- toggleElement( $collapsible, 'collapse', $that );
+ toggleElement( $collapsible, 'collapse', $that, options );
// It's collapsed right now
} else {
// Change toggle to expanded
$that.removeClass( 'mw-collapsible-toggle-collapsed' ).addClass( 'mw-collapsible-toggle-expanded' );
// Expand element
- toggleElement( $collapsible, 'expand', $that );
+ toggleElement( $collapsible, 'expand', $that, options );
}
return;
},
- // Toggles customcollapsible
- toggleLinkCustom = function ( $that, e, $collapsible ) {
+ /**
+ * Toggles customcollapsible.
+ *
+ * @param {jQuery} $that
+ * @param {jQuery.Event} e
+ * @param {Object|undefined} options
+ * @param {jQuery} $collapsible
+ */
+ toggleLinkCustom = function ( $that, e, options, $collapsible ) {
// For the initial state call of customtogglers there is no event passed
- if (e) {
+ if ( e ) {
e.preventDefault();
e.stopPropagation();
}
// Get current state and toggle to the opposite
var action = $collapsible.hasClass( 'mw-collapsed' ) ? 'expand' : 'collapse';
$collapsible.toggleClass( 'mw-collapsed' );
- toggleElement( $collapsible, action, $that );
+ toggleElement( $collapsible, action, $that, options );
};
+ // Return if it has been enabled already.
+ if ( $collapsible.data( 'mw-made-collapsible' ) ) {
+ return;
+ } else {
+ $collapsible.data( 'mw-made-collapsible', true );
+ }
+
// Use custom text or default ?
if ( !collapsetext ) {
collapsetext = mw.msg( 'collapsible-collapse' );
}
// Create toggle link with a space around the brackets ( [text] )
- var $toggleLink =
+ $toggleLink =
$( '<a href="#"></a>' )
.text( collapsetext )
.wrap( '<span class="mw-collapsible-toggle"></span>' )
- .parent()
- .prepend( ' [' )
- .append( '] ' )
- .on( 'click.mw-collapse', function ( e ) {
- toggleLinkDefault( this, e );
- } );
-
- // Return if it has been enabled already.
- if ( $that.hasClass( 'mw-made-collapsible' ) ) {
- return;
- } else {
- $that.addClass( 'mw-made-collapsible' );
- }
+ .parent()
+ .prepend( ' [' )
+ .append( '] ' )
+ .on( 'click.mw-collapse', function ( e, options ) {
+ toggleLinkDefault( $(this), e, options );
+ } );
// Check if this element has a custom position for the toggle link
// (ie. outside the container or deeper inside the tree)
// Then: Locate the custom toggle link(s) and bind them
- if ( ( $that.attr( 'id' ) || '' ).indexOf( 'mw-customcollapsible-' ) === 0 ) {
+ if ( ( $collapsible.attr( 'id' ) || '' ).indexOf( 'mw-customcollapsible-' ) === 0 ) {
- var thatId = $that.attr( 'id' ),
- $customTogglers = $( '.' + thatId.replace( 'mw-customcollapsible', 'mw-customtoggle' ) );
- mw.log( lpx + 'Found custom collapsible: #' + thatId );
+ collapsibleId = $collapsible.attr( 'id' );
+ $customTogglers = $( '.' + collapsibleId.replace( 'mw-customcollapsible', 'mw-customtoggle' ) );
+ mw.log( lpx + 'Found custom collapsible: #' + collapsibleId );
// Double check that there is actually a customtoggle link
if ( $customTogglers.length ) {
- $customTogglers.on( 'click.mw-collapse', function ( e ) {
- toggleLinkCustom( $(this), e, $that );
+ $customTogglers.on( 'click.mw-collapse', function ( e, options ) {
+ toggleLinkCustom( $(this), e, options, $collapsible );
} );
} else {
- mw.log( lpx + '#' + thatId + ': Missing toggler!' );
+ mw.log( lpx + '#' + collapsibleId + ': Missing toggler!' );
}
// Initial state
- if ( $that.hasClass( 'mw-collapsed' ) ) {
- $that.removeClass( 'mw-collapsed' );
- toggleLinkCustom( $customTogglers, null, $that );
+ if ( $collapsible.hasClass( 'mw-collapsed' ) ) {
+ // Remove here so that the toggler goes in the right direction,
+ // It re-adds the class.
+ $collapsible.removeClass( 'mw-collapsed' );
+ toggleLinkCustom( $customTogglers, null, { instantHide: true }, $collapsible );
}
// If this is not a custom case, do the default:
} else {
// Elements are treated differently
- if ( $that.is( 'table' ) ) {
+ if ( $collapsible.is( 'table' ) ) {
// The toggle-link will be in one the the cells (td or th) of the first row
- var $firstRowCells = $that.find( 'tr:first th, tr:first td' );
- $toggle = $firstRowCells.find( '> .mw-collapsible-toggle' );
+ $firstItem = $collapsible.find( 'tr:first th, tr:first td' );
+ $toggle = $firstItem.find( '> .mw-collapsible-toggle' );
// If theres no toggle link, add it to the last cell
if ( !$toggle.length ) {
- $firstRowCells.eq(-1).prepend( $toggleLink );
+ $firstItem.eq(-1).prepend( $toggleLink );
} else {
- $toggleLink = $toggle.off( 'click.mw-collapse' ).on( 'click.mw-collapse', function ( e ) {
- toggleLinkPremade( $toggle, e );
+ $toggleLink = $toggle.off( 'click.mw-collapse' ).on( 'click.mw-collapse', function ( e, options ) {
+ toggleLinkPremade( $toggle, e, options );
} );
}
- } else if ( $that.is( 'ul' ) || $that.is( 'ol' ) ) {
+ } else if ( $collapsible.is( 'ul' ) || $collapsible.is( 'ol' ) ) {
// The toggle-link will be in the first list-item
- var $firstItem = $that.find( 'li:first' );
+ $firstItem = $collapsible.find( 'li:first' );
$toggle = $firstItem.find( '> .mw-collapsible-toggle' );
// If theres no toggle link, add it
// Make sure the numeral order doesn't get messed up, force the first (soon to be second) item
// to be "1". Except if the value-attribute is already used.
// If no value was set WebKit returns "", Mozilla returns '-1', others return null or undefined.
- var firstval = $firstItem.attr( 'value' );
+ firstval = $firstItem.attr( 'value' );
if ( firstval === undefined || !firstval || firstval === '-1' || firstval === -1 ) {
$firstItem.attr( 'value', '1' );
}
- $that.prepend( $toggleLink.wrap( '<li class="mw-collapsible-toggle-li"></li>' ).parent() );
+ $collapsible.prepend( $toggleLink.wrap( '<li class="mw-collapsible-toggle-li"></li>' ).parent() );
} else {
- $toggleLink = $toggle.off( 'click.mw-collapse' ).on( 'click.mw-collapse', function ( e ) {
- toggleLinkPremade( $toggle, e );
+ $toggleLink = $toggle.off( 'click.mw-collapse' ).on( 'click.mw-collapse', function ( e, options ) {
+ toggleLinkPremade( $toggle, e, options );
} );
}
} else { // <div>, <p> etc.
// The toggle-link will be the first child of the element
- $toggle = $that.find( '> .mw-collapsible-toggle' );
+ $toggle = $collapsible.find( '> .mw-collapsible-toggle' );
// If a direct child .content-wrapper does not exists, create it
- if ( !$that.find( '> .mw-collapsible-content' ).length ) {
- $that.wrapInner( '<div class="mw-collapsible-content"></div>' );
+ if ( !$collapsible.find( '> .mw-collapsible-content' ).length ) {
+ $collapsible.wrapInner( '<div class="mw-collapsible-content"></div>' );
}
// If theres no toggle link, add it
if ( !$toggle.length ) {
- $that.prepend( $toggleLink );
+ $collapsible.prepend( $toggleLink );
} else {
- $toggleLink = $toggle.off( 'click.mw-collapse' ).on( 'click.mw-collapse', function ( e ) {
- toggleLinkPremade( $toggle, e );
+ $toggleLink = $toggle.off( 'click.mw-collapse' ).on( 'click.mw-collapse', function ( e, options ) {
+ toggleLinkPremade( $toggle, e, options );
} );
}
}
}
- // Initial state (only for those that are not custom)
- if ( $that.hasClass( 'mw-collapsed' ) && ( $that.attr( 'id' ) || '').indexOf( 'mw-customcollapsible-' ) !== 0 ) {
- $that.removeClass( 'mw-collapsed' );
+ // Initial state (only for those that are not custom,
+ // because the initial state of those has been taken care of already).
+ if ( $collapsible.hasClass( 'mw-collapsed' ) && ( $collapsible.attr( 'id' ) || '').indexOf( 'mw-customcollapsible-' ) !== 0 ) {
+ $collapsible.removeClass( 'mw-collapsed' );
// The collapsible element could have multiple togglers
// To toggle the initial state only click one of them (ie. the first one, eq(0) )
// Else it would go like: hide,show,hide,show for each toggle link.
- toggleElement( $that, 'collapse', $toggleLink.eq(0), /* instantHide = */ true );
- $toggleLink.eq(0).click();
+ // This is just like it would be in reality (only one toggle is clicked at a time).
+ $toggleLink.eq( 0 ).trigger( 'click', [ { instantHide: true } ] );
}
} );
};
* @example $( 'table' ).tablesorter();
* @desc Create a simple tablesorter interface.
*
+ * @example $( 'table' ).tablesorter( { sortList: [ { 0: 'desc' }, { 1: 'asc' } ] } );
+ * @desc Create a tablesorter interface initially sorting on the first and second column.
+ *
* @option String cssHeader ( optional ) A string of the class name to be appended
* to sortable tr elements in the thead of the table. Default value:
* "header"
* tablesorter should cancel selection of the table headers text.
* Default value: true
*
+ * @option Array sortList ( optional ) An array containing objects specifying sorting.
+ * By passing more than one object, multi-sorting will be applied. Object structure:
+ * { <Integer column index>: <String 'asc' or 'desc'> }
+ * Default value: []
+ *
* @option Boolean debug ( optional ) Boolean flag indicating if tablesorter
* should display debuging information usefull for development.
*
+ * @event sortEnd.tablesorter: Triggered as soon as any sorting has been applied.
+ *
* @type jQuery
*
* @name tablesorter
for ( i = 0; i < len; i++ ) {
parser = false;
- sortType = $headers.eq( i ).data( 'sort-type' );
+ sortType = $headers.eq( i ).data( 'sortType' );
if ( sortType !== undefined ) {
parser = getParserById( sortType );
}
}
table.tBodies[0].appendChild( fragment );
+
+ $( table ).trigger( 'sortEnd.tablesorter' );
}
/**
}
function setHeadersCss( table, $headers, list, css, msg ) {
- // Remove all header information
- $headers.removeClass( css[0] ).removeClass( css[1] );
+ // Remove all header information and reset titles to default message
+ $headers.removeClass( css[0] ).removeClass( css[1] ).attr( 'title', msg[1] );
var h = [];
$headers.each( function ( offset ) {
};
}
+ /**
+ * Converts sort objects [ { Integer: String }, ... ] to the internally used nested array
+ * structure [ [ Integer , Integer ], ... ]
+ *
+ * @param sortObjects {Array} List of sort objects.
+ * @return {Array} List of internal sort definitions.
+ */
+
+ function convertSortList( sortObjects ) {
+ var sortList = [];
+ $.each( sortObjects, function( i, sortObject ) {
+ $.each ( sortObject, function( columnIndex, order ) {
+ var orderIndex = ( order === 'desc' ) ? 1 : 0;
+ sortList.push( [columnIndex, orderIndex] );
+ } );
+ } );
+ return sortList;
+ }
+
/* Public scope */
$.tablesorter = {
// Declare and cache.
var $document, $headers, cache, config, sortOrder,
$table = $( table ),
- shiftDown = 0,
- firstTime = true;
+ shiftDown = 0;
// Quit if no tbody
if ( !table.tBodies ) {
}
$table.addClass( "jquery-tablesorter" );
+ // FIXME config should probably not be stored in the plain table node
// New config object.
table.config = {};
config = $.extend( table.config, $.tablesorter.defaultOptions, settings );
// Save the settings where they read
- $.data( table, 'tablesorter', config );
+ $.data( table, 'tablesorter', { config: config } );
// Get the CSS class names, could be done else where.
var sortCSS = [ config.cssDesc, config.cssAsc ];
// performance improvements in some browsers.
cacheRegexs();
+ // Legacy fix of .sortbottoms
+ // Wrap them inside inside a tfoot (because that's what they actually want to be) &
+ // and put the <tfoot> at the end of the <table>
+ var $sortbottoms = $table.find( '> tbody > tr.sortbottom' );
+ if ( $sortbottoms.length ) {
+ var $tfoot = $table.children( 'tfoot' );
+ if ( $tfoot.length ) {
+ $tfoot.eq(0).prepend( $sortbottoms );
+ } else {
+ $table.append( $( '<tfoot>' ).append( $sortbottoms ) );
+ }
+ }
+
+ explodeRowspans( $table );
+
+ // try to auto detect column type, and store in tables config
+ table.config.parsers = buildParserCache( table, $headers );
+
+ // initially build the cache for the tbody cells (to be able to sort initially)
+ cache = buildCache( table );
+
// Apply event handling to headers
// this is too big, perhaps break it out?
- $headers.click( function ( e ) {
+ $headers.filter( ':not(.unsortable)' ).click( function ( e ) {
if ( e.target.nodeName.toLowerCase() === 'a' ) {
// The user clicked on a link inside a table header
// Do nothing and let the default link click action continue
return true;
}
- if ( firstTime ) {
- firstTime = false;
-
- // Legacy fix of .sortbottoms
- // Wrap them inside inside a tfoot (because that's what they actually want to be) &
- // and put the <tfoot> at the end of the <table>
- var $sortbottoms = $table.find( '> tbody > tr.sortbottom' );
- if ( $sortbottoms.length ) {
- var $tfoot = $table.children( 'tfoot' );
- if ( $tfoot.length ) {
- $tfoot.eq(0).prepend( $sortbottoms );
- } else {
- $table.append( $( '<tfoot>' ).append( $sortbottoms ) );
- }
- }
-
- explodeRowspans( $table );
- // try to auto detect column type, and store in tables config
- table.config.parsers = buildParserCache( table, $headers );
- }
-
// Build the cache for the tbody cells
// to share between calculations for this sort action.
// Re-calculated each time a sort action is performed due to possiblity
return false;
}
} );
+
+ /**
+ * Sorts the table. If no sorting is specified by passing a list of sort
+ * objects, the table is sorted according to the initial sorting order.
+ * Passing an empty array will reset sorting (basically just reset the headers
+ * making the table appear unsorted).
+ *
+ * @param sortList {Array} (optional) List of sort objects.
+ */
+ $table.data( 'tablesorter' ).sort = function( sortList ) {
+
+ if ( sortList === undefined ) {
+ sortList = config.sortList;
+ } else if ( sortList.length > 0 ) {
+ sortList = convertSortList( sortList );
+ }
+
+ // re-build the cache for the tbody cells
+ cache = buildCache( table );
+
+ // set css for headers
+ setHeadersCss( table, $headers, sortList, sortCSS, sortMsg );
+
+ // sort the table and append it to the dom
+ appendToTable( table, multisort( table, sortList, cache ) );
+ };
+
+ // sort initially
+ if ( config.sortList.length > 0 ) {
+ explodeRowspans( $table );
+ config.sortList = convertSortList( config.sortList );
+ $table.data( 'tablesorter' ).sort();
+ }
+
} );
},
--- /dev/null
+$( function() {
+ // Apply hidpi images on DOM-ready
+ // Some may have already partly preloaded at low resolution.
+ $( 'body' ).hidpi();
+} );
\ No newline at end of file
* @param module string module name to execute
*/
function execute( module ) {
- var key, value, media, i, script, markModuleReady, nestedAddScript;
+ var key, value, media, i, urls, script, markModuleReady, nestedAddScript;
if ( registry[module] === undefined ) {
throw new Error( 'Module has not been registered yet: ' + module );
// Array of urls inside media-type key
} else if ( typeof value === 'object' ) {
// { "url": { <media>: [url, ..] } }
- $.each( value, addLink );
+ for ( media in value ) {
+ urls = value[media];
+ for ( i = 0; i < urls.length; i += 1 ) {
+ addLink( media, urls[i] );
+ }
+ }
}
}
}
var jqXhr = $(this).data( 'request' );
// If the delay setting has caused the fetch to have not even happened
// yet, the jqXHR object will have never been set.
- if ( jqXhr && $.isFunction ( jqXhr.abort ) ) {
+ if ( jqXhr && $.isFunction( jqXhr.abort ) ) {
jqXhr.abort();
$(this).removeData( 'request' );
}
.append(
$( '<div>' )
.addClass( 'special-label' )
- .text( mw.msg( 'searchsuggest-containing' ) )
- )
- .append(
+ .text( mw.msg( 'searchsuggest-containing' ) ),
$( '<div>' )
.addClass( 'special-query' )
.text( query )
.show();
} else {
$el.find( '.special-query' )
- .empty()
.text( query )
.autoEllipsis();
}
. "<input type='submit' class=\"searchButton\" name=\"go\" value=\"" . wfMessage( 'searcharticle' )->escaped() . "\" />";
if( $wgUseTwoButtonsSearchForm ) {
- $s .= " <input type='submit' class=\"searchButton\" name=\"fulltext\" value=\"" . wfMessage( 'search' )->escaped() . "\" />\n";
+ $s .= " <input type='submit' class=\"searchButton\" name=\"fulltext\" value=\"" . wfMessage( 'searchbutton' )->escaped() . "\" />\n";
} else {
$s .= '<div><a href="' . $action . '" rel="search">' . wfMessage( 'powersearch-legend' )->escaped() . "</a></div>\n";
}
+++ /dev/null
-Allow from all
+++ /dev/null
-<!DOCTYPE html>
-<html lang="en" dir="ltr">
- <head>
- <title>Jasmine Test Runner</title>
- <meta charset="UTF-8" />
- <link rel="stylesheet" type="text/css" href="lib/jasmine-1.0.1/jasmine.css">
- <script src="lib/jasmine-1.0.1/jasmine.js"></script>
- <script src="lib/jasmine-1.0.1/jasmine-html.js"></script>
-
- <!-- include source files here... -->
- <script src="../../load.php?debug=true&lang=en&modules=startup&only=scripts&skin=vector&*"></script>
- <script>
- mw.loader.load( ['mediawiki.jqueryMsg'] );
- </script>
-
- <!-- insert test data files here -->
- <script src="spec/mediawiki.jqueryMsg.spec.data.js"></script>
-
- <!-- include spec files here... -->
- <script src="spec/mediawiki.jqueryMsg.spec.js"></script>
- </head>
-<body>
- <script>
- jasmine.getEnv().addReporter( new jasmine.TrivialReporter() );
- jasmine.getEnv().execute();
- </script>
-</body>
-</html>
+++ /dev/null
-Copyright (c) 2008-2010 Pivotal Labs
-
-Permission is hereby granted, free of charge, to any person obtaining
-a copy of this software and associated documentation files (the
-"Software"), to deal in the Software without restriction, including
-without limitation the rights to use, copy, modify, merge, publish,
-distribute, sublicense, and/or sell copies of the Software, and to
-permit persons to whom the Software is furnished to do so, subject to
-the following conditions:
-
-The above copyright notice and this permission notice shall be
-included in all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+++ /dev/null
-jasmine.TrivialReporter = function(doc) {
- this.document = doc || document;
- this.suiteDivs = {};
- this.logRunningSpecs = false;
-};
-
-jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) {
- var el = document.createElement(type);
-
- for (var i = 2; i < arguments.length; i++) {
- var child = arguments[i];
-
- if (typeof child === 'string') {
- el.appendChild(document.createTextNode(child));
- } else {
- if (child) { el.appendChild(child); }
- }
- }
-
- for (var attr in attrs) {
- if (attr == "className") {
- el[attr] = attrs[attr];
- } else {
- el.setAttribute(attr, attrs[attr]);
- }
- }
-
- return el;
-};
-
-jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) {
- var showPassed, showSkipped;
-
- this.outerDiv = this.createDom('div', { className: 'jasmine_reporter' },
- this.createDom('div', { className: 'banner' },
- this.createDom('div', { className: 'logo' },
- this.createDom('a', { href: 'http://pivotal.github.com/jasmine/', target: "_blank" }, "Jasmine"),
- this.createDom('span', { className: 'version' }, runner.env.versionString())),
- this.createDom('div', { className: 'options' },
- "Show ",
- showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }),
- this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "),
- showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }),
- this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped")
- )
- ),
-
- this.runnerDiv = this.createDom('div', { className: 'runner running' },
- this.createDom('a', { className: 'run_spec', href: '?' }, "run all"),
- this.runnerMessageSpan = this.createDom('span', {}, "Running..."),
- this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, ""))
- );
-
- this.document.body.appendChild(this.outerDiv);
-
- var suites = runner.suites();
- for (var i = 0; i < suites.length; i++) {
- var suite = suites[i];
- var suiteDiv = this.createDom('div', { className: 'suite' },
- this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"),
- this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description));
- this.suiteDivs[suite.id] = suiteDiv;
- var parentDiv = this.outerDiv;
- if (suite.parentSuite) {
- parentDiv = this.suiteDivs[suite.parentSuite.id];
- }
- parentDiv.appendChild(suiteDiv);
- }
-
- this.startedAt = new Date();
-
- var self = this;
- showPassed.onclick = function(evt) {
- if (showPassed.checked) {
- self.outerDiv.className += ' show-passed';
- } else {
- self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, '');
- }
- };
-
- showSkipped.onclick = function(evt) {
- if (showSkipped.checked) {
- self.outerDiv.className += ' show-skipped';
- } else {
- self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, '');
- }
- };
-};
-
-jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) {
- var results = runner.results();
- var className = (results.failedCount > 0) ? "runner failed" : "runner passed";
- this.runnerDiv.setAttribute("class", className);
- //do it twice for IE
- this.runnerDiv.setAttribute("className", className);
- var specs = runner.specs();
- var specCount = 0;
- for (var i = 0; i < specs.length; i++) {
- if (this.specFilter(specs[i])) {
- specCount++;
- }
- }
- var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s");
- message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s";
- this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild);
-
- this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString()));
-};
-
-jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) {
- var results = suite.results();
- var status = results.passed() ? 'passed' : 'failed';
- if (results.totalCount == 0) { // todo: change this to check results.skipped
- status = 'skipped';
- }
- this.suiteDivs[suite.id].className += " " + status;
-};
-
-jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) {
- if (this.logRunningSpecs) {
- this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...');
- }
-};
-
-jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) {
- var results = spec.results();
- var status = results.passed() ? 'passed' : 'failed';
- if (results.skipped) {
- status = 'skipped';
- }
- var specDiv = this.createDom('div', { className: 'spec ' + status },
- this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"),
- this.createDom('a', {
- className: 'description',
- href: '?spec=' + encodeURIComponent(spec.getFullName()),
- title: spec.getFullName()
- }, spec.description));
-
-
- var resultItems = results.getItems();
- var messagesDiv = this.createDom('div', { className: 'messages' });
- for (var i = 0; i < resultItems.length; i++) {
- var result = resultItems[i];
-
- if (result.type == 'log') {
- messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString()));
- } else if (result.type == 'expect' && result.passed && !result.passed()) {
- messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message));
-
- if (result.trace.stack) {
- messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack));
- }
- }
- }
-
- if (messagesDiv.childNodes.length > 0) {
- specDiv.appendChild(messagesDiv);
- }
-
- this.suiteDivs[spec.suite.id].appendChild(specDiv);
-};
-
-jasmine.TrivialReporter.prototype.log = function() {
- var console = jasmine.getGlobal().console;
- if (console && console.log) {
- if (console.log.apply) {
- console.log.apply(console, arguments);
- } else {
- console.log(arguments); // ie fix: console.log.apply doesn't exist on ie
- }
- }
-};
-
-jasmine.TrivialReporter.prototype.getLocation = function() {
- return this.document.location;
-};
-
-jasmine.TrivialReporter.prototype.specFilter = function(spec) {
- var paramMap = {};
- var params = this.getLocation().search.substring(1).split('&');
- for (var i = 0; i < params.length; i++) {
- var p = params[i].split('=');
- paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
- }
-
- if (!paramMap["spec"]) return true;
- return spec.getFullName().indexOf(paramMap["spec"]) == 0;
-};
+++ /dev/null
-body {
- font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif;
-}
-
-
-.jasmine_reporter a:visited, .jasmine_reporter a {
- color: #303;
-}
-
-.jasmine_reporter a:hover, .jasmine_reporter a:active {
- color: blue;
-}
-
-.run_spec {
- float:right;
- padding-right: 5px;
- font-size: .8em;
- text-decoration: none;
-}
-
-.jasmine_reporter {
- margin: 0 5px;
-}
-
-.banner {
- color: #303;
- background-color: #fef;
- padding: 5px;
-}
-
-.logo {
- float: left;
- font-size: 1.1em;
- padding-left: 5px;
-}
-
-.logo .version {
- font-size: .6em;
- padding-left: 1em;
-}
-
-.runner.running {
- background-color: yellow;
-}
-
-
-.options {
- text-align: right;
- font-size: .8em;
-}
-
-
-
-
-.suite {
- border: 1px outset gray;
- margin: 5px 0;
- padding-left: 1em;
-}
-
-.suite .suite {
- margin: 5px;
-}
-
-.suite.passed {
- background-color: #dfd;
-}
-
-.suite.failed {
- background-color: #fdd;
-}
-
-.spec {
- margin: 5px;
- padding-left: 1em;
- clear: both;
-}
-
-.spec.failed, .spec.passed, .spec.skipped {
- padding-bottom: 5px;
- border: 1px solid gray;
-}
-
-.spec.failed {
- background-color: #fbb;
- border-color: red;
-}
-
-.spec.passed {
- background-color: #bfb;
- border-color: green;
-}
-
-.spec.skipped {
- background-color: #bbb;
-}
-
-.messages {
- border-left: 1px dashed gray;
- padding-left: 1em;
- padding-right: 1em;
-}
-
-.passed {
- background-color: #cfc;
- display: none;
-}
-
-.failed {
- background-color: #fbb;
-}
-
-.skipped {
- color: #777;
- background-color: #eee;
- display: none;
-}
-
-
-/*.resultMessage {*/
- /*white-space: pre;*/
-/*}*/
-
-.resultMessage span.result {
- display: block;
- line-height: 2em;
- color: black;
-}
-
-.resultMessage .mismatch {
- color: black;
-}
-
-.stackTrace {
- white-space: pre;
- font-size: .8em;
- margin-left: 10px;
- max-height: 5em;
- overflow: auto;
- border: 1px inset red;
- padding: 1em;
- background: #eef;
-}
-
-.finished-at {
- padding-left: 1em;
- font-size: .6em;
-}
-
-.show-passed .passed,
-.show-skipped .skipped {
- display: block;
-}
-
-
-#jasmine_content {
- position:fixed;
- right: 100%;
-}
-
-.runner {
- border: 1px solid gray;
- display: block;
- margin: 5px 0;
- padding: 2px 0 2px 10px;
-}
+++ /dev/null
-/**
- * Top level namespace for Jasmine, a lightweight JavaScript BDD/spec/testing framework.
- *
- * @namespace
- */
-var jasmine = {};
-
-/**
- * @private
- */
-jasmine.unimplementedMethod_ = function() {
- throw new Error("unimplemented method");
-};
-
-/**
- * Use <code>jasmine.undefined</code> instead of <code>undefined</code>, since <code>undefined</code> is just
- * a plain old variable and may be redefined by somebody else.
- *
- * @private
- */
-jasmine.undefined = jasmine.___undefined___;
-
-/**
- * Default interval in milliseconds for event loop yields (e.g. to allow network activity or to refresh the screen with the HTML-based runner). Small values here may result in slow test running. Zero means no updates until all tests have completed.
- *
- */
-jasmine.DEFAULT_UPDATE_INTERVAL = 250;
-
-/**
- * Default timeout interval in milliseconds for waitsFor() blocks.
- */
-jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000;
-
-jasmine.getGlobal = function() {
- function getGlobal() {
- return this;
- }
-
- return getGlobal();
-};
-
-/**
- * Allows for bound functions to be compared. Internal use only.
- *
- * @ignore
- * @private
- * @param base {Object} bound 'this' for the function
- * @param name {Function} function to find
- */
-jasmine.bindOriginal_ = function(base, name) {
- var original = base[name];
- if (original.apply) {
- return function() {
- return original.apply(base, arguments);
- };
- } else {
- // IE support
- return jasmine.getGlobal()[name];
- }
-};
-
-jasmine.setTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'setTimeout');
-jasmine.clearTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearTimeout');
-jasmine.setInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'setInterval');
-jasmine.clearInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearInterval');
-
-jasmine.MessageResult = function(values) {
- this.type = 'log';
- this.values = values;
- this.trace = new Error(); // todo: test better
-};
-
-jasmine.MessageResult.prototype.toString = function() {
- var text = "";
- for(var i = 0; i < this.values.length; i++) {
- if (i > 0) text += " ";
- if (jasmine.isString_(this.values[i])) {
- text += this.values[i];
- } else {
- text += jasmine.pp(this.values[i]);
- }
- }
- return text;
-};
-
-jasmine.ExpectationResult = function(params) {
- this.type = 'expect';
- this.matcherName = params.matcherName;
- this.passed_ = params.passed;
- this.expected = params.expected;
- this.actual = params.actual;
-
- this.message = this.passed_ ? 'Passed.' : params.message;
- this.trace = this.passed_ ? '' : new Error(this.message);
-};
-
-jasmine.ExpectationResult.prototype.toString = function () {
- return this.message;
-};
-
-jasmine.ExpectationResult.prototype.passed = function () {
- return this.passed_;
-};
-
-/**
- * Getter for the Jasmine environment. Ensures one gets created
- */
-jasmine.getEnv = function() {
- return jasmine.currentEnv_ = jasmine.currentEnv_ || new jasmine.Env();
-};
-
-/**
- * @ignore
- * @private
- * @param value
- * @returns {Boolean}
- */
-jasmine.isArray_ = function(value) {
- return jasmine.isA_("Array", value);
-};
-
-/**
- * @ignore
- * @private
- * @param value
- * @returns {Boolean}
- */
-jasmine.isString_ = function(value) {
- return jasmine.isA_("String", value);
-};
-
-/**
- * @ignore
- * @private
- * @param value
- * @returns {Boolean}
- */
-jasmine.isNumber_ = function(value) {
- return jasmine.isA_("Number", value);
-};
-
-/**
- * @ignore
- * @private
- * @param {String} typeName
- * @param value
- * @returns {Boolean}
- */
-jasmine.isA_ = function(typeName, value) {
- return Object.prototype.toString.apply(value) === '[object ' + typeName + ']';
-};
-
-/**
- * Pretty printer for expecations. Takes any object and turns it into a human-readable string.
- *
- * @param value {Object} an object to be outputted
- * @returns {String}
- */
-jasmine.pp = function(value) {
- var stringPrettyPrinter = new jasmine.StringPrettyPrinter();
- stringPrettyPrinter.format(value);
- return stringPrettyPrinter.string;
-};
-
-/**
- * Returns true if the object is a DOM Node.
- *
- * @param {Object} obj object to check
- * @returns {Boolean}
- */
-jasmine.isDomNode = function(obj) {
- return obj['nodeType'] > 0;
-};
-
-/**
- * Returns a matchable 'generic' object of the class type. For use in expecations of type when values don't matter.
- *
- * @example
- * // don't care about which function is passed in, as long as it's a function
- * expect(mySpy).toHaveBeenCalledWith(jasmine.any(Function));
- *
- * @param {Class} clazz
- * @returns matchable object of the type clazz
- */
-jasmine.any = function(clazz) {
- return new jasmine.Matchers.Any(clazz);
-};
-
-/**
- * Jasmine Spies are test doubles that can act as stubs, spies, fakes or when used in an expecation, mocks.
- *
- * Spies should be created in test setup, before expectations. They can then be checked, using the standard Jasmine
- * expectation syntax. Spies can be checked if they were called or not and what the calling params were.
- *
- * A Spy has the following fields: wasCalled, callCount, mostRecentCall, and argsForCall (see docs).
- *
- * Spies are torn down at the end of every spec.
- *
- * Note: Do <b>not</b> call new jasmine.Spy() directly - a spy must be created using spyOn, jasmine.createSpy or jasmine.createSpyObj.
- *
- * @example
- * // a stub
- * var myStub = jasmine.createSpy('myStub'); // can be used anywhere
- *
- * // spy example
- * var foo = {
- * not: function(bool) { return !bool; }
- * }
- *
- * // actual foo.not will not be called, execution stops
- * spyOn(foo, 'not');
-
- // foo.not spied upon, execution will continue to implementation
- * spyOn(foo, 'not').andCallThrough();
- *
- * // fake example
- * var foo = {
- * not: function(bool) { return !bool; }
- * }
- *
- * // foo.not(val) will return val
- * spyOn(foo, 'not').andCallFake(function(value) {return value;});
- *
- * // mock example
- * foo.not(7 == 7);
- * expect(foo.not).toHaveBeenCalled();
- * expect(foo.not).toHaveBeenCalledWith(true);
- *
- * @constructor
- * @see spyOn, jasmine.createSpy, jasmine.createSpyObj
- * @param {String} name
- */
-jasmine.Spy = function(name) {
- /**
- * The name of the spy, if provided.
- */
- this.identity = name || 'unknown';
- /**
- * Is this Object a spy?
- */
- this.isSpy = true;
- /**
- * The actual function this spy stubs.
- */
- this.plan = function() {
- };
- /**
- * Tracking of the most recent call to the spy.
- * @example
- * var mySpy = jasmine.createSpy('foo');
- * mySpy(1, 2);
- * mySpy.mostRecentCall.args = [1, 2];
- */
- this.mostRecentCall = {};
-
- /**
- * Holds arguments for each call to the spy, indexed by call count
- * @example
- * var mySpy = jasmine.createSpy('foo');
- * mySpy(1, 2);
- * mySpy(7, 8);
- * mySpy.mostRecentCall.args = [7, 8];
- * mySpy.argsForCall[0] = [1, 2];
- * mySpy.argsForCall[1] = [7, 8];
- */
- this.argsForCall = [];
- this.calls = [];
-};
-
-/**
- * Tells a spy to call through to the actual implemenatation.
- *
- * @example
- * var foo = {
- * bar: function() { // do some stuff }
- * }
- *
- * // defining a spy on an existing property: foo.bar
- * spyOn(foo, 'bar').andCallThrough();
- */
-jasmine.Spy.prototype.andCallThrough = function() {
- this.plan = this.originalValue;
- return this;
-};
-
-/**
- * For setting the return value of a spy.
- *
- * @example
- * // defining a spy from scratch: foo() returns 'baz'
- * var foo = jasmine.createSpy('spy on foo').andReturn('baz');
- *
- * // defining a spy on an existing property: foo.bar() returns 'baz'
- * spyOn(foo, 'bar').andReturn('baz');
- *
- * @param {Object} value
- */
-jasmine.Spy.prototype.andReturn = function(value) {
- this.plan = function() {
- return value;
- };
- return this;
-};
-
-/**
- * For throwing an exception when a spy is called.
- *
- * @example
- * // defining a spy from scratch: foo() throws an exception w/ message 'ouch'
- * var foo = jasmine.createSpy('spy on foo').andThrow('baz');
- *
- * // defining a spy on an existing property: foo.bar() throws an exception w/ message 'ouch'
- * spyOn(foo, 'bar').andThrow('baz');
- *
- * @param {String} exceptionMsg
- */
-jasmine.Spy.prototype.andThrow = function(exceptionMsg) {
- this.plan = function() {
- throw exceptionMsg;
- };
- return this;
-};
-
-/**
- * Calls an alternate implementation when a spy is called.
- *
- * @example
- * var baz = function() {
- * // do some stuff, return something
- * }
- * // defining a spy from scratch: foo() calls the function baz
- * var foo = jasmine.createSpy('spy on foo').andCall(baz);
- *
- * // defining a spy on an existing property: foo.bar() calls an anonymnous function
- * spyOn(foo, 'bar').andCall(function() { return 'baz';} );
- *
- * @param {Function} fakeFunc
- */
-jasmine.Spy.prototype.andCallFake = function(fakeFunc) {
- this.plan = fakeFunc;
- return this;
-};
-
-/**
- * Resets all of a spy's the tracking variables so that it can be used again.
- *
- * @example
- * spyOn(foo, 'bar');
- *
- * foo.bar();
- *
- * expect(foo.bar.callCount).toEqual(1);
- *
- * foo.bar.reset();
- *
- * expect(foo.bar.callCount).toEqual(0);
- */
-jasmine.Spy.prototype.reset = function() {
- this.wasCalled = false;
- this.callCount = 0;
- this.argsForCall = [];
- this.calls = [];
- this.mostRecentCall = {};
-};
-
-jasmine.createSpy = function(name) {
-
- var spyObj = function() {
- spyObj.wasCalled = true;
- spyObj.callCount++;
- var args = jasmine.util.argsToArray(arguments);
- spyObj.mostRecentCall.object = this;
- spyObj.mostRecentCall.args = args;
- spyObj.argsForCall.push(args);
- spyObj.calls.push({object: this, args: args});
- return spyObj.plan.apply(this, arguments);
- };
-
- var spy = new jasmine.Spy(name);
-
- for (var prop in spy) {
- spyObj[prop] = spy[prop];
- }
-
- spyObj.reset();
-
- return spyObj;
-};
-
-/**
- * Determines whether an object is a spy.
- *
- * @param {jasmine.Spy|Object} putativeSpy
- * @returns {Boolean}
- */
-jasmine.isSpy = function(putativeSpy) {
- return putativeSpy && putativeSpy.isSpy;
-};
-
-/**
- * Creates a more complicated spy: an Object that has every property a function that is a spy. Used for stubbing something
- * large in one call.
- *
- * @param {String} baseName name of spy class
- * @param {Array} methodNames array of names of methods to make spies
- */
-jasmine.createSpyObj = function(baseName, methodNames) {
- if (!jasmine.isArray_(methodNames) || methodNames.length == 0) {
- throw new Error('createSpyObj requires a non-empty array of method names to create spies for');
- }
- var obj = {};
- for (var i = 0; i < methodNames.length; i++) {
- obj[methodNames[i]] = jasmine.createSpy(baseName + '.' + methodNames[i]);
- }
- return obj;
-};
-
-/**
- * All parameters are pretty-printed and concatenated together, then written to the current spec's output.
- *
- * Be careful not to leave calls to <code>jasmine.log</code> in production code.
- */
-jasmine.log = function() {
- var spec = jasmine.getEnv().currentSpec;
- spec.log.apply(spec, arguments);
-};
-
-/**
- * Function that installs a spy on an existing object's method name. Used within a Spec to create a spy.
- *
- * @example
- * // spy example
- * var foo = {
- * not: function(bool) { return !bool; }
- * }
- * spyOn(foo, 'not'); // actual foo.not will not be called, execution stops
- *
- * @see jasmine.createSpy
- * @param obj
- * @param methodName
- * @returns a Jasmine spy that can be chained with all spy methods
- */
-var spyOn = function(obj, methodName) {
- return jasmine.getEnv().currentSpec.spyOn(obj, methodName);
-};
-
-/**
- * Creates a Jasmine spec that will be added to the current suite.
- *
- * // TODO: pending tests
- *
- * @example
- * it('should be true', function() {
- * expect(true).toEqual(true);
- * });
- *
- * @param {String} desc description of this specification
- * @param {Function} func defines the preconditions and expectations of the spec
- */
-var it = function(desc, func) {
- return jasmine.getEnv().it(desc, func);
-};
-
-/**
- * Creates a <em>disabled</em> Jasmine spec.
- *
- * A convenience method that allows existing specs to be disabled temporarily during development.
- *
- * @param {String} desc description of this specification
- * @param {Function} func defines the preconditions and expectations of the spec
- */
-var xit = function(desc, func) {
- return jasmine.getEnv().xit(desc, func);
-};
-
-/**
- * Starts a chain for a Jasmine expectation.
- *
- * It is passed an Object that is the actual value and should chain to one of the many
- * jasmine.Matchers functions.
- *
- * @param {Object} actual Actual value to test against and expected value
- */
-var expect = function(actual) {
- return jasmine.getEnv().currentSpec.expect(actual);
-};
-
-/**
- * Defines part of a jasmine spec. Used in cominbination with waits or waitsFor in asynchrnous specs.
- *
- * @param {Function} func Function that defines part of a jasmine spec.
- */
-var runs = function(func) {
- jasmine.getEnv().currentSpec.runs(func);
-};
-
-/**
- * Waits a fixed time period before moving to the next block.
- *
- * @deprecated Use waitsFor() instead
- * @param {Number} timeout milliseconds to wait
- */
-var waits = function(timeout) {
- jasmine.getEnv().currentSpec.waits(timeout);
-};
-
-/**
- * Waits for the latchFunction to return true before proceeding to the next block.
- *
- * @param {Function} latchFunction
- * @param {String} optional_timeoutMessage
- * @param {Number} optional_timeout
- */
-var waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) {
- jasmine.getEnv().currentSpec.waitsFor.apply(jasmine.getEnv().currentSpec, arguments);
-};
-
-/**
- * A function that is called before each spec in a suite.
- *
- * Used for spec setup, including validating assumptions.
- *
- * @param {Function} beforeEachFunction
- */
-var beforeEach = function(beforeEachFunction) {
- jasmine.getEnv().beforeEach(beforeEachFunction);
-};
-
-/**
- * A function that is called after each spec in a suite.
- *
- * Used for restoring any state that is hijacked during spec execution.
- *
- * @param {Function} afterEachFunction
- */
-var afterEach = function(afterEachFunction) {
- jasmine.getEnv().afterEach(afterEachFunction);
-};
-
-/**
- * Defines a suite of specifications.
- *
- * Stores the description and all defined specs in the Jasmine environment as one suite of specs. Variables declared
- * are accessible by calls to beforeEach, it, and afterEach. Describe blocks can be nested, allowing for specialization
- * of setup in some tests.
- *
- * @example
- * // TODO: a simple suite
- *
- * // TODO: a simple suite with a nested describe block
- *
- * @param {String} description A string, usually the class under test.
- * @param {Function} specDefinitions function that defines several specs.
- */
-var describe = function(description, specDefinitions) {
- return jasmine.getEnv().describe(description, specDefinitions);
-};
-
-/**
- * Disables a suite of specifications. Used to disable some suites in a file, or files, temporarily during development.
- *
- * @param {String} description A string, usually the class under test.
- * @param {Function} specDefinitions function that defines several specs.
- */
-var xdescribe = function(description, specDefinitions) {
- return jasmine.getEnv().xdescribe(description, specDefinitions);
-};
-
-
-// Provide the XMLHttpRequest class for IE 5.x-6.x:
-jasmine.XmlHttpRequest = (typeof XMLHttpRequest == "undefined") ? function() {
- try {
- return new ActiveXObject("Msxml2.XMLHTTP.6.0");
- } catch(e) {
- }
- try {
- return new ActiveXObject("Msxml2.XMLHTTP.3.0");
- } catch(e) {
- }
- try {
- return new ActiveXObject("Msxml2.XMLHTTP");
- } catch(e) {
- }
- try {
- return new ActiveXObject("Microsoft.XMLHTTP");
- } catch(e) {
- }
- throw new Error("This browser does not support XMLHttpRequest.");
-} : XMLHttpRequest;
-/**
- * @namespace
- */
-jasmine.util = {};
-
-/**
- * Declare that a child class inherit it's prototype from the parent class.
- *
- * @private
- * @param {Function} childClass
- * @param {Function} parentClass
- */
-jasmine.util.inherit = function(childClass, parentClass) {
- /**
- * @private
- */
- var subclass = function() {
- };
- subclass.prototype = parentClass.prototype;
- childClass.prototype = new subclass;
-};
-
-jasmine.util.formatException = function(e) {
- var lineNumber;
- if (e.line) {
- lineNumber = e.line;
- }
- else if (e.lineNumber) {
- lineNumber = e.lineNumber;
- }
-
- var file;
-
- if (e.sourceURL) {
- file = e.sourceURL;
- }
- else if (e.fileName) {
- file = e.fileName;
- }
-
- var message = (e.name && e.message) ? (e.name + ': ' + e.message) : e.toString();
-
- if (file && lineNumber) {
- message += ' in ' + file + ' (line ' + lineNumber + ')';
- }
-
- return message;
-};
-
-jasmine.util.htmlEscape = function(str) {
- if (!str) return str;
- return str.replace(/&/g, '&')
- .replace(/</g, '<')
- .replace(/>/g, '>');
-};
-
-jasmine.util.argsToArray = function(args) {
- var arrayOfArgs = [];
- for (var i = 0; i < args.length; i++) arrayOfArgs.push(args[i]);
- return arrayOfArgs;
-};
-
-jasmine.util.extend = function(destination, source) {
- for (var property in source) destination[property] = source[property];
- return destination;
-};
-
-/**
- * Environment for Jasmine
- *
- * @constructor
- */
-jasmine.Env = function() {
- this.currentSpec = null;
- this.currentSuite = null;
- this.currentRunner_ = new jasmine.Runner(this);
-
- this.reporter = new jasmine.MultiReporter();
-
- this.updateInterval = jasmine.DEFAULT_UPDATE_INTERVAL;
- this.defaultTimeoutInterval = jasmine.DEFAULT_TIMEOUT_INTERVAL;
- this.lastUpdate = 0;
- this.specFilter = function() {
- return true;
- };
-
- this.nextSpecId_ = 0;
- this.nextSuiteId_ = 0;
- this.equalityTesters_ = [];
-
- // wrap matchers
- this.matchersClass = function() {
- jasmine.Matchers.apply(this, arguments);
- };
- jasmine.util.inherit(this.matchersClass, jasmine.Matchers);
-
- jasmine.Matchers.wrapInto_(jasmine.Matchers.prototype, this.matchersClass);
-};
-
-
-jasmine.Env.prototype.setTimeout = jasmine.setTimeout;
-jasmine.Env.prototype.clearTimeout = jasmine.clearTimeout;
-jasmine.Env.prototype.setInterval = jasmine.setInterval;
-jasmine.Env.prototype.clearInterval = jasmine.clearInterval;
-
-/**
- * @returns an object containing jasmine version build info, if set.
- */
-jasmine.Env.prototype.version = function () {
- if (jasmine.version_) {
- return jasmine.version_;
- } else {
- throw new Error('Version not set');
- }
-};
-
-/**
- * @returns string containing jasmine version build info, if set.
- */
-jasmine.Env.prototype.versionString = function() {
- if (jasmine.version_) {
- var version = this.version();
- return version.major + "." + version.minor + "." + version.build + " revision " + version.revision;
- } else {
- return "version unknown";
- }
-};
-
-/**
- * @returns a sequential integer starting at 0
- */
-jasmine.Env.prototype.nextSpecId = function () {
- return this.nextSpecId_++;
-};
-
-/**
- * @returns a sequential integer starting at 0
- */
-jasmine.Env.prototype.nextSuiteId = function () {
- return this.nextSuiteId_++;
-};
-
-/**
- * Register a reporter to receive status updates from Jasmine.
- * @param {jasmine.Reporter} reporter An object which will receive status updates.
- */
-jasmine.Env.prototype.addReporter = function(reporter) {
- this.reporter.addReporter(reporter);
-};
-
-jasmine.Env.prototype.execute = function() {
- this.currentRunner_.execute();
-};
-
-jasmine.Env.prototype.describe = function(description, specDefinitions) {
- var suite = new jasmine.Suite(this, description, specDefinitions, this.currentSuite);
-
- var parentSuite = this.currentSuite;
- if (parentSuite) {
- parentSuite.add(suite);
- } else {
- this.currentRunner_.add(suite);
- }
-
- this.currentSuite = suite;
-
- var declarationError = null;
- try {
- specDefinitions.call(suite);
- } catch(e) {
- declarationError = e;
- }
-
- this.currentSuite = parentSuite;
-
- if (declarationError) {
- this.it("encountered a declaration exception", function() {
- throw declarationError;
- });
- }
-
- return suite;
-};
-
-jasmine.Env.prototype.beforeEach = function(beforeEachFunction) {
- if (this.currentSuite) {
- this.currentSuite.beforeEach(beforeEachFunction);
- } else {
- this.currentRunner_.beforeEach(beforeEachFunction);
- }
-};
-
-jasmine.Env.prototype.currentRunner = function () {
- return this.currentRunner_;
-};
-
-jasmine.Env.prototype.afterEach = function(afterEachFunction) {
- if (this.currentSuite) {
- this.currentSuite.afterEach(afterEachFunction);
- } else {
- this.currentRunner_.afterEach(afterEachFunction);
- }
-
-};
-
-jasmine.Env.prototype.xdescribe = function(desc, specDefinitions) {
- return {
- execute: function() {
- }
- };
-};
-
-jasmine.Env.prototype.it = function(description, func) {
- var spec = new jasmine.Spec(this, this.currentSuite, description);
- this.currentSuite.add(spec);
- this.currentSpec = spec;
-
- if (func) {
- spec.runs(func);
- }
-
- return spec;
-};
-
-jasmine.Env.prototype.xit = function(desc, func) {
- return {
- id: this.nextSpecId(),
- runs: function() {
- }
- };
-};
-
-jasmine.Env.prototype.compareObjects_ = function(a, b, mismatchKeys, mismatchValues) {
- if (a.__Jasmine_been_here_before__ === b && b.__Jasmine_been_here_before__ === a) {
- return true;
- }
-
- a.__Jasmine_been_here_before__ = b;
- b.__Jasmine_been_here_before__ = a;
-
- var hasKey = function(obj, keyName) {
- return obj != null && obj[keyName] !== jasmine.undefined;
- };
-
- for (var property in b) {
- if (!hasKey(a, property) && hasKey(b, property)) {
- mismatchKeys.push("expected has key '" + property + "', but missing from actual.");
- }
- }
- for (property in a) {
- if (!hasKey(b, property) && hasKey(a, property)) {
- mismatchKeys.push("expected missing key '" + property + "', but present in actual.");
- }
- }
- for (property in b) {
- if (property == '__Jasmine_been_here_before__') continue;
- if (!this.equals_(a[property], b[property], mismatchKeys, mismatchValues)) {
- mismatchValues.push("'" + property + "' was '" + (b[property] ? jasmine.util.htmlEscape(b[property].toString()) : b[property]) + "' in expected, but was '" + (a[property] ? jasmine.util.htmlEscape(a[property].toString()) : a[property]) + "' in actual.");
- }
- }
-
- if (jasmine.isArray_(a) && jasmine.isArray_(b) && a.length != b.length) {
- mismatchValues.push("arrays were not the same length");
- }
-
- delete a.__Jasmine_been_here_before__;
- delete b.__Jasmine_been_here_before__;
- return (mismatchKeys.length == 0 && mismatchValues.length == 0);
-};
-
-jasmine.Env.prototype.equals_ = function(a, b, mismatchKeys, mismatchValues) {
- mismatchKeys = mismatchKeys || [];
- mismatchValues = mismatchValues || [];
-
- for (var i = 0; i < this.equalityTesters_.length; i++) {
- var equalityTester = this.equalityTesters_[i];
- var result = equalityTester(a, b, this, mismatchKeys, mismatchValues);
- if (result !== jasmine.undefined) return result;
- }
-
- if (a === b) return true;
-
- if (a === jasmine.undefined || a === null || b === jasmine.undefined || b === null) {
- return (a == jasmine.undefined && b == jasmine.undefined);
- }
-
- if (jasmine.isDomNode(a) && jasmine.isDomNode(b)) {
- return a === b;
- }
-
- if (a instanceof Date && b instanceof Date) {
- return a.getTime() == b.getTime();
- }
-
- if (a instanceof jasmine.Matchers.Any) {
- return a.matches(b);
- }
-
- if (b instanceof jasmine.Matchers.Any) {
- return b.matches(a);
- }
-
- if (jasmine.isString_(a) && jasmine.isString_(b)) {
- return (a == b);
- }
-
- if (jasmine.isNumber_(a) && jasmine.isNumber_(b)) {
- return (a == b);
- }
-
- if (typeof a === "object" && typeof b === "object") {
- return this.compareObjects_(a, b, mismatchKeys, mismatchValues);
- }
-
- //Straight check
- return (a === b);
-};
-
-jasmine.Env.prototype.contains_ = function(haystack, needle) {
- if (jasmine.isArray_(haystack)) {
- for (var i = 0; i < haystack.length; i++) {
- if (this.equals_(haystack[i], needle)) return true;
- }
- return false;
- }
- return haystack.indexOf(needle) >= 0;
-};
-
-jasmine.Env.prototype.addEqualityTester = function(equalityTester) {
- this.equalityTesters_.push(equalityTester);
-};
-/** No-op base class for Jasmine reporters.
- *
- * @constructor
- */
-jasmine.Reporter = function() {
-};
-
-//noinspection JSUnusedLocalSymbols
-jasmine.Reporter.prototype.reportRunnerStarting = function(runner) {
-};
-
-//noinspection JSUnusedLocalSymbols
-jasmine.Reporter.prototype.reportRunnerResults = function(runner) {
-};
-
-//noinspection JSUnusedLocalSymbols
-jasmine.Reporter.prototype.reportSuiteResults = function(suite) {
-};
-
-//noinspection JSUnusedLocalSymbols
-jasmine.Reporter.prototype.reportSpecStarting = function(spec) {
-};
-
-//noinspection JSUnusedLocalSymbols
-jasmine.Reporter.prototype.reportSpecResults = function(spec) {
-};
-
-//noinspection JSUnusedLocalSymbols
-jasmine.Reporter.prototype.log = function(str) {
-};
-
-/**
- * Blocks are functions with executable code that make up a spec.
- *
- * @constructor
- * @param {jasmine.Env} env
- * @param {Function} func
- * @param {jasmine.Spec} spec
- */
-jasmine.Block = function(env, func, spec) {
- this.env = env;
- this.func = func;
- this.spec = spec;
-};
-
-jasmine.Block.prototype.execute = function(onComplete) {
- try {
- this.func.apply(this.spec);
- } catch (e) {
- this.spec.fail(e);
- }
- onComplete();
-};
-/** JavaScript API reporter.
- *
- * @constructor
- */
-jasmine.JsApiReporter = function() {
- this.started = false;
- this.finished = false;
- this.suites_ = [];
- this.results_ = {};
-};
-
-jasmine.JsApiReporter.prototype.reportRunnerStarting = function(runner) {
- this.started = true;
- var suites = runner.topLevelSuites();
- for (var i = 0; i < suites.length; i++) {
- var suite = suites[i];
- this.suites_.push(this.summarize_(suite));
- }
-};
-
-jasmine.JsApiReporter.prototype.suites = function() {
- return this.suites_;
-};
-
-jasmine.JsApiReporter.prototype.summarize_ = function(suiteOrSpec) {
- var isSuite = suiteOrSpec instanceof jasmine.Suite;
- var summary = {
- id: suiteOrSpec.id,
- name: suiteOrSpec.description,
- type: isSuite ? 'suite' : 'spec',
- children: []
- };
-
- if (isSuite) {
- var children = suiteOrSpec.children();
- for (var i = 0; i < children.length; i++) {
- summary.children.push(this.summarize_(children[i]));
- }
- }
- return summary;
-};
-
-jasmine.JsApiReporter.prototype.results = function() {
- return this.results_;
-};
-
-jasmine.JsApiReporter.prototype.resultsForSpec = function(specId) {
- return this.results_[specId];
-};
-
-//noinspection JSUnusedLocalSymbols
-jasmine.JsApiReporter.prototype.reportRunnerResults = function(runner) {
- this.finished = true;
-};
-
-//noinspection JSUnusedLocalSymbols
-jasmine.JsApiReporter.prototype.reportSuiteResults = function(suite) {
-};
-
-//noinspection JSUnusedLocalSymbols
-jasmine.JsApiReporter.prototype.reportSpecResults = function(spec) {
- this.results_[spec.id] = {
- messages: spec.results().getItems(),
- result: spec.results().failedCount > 0 ? "failed" : "passed"
- };
-};
-
-//noinspection JSUnusedLocalSymbols
-jasmine.JsApiReporter.prototype.log = function(str) {
-};
-
-jasmine.JsApiReporter.prototype.resultsForSpecs = function(specIds){
- var results = {};
- for (var i = 0; i < specIds.length; i++) {
- var specId = specIds[i];
- results[specId] = this.summarizeResult_(this.results_[specId]);
- }
- return results;
-};
-
-jasmine.JsApiReporter.prototype.summarizeResult_ = function(result){
- var summaryMessages = [];
- var messagesLength = result.messages.length;
- for (var messageIndex = 0; messageIndex < messagesLength; messageIndex++) {
- var resultMessage = result.messages[messageIndex];
- summaryMessages.push({
- text: resultMessage.type == 'log' ? resultMessage.toString() : jasmine.undefined,
- passed: resultMessage.passed ? resultMessage.passed() : true,
- type: resultMessage.type,
- message: resultMessage.message,
- trace: {
- stack: resultMessage.passed && !resultMessage.passed() ? resultMessage.trace.stack : jasmine.undefined
- }
- });
- }
-
- return {
- result : result.result,
- messages : summaryMessages
- };
-};
-
-/**
- * @constructor
- * @param {jasmine.Env} env
- * @param actual
- * @param {jasmine.Spec} spec
- */
-jasmine.Matchers = function(env, actual, spec, opt_isNot) {
- this.env = env;
- this.actual = actual;
- this.spec = spec;
- this.isNot = opt_isNot || false;
- this.reportWasCalled_ = false;
-};
-
-// todo: @deprecated as of Jasmine 0.11, remove soon [xw]
-jasmine.Matchers.pp = function(str) {
- throw new Error("jasmine.Matchers.pp() is no longer supported, please use jasmine.pp() instead!");
-};
-
-// todo: @deprecated Deprecated as of Jasmine 0.10. Rewrite your custom matchers to return true or false. [xw]
-jasmine.Matchers.prototype.report = function(result, failing_message, details) {
- throw new Error("As of jasmine 0.11, custom matchers must be implemented differently -- please see jasmine docs");
-};
-
-jasmine.Matchers.wrapInto_ = function(prototype, matchersClass) {
- for (var methodName in prototype) {
- if (methodName == 'report') continue;
- var orig = prototype[methodName];
- matchersClass.prototype[methodName] = jasmine.Matchers.matcherFn_(methodName, orig);
- }
-};
-
-jasmine.Matchers.matcherFn_ = function(matcherName, matcherFunction) {
- return function() {
- var matcherArgs = jasmine.util.argsToArray(arguments);
- var result = matcherFunction.apply(this, arguments);
-
- if (this.isNot) {
- result = !result;
- }
-
- if (this.reportWasCalled_) return result;
-
- var message;
- if (!result) {
- if (this.message) {
- message = this.message.apply(this, arguments);
- if (jasmine.isArray_(message)) {
- message = message[this.isNot ? 1 : 0];
- }
- } else {
- var englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); });
- message = "Expected " + jasmine.pp(this.actual) + (this.isNot ? " not " : " ") + englishyPredicate;
- if (matcherArgs.length > 0) {
- for (var i = 0; i < matcherArgs.length; i++) {
- if (i > 0) message += ",";
- message += " " + jasmine.pp(matcherArgs[i]);
- }
- }
- message += ".";
- }
- }
- var expectationResult = new jasmine.ExpectationResult({
- matcherName: matcherName,
- passed: result,
- expected: matcherArgs.length > 1 ? matcherArgs : matcherArgs[0],
- actual: this.actual,
- message: message
- });
- this.spec.addMatcherResult(expectationResult);
- return jasmine.undefined;
- };
-};
-
-
-
-
-/**
- * toBe: compares the actual to the expected using ===
- * @param expected
- */
-jasmine.Matchers.prototype.toBe = function(expected) {
- return this.actual === expected;
-};
-
-/**
- * toNotBe: compares the actual to the expected using !==
- * @param expected
- * @deprecated as of 1.0. Use not.toBe() instead.
- */
-jasmine.Matchers.prototype.toNotBe = function(expected) {
- return this.actual !== expected;
-};
-
-/**
- * toEqual: compares the actual to the expected using common sense equality. Handles Objects, Arrays, etc.
- *
- * @param expected
- */
-jasmine.Matchers.prototype.toEqual = function(expected) {
- return this.env.equals_(this.actual, expected);
-};
-
-/**
- * toNotEqual: compares the actual to the expected using the ! of jasmine.Matchers.toEqual
- * @param expected
- * @deprecated as of 1.0. Use not.toNotEqual() instead.
- */
-jasmine.Matchers.prototype.toNotEqual = function(expected) {
- return !this.env.equals_(this.actual, expected);
-};
-
-/**
- * Matcher that compares the actual to the expected using a regular expression. Constructs a RegExp, so takes
- * a pattern or a String.
- *
- * @param expected
- */
-jasmine.Matchers.prototype.toMatch = function(expected) {
- return new RegExp(expected).test(this.actual);
-};
-
-/**
- * Matcher that compares the actual to the expected using the boolean inverse of jasmine.Matchers.toMatch
- * @param expected
- * @deprecated as of 1.0. Use not.toMatch() instead.
- */
-jasmine.Matchers.prototype.toNotMatch = function(expected) {
- return !(new RegExp(expected).test(this.actual));
-};
-
-/**
- * Matcher that compares the actual to jasmine.undefined.
- */
-jasmine.Matchers.prototype.toBeDefined = function() {
- return (this.actual !== jasmine.undefined);
-};
-
-/**
- * Matcher that compares the actual to jasmine.undefined.
- */
-jasmine.Matchers.prototype.toBeUndefined = function() {
- return (this.actual === jasmine.undefined);
-};
-
-/**
- * Matcher that compares the actual to null.
- */
-jasmine.Matchers.prototype.toBeNull = function() {
- return (this.actual === null);
-};
-
-/**
- * Matcher that boolean not-nots the actual.
- */
-jasmine.Matchers.prototype.toBeTruthy = function() {
- return !!this.actual;
-};
-
-
-/**
- * Matcher that boolean nots the actual.
- */
-jasmine.Matchers.prototype.toBeFalsy = function() {
- return !this.actual;
-};
-
-
-/**
- * Matcher that checks to see if the actual, a Jasmine spy, was called.
- */
-jasmine.Matchers.prototype.toHaveBeenCalled = function() {
- if (arguments.length > 0) {
- throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith');
- }
-
- if (!jasmine.isSpy(this.actual)) {
- throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
- }
-
- this.message = function() {
- return [
- "Expected spy " + this.actual.identity + " to have been called.",
- "Expected spy " + this.actual.identity + " not to have been called."
- ];
- };
-
- return this.actual.wasCalled;
-};
-
-/** @deprecated Use expect(xxx).toHaveBeenCalled() instead */
-jasmine.Matchers.prototype.wasCalled = jasmine.Matchers.prototype.toHaveBeenCalled;
-
-/**
- * Matcher that checks to see if the actual, a Jasmine spy, was not called.
- *
- * @deprecated Use expect(xxx).not.toHaveBeenCalled() instead
- */
-jasmine.Matchers.prototype.wasNotCalled = function() {
- if (arguments.length > 0) {
- throw new Error('wasNotCalled does not take arguments');
- }
-
- if (!jasmine.isSpy(this.actual)) {
- throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
- }
-
- this.message = function() {
- return [
- "Expected spy " + this.actual.identity + " to not have been called.",
- "Expected spy " + this.actual.identity + " to have been called."
- ];
- };
-
- return !this.actual.wasCalled;
-};
-
-/**
- * Matcher that checks to see if the actual, a Jasmine spy, was called with a set of parameters.
- *
- * @example
- *
- */
-jasmine.Matchers.prototype.toHaveBeenCalledWith = function() {
- var expectedArgs = jasmine.util.argsToArray(arguments);
- if (!jasmine.isSpy(this.actual)) {
- throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
- }
- this.message = function() {
- if (this.actual.callCount == 0) {
- // todo: what should the failure message for .not.toHaveBeenCalledWith() be? is this right? test better. [xw]
- return [
- "Expected spy to have been called with " + jasmine.pp(expectedArgs) + " but it was never called.",
- "Expected spy not to have been called with " + jasmine.pp(expectedArgs) + " but it was."
- ];
- } else {
- return [
- "Expected spy to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall),
- "Expected spy not to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall)
- ];
- }
- };
-
- return this.env.contains_(this.actual.argsForCall, expectedArgs);
-};
-
-/** @deprecated Use expect(xxx).toHaveBeenCalledWith() instead */
-jasmine.Matchers.prototype.wasCalledWith = jasmine.Matchers.prototype.toHaveBeenCalledWith;
-
-/** @deprecated Use expect(xxx).not.toHaveBeenCalledWith() instead */
-jasmine.Matchers.prototype.wasNotCalledWith = function() {
- var expectedArgs = jasmine.util.argsToArray(arguments);
- if (!jasmine.isSpy(this.actual)) {
- throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
- }
-
- this.message = function() {
- return [
- "Expected spy not to have been called with " + jasmine.pp(expectedArgs) + " but it was",
- "Expected spy to have been called with " + jasmine.pp(expectedArgs) + " but it was"
- ]
- };
-
- return !this.env.contains_(this.actual.argsForCall, expectedArgs);
-};
-
-/**
- * Matcher that checks that the expected item is an element in the actual Array.
- *
- * @param {Object} expected
- */
-jasmine.Matchers.prototype.toContain = function(expected) {
- return this.env.contains_(this.actual, expected);
-};
-
-/**
- * Matcher that checks that the expected item is NOT an element in the actual Array.
- *
- * @param {Object} expected
- * @deprecated as of 1.0. Use not.toNotContain() instead.
- */
-jasmine.Matchers.prototype.toNotContain = function(expected) {
- return !this.env.contains_(this.actual, expected);
-};
-
-jasmine.Matchers.prototype.toBeLessThan = function(expected) {
- return this.actual < expected;
-};
-
-jasmine.Matchers.prototype.toBeGreaterThan = function(expected) {
- return this.actual > expected;
-};
-
-/**
- * Matcher that checks that the expected exception was thrown by the actual.
- *
- * @param {String} expected
- */
-jasmine.Matchers.prototype.toThrow = function(expected) {
- var result = false;
- var exception;
- if (typeof this.actual != 'function') {
- throw new Error('Actual is not a function');
- }
- try {
- this.actual();
- } catch (e) {
- exception = e;
- }
- if (exception) {
- result = (expected === jasmine.undefined || this.env.equals_(exception.message || exception, expected.message || expected));
- }
-
- var not = this.isNot ? "not " : "";
-
- this.message = function() {
- if (exception && (expected === jasmine.undefined || !this.env.equals_(exception.message || exception, expected.message || expected))) {
- return ["Expected function " + not + "to throw", expected ? expected.message || expected : " an exception", ", but it threw", exception.message || exception].join(' ');
- } else {
- return "Expected function to throw an exception.";
- }
- };
-
- return result;
-};
-
-jasmine.Matchers.Any = function(expectedClass) {
- this.expectedClass = expectedClass;
-};
-
-jasmine.Matchers.Any.prototype.matches = function(other) {
- if (this.expectedClass == String) {
- return typeof other == 'string' || other instanceof String;
- }
-
- if (this.expectedClass == Number) {
- return typeof other == 'number' || other instanceof Number;
- }
-
- if (this.expectedClass == Function) {
- return typeof other == 'function' || other instanceof Function;
- }
-
- if (this.expectedClass == Object) {
- return typeof other == 'object';
- }
-
- return other instanceof this.expectedClass;
-};
-
-jasmine.Matchers.Any.prototype.toString = function() {
- return '<jasmine.any(' + this.expectedClass + ')>';
-};
-
-/**
- * @constructor
- */
-jasmine.MultiReporter = function() {
- this.subReporters_ = [];
-};
-jasmine.util.inherit(jasmine.MultiReporter, jasmine.Reporter);
-
-jasmine.MultiReporter.prototype.addReporter = function(reporter) {
- this.subReporters_.push(reporter);
-};
-
-(function() {
- var functionNames = [
- "reportRunnerStarting",
- "reportRunnerResults",
- "reportSuiteResults",
- "reportSpecStarting",
- "reportSpecResults",
- "log"
- ];
- for (var i = 0; i < functionNames.length; i++) {
- var functionName = functionNames[i];
- jasmine.MultiReporter.prototype[functionName] = (function(functionName) {
- return function() {
- for (var j = 0; j < this.subReporters_.length; j++) {
- var subReporter = this.subReporters_[j];
- if (subReporter[functionName]) {
- subReporter[functionName].apply(subReporter, arguments);
- }
- }
- };
- })(functionName);
- }
-})();
-/**
- * Holds results for a set of Jasmine spec. Allows for the results array to hold another jasmine.NestedResults
- *
- * @constructor
- */
-jasmine.NestedResults = function() {
- /**
- * The total count of results
- */
- this.totalCount = 0;
- /**
- * Number of passed results
- */
- this.passedCount = 0;
- /**
- * Number of failed results
- */
- this.failedCount = 0;
- /**
- * Was this suite/spec skipped?
- */
- this.skipped = false;
- /**
- * @ignore
- */
- this.items_ = [];
-};
-
-/**
- * Roll up the result counts.
- *
- * @param result
- */
-jasmine.NestedResults.prototype.rollupCounts = function(result) {
- this.totalCount += result.totalCount;
- this.passedCount += result.passedCount;
- this.failedCount += result.failedCount;
-};
-
-/**
- * Adds a log message.
- * @param values Array of message parts which will be concatenated later.
- */
-jasmine.NestedResults.prototype.log = function(values) {
- this.items_.push(new jasmine.MessageResult(values));
-};
-
-/**
- * Getter for the results: message & results.
- */
-jasmine.NestedResults.prototype.getItems = function() {
- return this.items_;
-};
-
-/**
- * Adds a result, tracking counts (total, passed, & failed)
- * @param {jasmine.ExpectationResult|jasmine.NestedResults} result
- */
-jasmine.NestedResults.prototype.addResult = function(result) {
- if (result.type != 'log') {
- if (result.items_) {
- this.rollupCounts(result);
- } else {
- this.totalCount++;
- if (result.passed()) {
- this.passedCount++;
- } else {
- this.failedCount++;
- }
- }
- }
- this.items_.push(result);
-};
-
-/**
- * @returns {Boolean} True if <b>everything</b> below passed
- */
-jasmine.NestedResults.prototype.passed = function() {
- return this.passedCount === this.totalCount;
-};
-/**
- * Base class for pretty printing for expectation results.
- */
-jasmine.PrettyPrinter = function() {
- this.ppNestLevel_ = 0;
-};
-
-/**
- * Formats a value in a nice, human-readable string.
- *
- * @param value
- */
-jasmine.PrettyPrinter.prototype.format = function(value) {
- if (this.ppNestLevel_ > 40) {
- throw new Error('jasmine.PrettyPrinter: format() nested too deeply!');
- }
-
- this.ppNestLevel_++;
- try {
- if (value === jasmine.undefined) {
- this.emitScalar('undefined');
- } else if (value === null) {
- this.emitScalar('null');
- } else if (value === jasmine.getGlobal()) {
- this.emitScalar('<global>');
- } else if (value instanceof jasmine.Matchers.Any) {
- this.emitScalar(value.toString());
- } else if (typeof value === 'string') {
- this.emitString(value);
- } else if (jasmine.isSpy(value)) {
- this.emitScalar("spy on " + value.identity);
- } else if (value instanceof RegExp) {
- this.emitScalar(value.toString());
- } else if (typeof value === 'function') {
- this.emitScalar('Function');
- } else if (typeof value.nodeType === 'number') {
- this.emitScalar('HTMLNode');
- } else if (value instanceof Date) {
- this.emitScalar('Date(' + value + ')');
- } else if (value.__Jasmine_been_here_before__) {
- this.emitScalar('<circular reference: ' + (jasmine.isArray_(value) ? 'Array' : 'Object') + '>');
- } else if (jasmine.isArray_(value) || typeof value == 'object') {
- value.__Jasmine_been_here_before__ = true;
- if (jasmine.isArray_(value)) {
- this.emitArray(value);
- } else {
- this.emitObject(value);
- }
- delete value.__Jasmine_been_here_before__;
- } else {
- this.emitScalar(value.toString());
- }
- } finally {
- this.ppNestLevel_--;
- }
-};
-
-jasmine.PrettyPrinter.prototype.iterateObject = function(obj, fn) {
- for (var property in obj) {
- if (property == '__Jasmine_been_here_before__') continue;
- fn(property, obj.__lookupGetter__ ? (obj.__lookupGetter__(property) != null) : false);
- }
-};
-
-jasmine.PrettyPrinter.prototype.emitArray = jasmine.unimplementedMethod_;
-jasmine.PrettyPrinter.prototype.emitObject = jasmine.unimplementedMethod_;
-jasmine.PrettyPrinter.prototype.emitScalar = jasmine.unimplementedMethod_;
-jasmine.PrettyPrinter.prototype.emitString = jasmine.unimplementedMethod_;
-
-jasmine.StringPrettyPrinter = function() {
- jasmine.PrettyPrinter.call(this);
-
- this.string = '';
-};
-jasmine.util.inherit(jasmine.StringPrettyPrinter, jasmine.PrettyPrinter);
-
-jasmine.StringPrettyPrinter.prototype.emitScalar = function(value) {
- this.append(value);
-};
-
-jasmine.StringPrettyPrinter.prototype.emitString = function(value) {
- this.append("'" + value + "'");
-};
-
-jasmine.StringPrettyPrinter.prototype.emitArray = function(array) {
- this.append('[ ');
- for (var i = 0; i < array.length; i++) {
- if (i > 0) {
- this.append(', ');
- }
- this.format(array[i]);
- }
- this.append(' ]');
-};
-
-jasmine.StringPrettyPrinter.prototype.emitObject = function(obj) {
- var self = this;
- this.append('{ ');
- var first = true;
-
- this.iterateObject(obj, function(property, isGetter) {
- if (first) {
- first = false;
- } else {
- self.append(', ');
- }
-
- self.append(property);
- self.append(' : ');
- if (isGetter) {
- self.append('<getter>');
- } else {
- self.format(obj[property]);
- }
- });
-
- this.append(' }');
-};
-
-jasmine.StringPrettyPrinter.prototype.append = function(value) {
- this.string += value;
-};
-jasmine.Queue = function(env) {
- this.env = env;
- this.blocks = [];
- this.running = false;
- this.index = 0;
- this.offset = 0;
- this.abort = false;
-};
-
-jasmine.Queue.prototype.addBefore = function(block) {
- this.blocks.unshift(block);
-};
-
-jasmine.Queue.prototype.add = function(block) {
- this.blocks.push(block);
-};
-
-jasmine.Queue.prototype.insertNext = function(block) {
- this.blocks.splice((this.index + this.offset + 1), 0, block);
- this.offset++;
-};
-
-jasmine.Queue.prototype.start = function(onComplete) {
- this.running = true;
- this.onComplete = onComplete;
- this.next_();
-};
-
-jasmine.Queue.prototype.isRunning = function() {
- return this.running;
-};
-
-jasmine.Queue.LOOP_DONT_RECURSE = true;
-
-jasmine.Queue.prototype.next_ = function() {
- var self = this;
- var goAgain = true;
-
- while (goAgain) {
- goAgain = false;
-
- if (self.index < self.blocks.length && !this.abort) {
- var calledSynchronously = true;
- var completedSynchronously = false;
-
- var onComplete = function () {
- if (jasmine.Queue.LOOP_DONT_RECURSE && calledSynchronously) {
- completedSynchronously = true;
- return;
- }
-
- if (self.blocks[self.index].abort) {
- self.abort = true;
- }
-
- self.offset = 0;
- self.index++;
-
- var now = new Date().getTime();
- if (self.env.updateInterval && now - self.env.lastUpdate > self.env.updateInterval) {
- self.env.lastUpdate = now;
- self.env.setTimeout(function() {
- self.next_();
- }, 0);
- } else {
- if (jasmine.Queue.LOOP_DONT_RECURSE && completedSynchronously) {
- goAgain = true;
- } else {
- self.next_();
- }
- }
- };
- self.blocks[self.index].execute(onComplete);
-
- calledSynchronously = false;
- if (completedSynchronously) {
- onComplete();
- }
-
- } else {
- self.running = false;
- if (self.onComplete) {
- self.onComplete();
- }
- }
- }
-};
-
-jasmine.Queue.prototype.results = function() {
- var results = new jasmine.NestedResults();
- for (var i = 0; i < this.blocks.length; i++) {
- if (this.blocks[i].results) {
- results.addResult(this.blocks[i].results());
- }
- }
- return results;
-};
-
-
-/**
- * Runner
- *
- * @constructor
- * @param {jasmine.Env} env
- */
-jasmine.Runner = function(env) {
- var self = this;
- self.env = env;
- self.queue = new jasmine.Queue(env);
- self.before_ = [];
- self.after_ = [];
- self.suites_ = [];
-};
-
-jasmine.Runner.prototype.execute = function() {
- var self = this;
- if (self.env.reporter.reportRunnerStarting) {
- self.env.reporter.reportRunnerStarting(this);
- }
- self.queue.start(function () {
- self.finishCallback();
- });
-};
-
-jasmine.Runner.prototype.beforeEach = function(beforeEachFunction) {
- beforeEachFunction.typeName = 'beforeEach';
- this.before_.splice(0,0,beforeEachFunction);
-};
-
-jasmine.Runner.prototype.afterEach = function(afterEachFunction) {
- afterEachFunction.typeName = 'afterEach';
- this.after_.splice(0,0,afterEachFunction);
-};
-
-
-jasmine.Runner.prototype.finishCallback = function() {
- this.env.reporter.reportRunnerResults(this);
-};
-
-jasmine.Runner.prototype.addSuite = function(suite) {
- this.suites_.push(suite);
-};
-
-jasmine.Runner.prototype.add = function(block) {
- if (block instanceof jasmine.Suite) {
- this.addSuite(block);
- }
- this.queue.add(block);
-};
-
-jasmine.Runner.prototype.specs = function () {
- var suites = this.suites();
- var specs = [];
- for (var i = 0; i < suites.length; i++) {
- specs = specs.concat(suites[i].specs());
- }
- return specs;
-};
-
-jasmine.Runner.prototype.suites = function() {
- return this.suites_;
-};
-
-jasmine.Runner.prototype.topLevelSuites = function() {
- var topLevelSuites = [];
- for (var i = 0; i < this.suites_.length; i++) {
- if (!this.suites_[i].parentSuite) {
- topLevelSuites.push(this.suites_[i]);
- }
- }
- return topLevelSuites;
-};
-
-jasmine.Runner.prototype.results = function() {
- return this.queue.results();
-};
-/**
- * Internal representation of a Jasmine specification, or test.
- *
- * @constructor
- * @param {jasmine.Env} env
- * @param {jasmine.Suite} suite
- * @param {String} description
- */
-jasmine.Spec = function(env, suite, description) {
- if (!env) {
- throw new Error('jasmine.Env() required');
- }
- if (!suite) {
- throw new Error('jasmine.Suite() required');
- }
- var spec = this;
- spec.id = env.nextSpecId ? env.nextSpecId() : null;
- spec.env = env;
- spec.suite = suite;
- spec.description = description;
- spec.queue = new jasmine.Queue(env);
-
- spec.afterCallbacks = [];
- spec.spies_ = [];
-
- spec.results_ = new jasmine.NestedResults();
- spec.results_.description = description;
- spec.matchersClass = null;
-};
-
-jasmine.Spec.prototype.getFullName = function() {
- return this.suite.getFullName() + ' ' + this.description + '.';
-};
-
-
-jasmine.Spec.prototype.results = function() {
- return this.results_;
-};
-
-/**
- * All parameters are pretty-printed and concatenated together, then written to the spec's output.
- *
- * Be careful not to leave calls to <code>jasmine.log</code> in production code.
- */
-jasmine.Spec.prototype.log = function() {
- return this.results_.log(arguments);
-};
-
-jasmine.Spec.prototype.runs = function (func) {
- var block = new jasmine.Block(this.env, func, this);
- this.addToQueue(block);
- return this;
-};
-
-jasmine.Spec.prototype.addToQueue = function (block) {
- if (this.queue.isRunning()) {
- this.queue.insertNext(block);
- } else {
- this.queue.add(block);
- }
-};
-
-/**
- * @param {jasmine.ExpectationResult} result
- */
-jasmine.Spec.prototype.addMatcherResult = function(result) {
- this.results_.addResult(result);
-};
-
-jasmine.Spec.prototype.expect = function(actual) {
- var positive = new (this.getMatchersClass_())(this.env, actual, this);
- positive.not = new (this.getMatchersClass_())(this.env, actual, this, true);
- return positive;
-};
-
-/**
- * Waits a fixed time period before moving to the next block.
- *
- * @deprecated Use waitsFor() instead
- * @param {Number} timeout milliseconds to wait
- */
-jasmine.Spec.prototype.waits = function(timeout) {
- var waitsFunc = new jasmine.WaitsBlock(this.env, timeout, this);
- this.addToQueue(waitsFunc);
- return this;
-};
-
-/**
- * Waits for the latchFunction to return true before proceeding to the next block.
- *
- * @param {Function} latchFunction
- * @param {String} optional_timeoutMessage
- * @param {Number} optional_timeout
- */
-jasmine.Spec.prototype.waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) {
- var latchFunction_ = null;
- var optional_timeoutMessage_ = null;
- var optional_timeout_ = null;
-
- for (var i = 0; i < arguments.length; i++) {
- var arg = arguments[i];
- switch (typeof arg) {
- case 'function':
- latchFunction_ = arg;
- break;
- case 'string':
- optional_timeoutMessage_ = arg;
- break;
- case 'number':
- optional_timeout_ = arg;
- break;
- }
- }
-
- var waitsForFunc = new jasmine.WaitsForBlock(this.env, optional_timeout_, latchFunction_, optional_timeoutMessage_, this);
- this.addToQueue(waitsForFunc);
- return this;
-};
-
-jasmine.Spec.prototype.fail = function (e) {
- var expectationResult = new jasmine.ExpectationResult({
- passed: false,
- message: e ? jasmine.util.formatException(e) : 'Exception'
- });
- this.results_.addResult(expectationResult);
-};
-
-jasmine.Spec.prototype.getMatchersClass_ = function() {
- return this.matchersClass || this.env.matchersClass;
-};
-
-jasmine.Spec.prototype.addMatchers = function(matchersPrototype) {
- var parent = this.getMatchersClass_();
- var newMatchersClass = function() {
- parent.apply(this, arguments);
- };
- jasmine.util.inherit(newMatchersClass, parent);
- jasmine.Matchers.wrapInto_(matchersPrototype, newMatchersClass);
- this.matchersClass = newMatchersClass;
-};
-
-jasmine.Spec.prototype.finishCallback = function() {
- this.env.reporter.reportSpecResults(this);
-};
-
-jasmine.Spec.prototype.finish = function(onComplete) {
- this.removeAllSpies();
- this.finishCallback();
- if (onComplete) {
- onComplete();
- }
-};
-
-jasmine.Spec.prototype.after = function(doAfter) {
- if (this.queue.isRunning()) {
- this.queue.add(new jasmine.Block(this.env, doAfter, this));
- } else {
- this.afterCallbacks.unshift(doAfter);
- }
-};
-
-jasmine.Spec.prototype.execute = function(onComplete) {
- var spec = this;
- if (!spec.env.specFilter(spec)) {
- spec.results_.skipped = true;
- spec.finish(onComplete);
- return;
- }
-
- this.env.reporter.reportSpecStarting(this);
-
- spec.env.currentSpec = spec;
-
- spec.addBeforesAndAftersToQueue();
-
- spec.queue.start(function () {
- spec.finish(onComplete);
- });
-};
-
-jasmine.Spec.prototype.addBeforesAndAftersToQueue = function() {
- var runner = this.env.currentRunner();
- var i;
-
- for (var suite = this.suite; suite; suite = suite.parentSuite) {
- for (i = 0; i < suite.before_.length; i++) {
- this.queue.addBefore(new jasmine.Block(this.env, suite.before_[i], this));
- }
- }
- for (i = 0; i < runner.before_.length; i++) {
- this.queue.addBefore(new jasmine.Block(this.env, runner.before_[i], this));
- }
- for (i = 0; i < this.afterCallbacks.length; i++) {
- this.queue.add(new jasmine.Block(this.env, this.afterCallbacks[i], this));
- }
- for (suite = this.suite; suite; suite = suite.parentSuite) {
- for (i = 0; i < suite.after_.length; i++) {
- this.queue.add(new jasmine.Block(this.env, suite.after_[i], this));
- }
- }
- for (i = 0; i < runner.after_.length; i++) {
- this.queue.add(new jasmine.Block(this.env, runner.after_[i], this));
- }
-};
-
-jasmine.Spec.prototype.explodes = function() {
- throw 'explodes function should not have been called';
-};
-
-jasmine.Spec.prototype.spyOn = function(obj, methodName, ignoreMethodDoesntExist) {
- if (obj == jasmine.undefined) {
- throw "spyOn could not find an object to spy upon for " + methodName + "()";
- }
-
- if (!ignoreMethodDoesntExist && obj[methodName] === jasmine.undefined) {
- throw methodName + '() method does not exist';
- }
-
- if (!ignoreMethodDoesntExist && obj[methodName] && obj[methodName].isSpy) {
- throw new Error(methodName + ' has already been spied upon');
- }
-
- var spyObj = jasmine.createSpy(methodName);
-
- this.spies_.push(spyObj);
- spyObj.baseObj = obj;
- spyObj.methodName = methodName;
- spyObj.originalValue = obj[methodName];
-
- obj[methodName] = spyObj;
-
- return spyObj;
-};
-
-jasmine.Spec.prototype.removeAllSpies = function() {
- for (var i = 0; i < this.spies_.length; i++) {
- var spy = this.spies_[i];
- spy.baseObj[spy.methodName] = spy.originalValue;
- }
- this.spies_ = [];
-};
-
-/**
- * Internal representation of a Jasmine suite.
- *
- * @constructor
- * @param {jasmine.Env} env
- * @param {String} description
- * @param {Function} specDefinitions
- * @param {jasmine.Suite} parentSuite
- */
-jasmine.Suite = function(env, description, specDefinitions, parentSuite) {
- var self = this;
- self.id = env.nextSuiteId ? env.nextSuiteId() : null;
- self.description = description;
- self.queue = new jasmine.Queue(env);
- self.parentSuite = parentSuite;
- self.env = env;
- self.before_ = [];
- self.after_ = [];
- self.children_ = [];
- self.suites_ = [];
- self.specs_ = [];
-};
-
-jasmine.Suite.prototype.getFullName = function() {
- var fullName = this.description;
- for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) {
- fullName = parentSuite.description + ' ' + fullName;
- }
- return fullName;
-};
-
-jasmine.Suite.prototype.finish = function(onComplete) {
- this.env.reporter.reportSuiteResults(this);
- this.finished = true;
- if (typeof(onComplete) == 'function') {
- onComplete();
- }
-};
-
-jasmine.Suite.prototype.beforeEach = function(beforeEachFunction) {
- beforeEachFunction.typeName = 'beforeEach';
- this.before_.unshift(beforeEachFunction);
-};
-
-jasmine.Suite.prototype.afterEach = function(afterEachFunction) {
- afterEachFunction.typeName = 'afterEach';
- this.after_.unshift(afterEachFunction);
-};
-
-jasmine.Suite.prototype.results = function() {
- return this.queue.results();
-};
-
-jasmine.Suite.prototype.add = function(suiteOrSpec) {
- this.children_.push(suiteOrSpec);
- if (suiteOrSpec instanceof jasmine.Suite) {
- this.suites_.push(suiteOrSpec);
- this.env.currentRunner().addSuite(suiteOrSpec);
- } else {
- this.specs_.push(suiteOrSpec);
- }
- this.queue.add(suiteOrSpec);
-};
-
-jasmine.Suite.prototype.specs = function() {
- return this.specs_;
-};
-
-jasmine.Suite.prototype.suites = function() {
- return this.suites_;
-};
-
-jasmine.Suite.prototype.children = function() {
- return this.children_;
-};
-
-jasmine.Suite.prototype.execute = function(onComplete) {
- var self = this;
- this.queue.start(function () {
- self.finish(onComplete);
- });
-};
-jasmine.WaitsBlock = function(env, timeout, spec) {
- this.timeout = timeout;
- jasmine.Block.call(this, env, null, spec);
-};
-
-jasmine.util.inherit(jasmine.WaitsBlock, jasmine.Block);
-
-jasmine.WaitsBlock.prototype.execute = function (onComplete) {
- this.env.reporter.log('>> Jasmine waiting for ' + this.timeout + ' ms...');
- this.env.setTimeout(function () {
- onComplete();
- }, this.timeout);
-};
-/**
- * A block which waits for some condition to become true, with timeout.
- *
- * @constructor
- * @extends jasmine.Block
- * @param {jasmine.Env} env The Jasmine environment.
- * @param {Number} timeout The maximum time in milliseconds to wait for the condition to become true.
- * @param {Function} latchFunction A function which returns true when the desired condition has been met.
- * @param {String} message The message to display if the desired condition hasn't been met within the given time period.
- * @param {jasmine.Spec} spec The Jasmine spec.
- */
-jasmine.WaitsForBlock = function(env, timeout, latchFunction, message, spec) {
- this.timeout = timeout || env.defaultTimeoutInterval;
- this.latchFunction = latchFunction;
- this.message = message;
- this.totalTimeSpentWaitingForLatch = 0;
- jasmine.Block.call(this, env, null, spec);
-};
-jasmine.util.inherit(jasmine.WaitsForBlock, jasmine.Block);
-
-jasmine.WaitsForBlock.TIMEOUT_INCREMENT = 10;
-
-jasmine.WaitsForBlock.prototype.execute = function(onComplete) {
- this.env.reporter.log('>> Jasmine waiting for ' + (this.message || 'something to happen'));
- var latchFunctionResult;
- try {
- latchFunctionResult = this.latchFunction.apply(this.spec);
- } catch (e) {
- this.spec.fail(e);
- onComplete();
- return;
- }
-
- if (latchFunctionResult) {
- onComplete();
- } else if (this.totalTimeSpentWaitingForLatch >= this.timeout) {
- var message = 'timed out after ' + this.timeout + ' msec waiting for ' + (this.message || 'something to happen');
- this.spec.fail({
- name: 'timeout',
- message: message
- });
-
- this.abort = true;
- onComplete();
- } else {
- this.totalTimeSpentWaitingForLatch += jasmine.WaitsForBlock.TIMEOUT_INCREMENT;
- var self = this;
- this.env.setTimeout(function() {
- self.execute(onComplete);
- }, jasmine.WaitsForBlock.TIMEOUT_INCREMENT);
- }
-};
-// Mock setTimeout, clearTimeout
-// Contributed by Pivotal Computer Systems, www.pivotalsf.com
-
-jasmine.FakeTimer = function() {
- this.reset();
-
- var self = this;
- self.setTimeout = function(funcToCall, millis) {
- self.timeoutsMade++;
- self.scheduleFunction(self.timeoutsMade, funcToCall, millis, false);
- return self.timeoutsMade;
- };
-
- self.setInterval = function(funcToCall, millis) {
- self.timeoutsMade++;
- self.scheduleFunction(self.timeoutsMade, funcToCall, millis, true);
- return self.timeoutsMade;
- };
-
- self.clearTimeout = function(timeoutKey) {
- self.scheduledFunctions[timeoutKey] = jasmine.undefined;
- };
-
- self.clearInterval = function(timeoutKey) {
- self.scheduledFunctions[timeoutKey] = jasmine.undefined;
- };
-
-};
-
-jasmine.FakeTimer.prototype.reset = function() {
- this.timeoutsMade = 0;
- this.scheduledFunctions = {};
- this.nowMillis = 0;
-};
-
-jasmine.FakeTimer.prototype.tick = function(millis) {
- var oldMillis = this.nowMillis;
- var newMillis = oldMillis + millis;
- this.runFunctionsWithinRange(oldMillis, newMillis);
- this.nowMillis = newMillis;
-};
-
-jasmine.FakeTimer.prototype.runFunctionsWithinRange = function(oldMillis, nowMillis) {
- var scheduledFunc;
- var funcsToRun = [];
- for (var timeoutKey in this.scheduledFunctions) {
- scheduledFunc = this.scheduledFunctions[timeoutKey];
- if (scheduledFunc != jasmine.undefined &&
- scheduledFunc.runAtMillis >= oldMillis &&
- scheduledFunc.runAtMillis <= nowMillis) {
- funcsToRun.push(scheduledFunc);
- this.scheduledFunctions[timeoutKey] = jasmine.undefined;
- }
- }
-
- if (funcsToRun.length > 0) {
- funcsToRun.sort(function(a, b) {
- return a.runAtMillis - b.runAtMillis;
- });
- for (var i = 0; i < funcsToRun.length; ++i) {
- try {
- var funcToRun = funcsToRun[i];
- this.nowMillis = funcToRun.runAtMillis;
- funcToRun.funcToCall();
- if (funcToRun.recurring) {
- this.scheduleFunction(funcToRun.timeoutKey,
- funcToRun.funcToCall,
- funcToRun.millis,
- true);
- }
- } catch(e) {
- }
- }
- this.runFunctionsWithinRange(oldMillis, nowMillis);
- }
-};
-
-jasmine.FakeTimer.prototype.scheduleFunction = function(timeoutKey, funcToCall, millis, recurring) {
- this.scheduledFunctions[timeoutKey] = {
- runAtMillis: this.nowMillis + millis,
- funcToCall: funcToCall,
- recurring: recurring,
- timeoutKey: timeoutKey,
- millis: millis
- };
-};
-
-/**
- * @namespace
- */
-jasmine.Clock = {
- defaultFakeTimer: new jasmine.FakeTimer(),
-
- reset: function() {
- jasmine.Clock.assertInstalled();
- jasmine.Clock.defaultFakeTimer.reset();
- },
-
- tick: function(millis) {
- jasmine.Clock.assertInstalled();
- jasmine.Clock.defaultFakeTimer.tick(millis);
- },
-
- runFunctionsWithinRange: function(oldMillis, nowMillis) {
- jasmine.Clock.defaultFakeTimer.runFunctionsWithinRange(oldMillis, nowMillis);
- },
-
- scheduleFunction: function(timeoutKey, funcToCall, millis, recurring) {
- jasmine.Clock.defaultFakeTimer.scheduleFunction(timeoutKey, funcToCall, millis, recurring);
- },
-
- useMock: function() {
- if (!jasmine.Clock.isInstalled()) {
- var spec = jasmine.getEnv().currentSpec;
- spec.after(jasmine.Clock.uninstallMock);
-
- jasmine.Clock.installMock();
- }
- },
-
- installMock: function() {
- jasmine.Clock.installed = jasmine.Clock.defaultFakeTimer;
- },
-
- uninstallMock: function() {
- jasmine.Clock.assertInstalled();
- jasmine.Clock.installed = jasmine.Clock.real;
- },
-
- real: {
- setTimeout: jasmine.getGlobal().setTimeout,
- clearTimeout: jasmine.getGlobal().clearTimeout,
- setInterval: jasmine.getGlobal().setInterval,
- clearInterval: jasmine.getGlobal().clearInterval
- },
-
- assertInstalled: function() {
- if (!jasmine.Clock.isInstalled()) {
- throw new Error("Mock clock is not installed, use jasmine.Clock.useMock()");
- }
- },
-
- isInstalled: function() {
- return jasmine.Clock.installed == jasmine.Clock.defaultFakeTimer;
- },
-
- installed: null
-};
-jasmine.Clock.installed = jasmine.Clock.real;
-
-//else for IE support
-jasmine.getGlobal().setTimeout = function(funcToCall, millis) {
- if (jasmine.Clock.installed.setTimeout.apply) {
- return jasmine.Clock.installed.setTimeout.apply(this, arguments);
- } else {
- return jasmine.Clock.installed.setTimeout(funcToCall, millis);
- }
-};
-
-jasmine.getGlobal().setInterval = function(funcToCall, millis) {
- if (jasmine.Clock.installed.setInterval.apply) {
- return jasmine.Clock.installed.setInterval.apply(this, arguments);
- } else {
- return jasmine.Clock.installed.setInterval(funcToCall, millis);
- }
-};
-
-jasmine.getGlobal().clearTimeout = function(timeoutKey) {
- if (jasmine.Clock.installed.clearTimeout.apply) {
- return jasmine.Clock.installed.clearTimeout.apply(this, arguments);
- } else {
- return jasmine.Clock.installed.clearTimeout(timeoutKey);
- }
-};
-
-jasmine.getGlobal().clearInterval = function(timeoutKey) {
- if (jasmine.Clock.installed.clearTimeout.apply) {
- return jasmine.Clock.installed.clearInterval.apply(this, arguments);
- } else {
- return jasmine.Clock.installed.clearInterval(timeoutKey);
- }
-};
-
-
-jasmine.version_= {
- "major": 1,
- "minor": 0,
- "build": 1,
- "revision": 1286311016
-};
+++ /dev/null
-// This file stores the results from the PHP parser for certain messages and arguments,
-// so we can test the equivalent Javascript libraries.
-// Last generated with makeLanguageSpec.php at 2011-01-28T02:04:09+00:00
-
-mediaWiki.messages.set( {
- "en_undelete_short": "Undelete {{PLURAL:$1|one edit|$1 edits}}",
- "en_category-subcat-count": "{{PLURAL:$2|This category has only the following subcategory.|This category has the following {{PLURAL:$1|subcategory|$1 subcategories}}, out of $2 total.}}",
- "fr_undelete_short": "Restaurer $1 modification{{PLURAL:$1||s}}",
- "fr_category-subcat-count": "Cette cat\u00e9gorie comprend {{PLURAL:$2|la sous-cat\u00e9gorie|$2 sous-cat\u00e9gories, dont {{PLURAL:$1|celle|les $1}}}} ci-dessous.",
- "ar_undelete_short": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 {{PLURAL:$1|\u062a\u0639\u062f\u064a\u0644 \u0648\u0627\u062d\u062f|\u062a\u0639\u062f\u064a\u0644\u064a\u0646|$1 \u062a\u0639\u062f\u064a\u0644\u0627\u062a|$1 \u062a\u0639\u062f\u064a\u0644|$1 \u062a\u0639\u062f\u064a\u0644\u0627}}",
- "ar_category-subcat-count": "{{PLURAL:$2|\u0644\u0627 \u062a\u0635\u0627\u0646\u064a\u0641 \u0641\u0631\u0639\u064a\u0629 \u0641\u064a \u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641|\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0641\u064a\u0647 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0627\u0644\u0641\u0631\u0639\u064a \u0627\u0644\u062a\u0627\u0644\u064a \u0641\u0642\u0637.|\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0641\u064a\u0647 {{PLURAL:$1||\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0627\u0644\u0641\u0631\u0639\u064a|\u0647\u0630\u064a\u0646 \u0627\u0644\u062a\u0635\u0646\u064a\u0641\u064a\u0646 \u0627\u0644\u0641\u0631\u0639\u064a\u064a\u0646|\u0647\u0630\u0647 \u0627\u0644$1 \u062a\u0635\u0627\u0646\u064a\u0641 \u0627\u0644\u0641\u0631\u0639\u064a\u0629|\u0647\u0630\u0647 \u0627\u0644$1 \u062a\u0635\u0646\u064a\u0641\u0627 \u0641\u0631\u0639\u064a\u0627|\u0647\u0630\u0647 \u0627\u0644$1 \u062a\u0635\u0646\u064a\u0641 \u0641\u0631\u0639\u064a}}\u060c \u0645\u0646 \u0625\u062c\u0645\u0627\u0644\u064a $2.}}",
- "jp_undelete_short": "Undelete {{PLURAL:$1|one edit|$1 edits}}",
- "jp_category-subcat-count": "{{PLURAL:$2|This category has only the following subcategory.|This category has the following {{PLURAL:$1|subcategory|$1 subcategories}}, out of $2 total.}}",
- "zh_undelete_short": "\u6062\u590d\u88ab\u5220\u9664\u7684$1\u9879\u4fee\u8ba2",
- "zh_category-subcat-count": "{{PLURAL:$2|\u672c\u5206\u7c7b\u53ea\u6709\u4e0b\u5217\u4e00\u4e2a\u5b50\u5206\u7c7b\u3002|\u672c\u5206\u7c7b\u5305\u542b\u4e0b\u5217$1\u4e2a\u5b50\u5206\u7c7b\uff0c\u5171\u6709$2\u4e2a\u5b50\u5206\u7c7b\u3002}}"
-} );
-var jasmineMsgSpec = [
- {
- "name": "en undelete_short 0",
- "key": "en_undelete_short",
- "args": [
- 0
- ],
- "result": "Undelete 0 edits",
- "lang": "en"
- },
- {
- "name": "en undelete_short 1",
- "key": "en_undelete_short",
- "args": [
- 1
- ],
- "result": "Undelete one edit",
- "lang": "en"
- },
- {
- "name": "en undelete_short 2",
- "key": "en_undelete_short",
- "args": [
- 2
- ],
- "result": "Undelete 2 edits",
- "lang": "en"
- },
- {
- "name": "en undelete_short 5",
- "key": "en_undelete_short",
- "args": [
- 5
- ],
- "result": "Undelete 5 edits",
- "lang": "en"
- },
- {
- "name": "en undelete_short 21",
- "key": "en_undelete_short",
- "args": [
- 21
- ],
- "result": "Undelete 21 edits",
- "lang": "en"
- },
- {
- "name": "en undelete_short 101",
- "key": "en_undelete_short",
- "args": [
- 101
- ],
- "result": "Undelete 101 edits",
- "lang": "en"
- },
- {
- "name": "en category-subcat-count 0,10",
- "key": "en_category-subcat-count",
- "args": [
- 0,
- 10
- ],
- "result": "This category has the following 0 subcategories, out of 10 total.",
- "lang": "en"
- },
- {
- "name": "en category-subcat-count 1,1",
- "key": "en_category-subcat-count",
- "args": [
- 1,
- 1
- ],
- "result": "This category has only the following subcategory.",
- "lang": "en"
- },
- {
- "name": "en category-subcat-count 1,2",
- "key": "en_category-subcat-count",
- "args": [
- 1,
- 2
- ],
- "result": "This category has the following subcategory, out of 2 total.",
- "lang": "en"
- },
- {
- "name": "en category-subcat-count 3,30",
- "key": "en_category-subcat-count",
- "args": [
- 3,
- 30
- ],
- "result": "This category has the following 3 subcategories, out of 30 total.",
- "lang": "en"
- },
- {
- "name": "fr undelete_short 0",
- "key": "fr_undelete_short",
- "args": [
- 0
- ],
- "result": "Restaurer 0 modification",
- "lang": "fr"
- },
- {
- "name": "fr undelete_short 1",
- "key": "fr_undelete_short",
- "args": [
- 1
- ],
- "result": "Restaurer 1 modification",
- "lang": "fr"
- },
- {
- "name": "fr undelete_short 2",
- "key": "fr_undelete_short",
- "args": [
- 2
- ],
- "result": "Restaurer 2 modifications",
- "lang": "fr"
- },
- {
- "name": "fr undelete_short 5",
- "key": "fr_undelete_short",
- "args": [
- 5
- ],
- "result": "Restaurer 5 modifications",
- "lang": "fr"
- },
- {
- "name": "fr undelete_short 21",
- "key": "fr_undelete_short",
- "args": [
- 21
- ],
- "result": "Restaurer 21 modifications",
- "lang": "fr"
- },
- {
- "name": "fr undelete_short 101",
- "key": "fr_undelete_short",
- "args": [
- 101
- ],
- "result": "Restaurer 101 modifications",
- "lang": "fr"
- },
- {
- "name": "fr category-subcat-count 0,10",
- "key": "fr_category-subcat-count",
- "args": [
- 0,
- 10
- ],
- "result": "Cette cat\u00e9gorie comprend 10 sous-cat\u00e9gories, dont celle ci-dessous.",
- "lang": "fr"
- },
- {
- "name": "fr category-subcat-count 1,1",
- "key": "fr_category-subcat-count",
- "args": [
- 1,
- 1
- ],
- "result": "Cette cat\u00e9gorie comprend la sous-cat\u00e9gorie ci-dessous.",
- "lang": "fr"
- },
- {
- "name": "fr category-subcat-count 1,2",
- "key": "fr_category-subcat-count",
- "args": [
- 1,
- 2
- ],
- "result": "Cette cat\u00e9gorie comprend 2 sous-cat\u00e9gories, dont celle ci-dessous.",
- "lang": "fr"
- },
- {
- "name": "fr category-subcat-count 3,30",
- "key": "fr_category-subcat-count",
- "args": [
- 3,
- 30
- ],
- "result": "Cette cat\u00e9gorie comprend 30 sous-cat\u00e9gories, dont les 3 ci-dessous.",
- "lang": "fr"
- },
- {
- "name": "ar undelete_short 0",
- "key": "ar_undelete_short",
- "args": [
- 0
- ],
- "result": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 \u062a\u0639\u062f\u064a\u0644 \u0648\u0627\u062d\u062f",
- "lang": "ar"
- },
- {
- "name": "ar undelete_short 1",
- "key": "ar_undelete_short",
- "args": [
- 1
- ],
- "result": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 \u062a\u0639\u062f\u064a\u0644\u064a\u0646",
- "lang": "ar"
- },
- {
- "name": "ar undelete_short 2",
- "key": "ar_undelete_short",
- "args": [
- 2
- ],
- "result": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 2 \u062a\u0639\u062f\u064a\u0644\u0627\u062a",
- "lang": "ar"
- },
- {
- "name": "ar undelete_short 5",
- "key": "ar_undelete_short",
- "args": [
- 5
- ],
- "result": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 5 \u062a\u0639\u062f\u064a\u0644",
- "lang": "ar"
- },
- {
- "name": "ar undelete_short 21",
- "key": "ar_undelete_short",
- "args": [
- 21
- ],
- "result": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 21 \u062a\u0639\u062f\u064a\u0644\u0627",
- "lang": "ar"
- },
- {
- "name": "ar undelete_short 101",
- "key": "ar_undelete_short",
- "args": [
- 101
- ],
- "result": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 101 \u062a\u0639\u062f\u064a\u0644\u0627",
- "lang": "ar"
- },
- {
- "name": "ar category-subcat-count 0,10",
- "key": "ar_category-subcat-count",
- "args": [
- 0,
- 10
- ],
- "result": "\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0641\u064a\u0647 \u060c \u0645\u0646 \u0625\u062c\u0645\u0627\u0644\u064a 10.",
- "lang": "ar"
- },
- {
- "name": "ar category-subcat-count 1,1",
- "key": "ar_category-subcat-count",
- "args": [
- 1,
- 1
- ],
- "result": "\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0641\u064a\u0647 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0627\u0644\u0641\u0631\u0639\u064a \u0627\u0644\u062a\u0627\u0644\u064a \u0641\u0642\u0637.",
- "lang": "ar"
- },
- {
- "name": "ar category-subcat-count 1,2",
- "key": "ar_category-subcat-count",
- "args": [
- 1,
- 2
- ],
- "result": "\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0641\u064a\u0647 \u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0627\u0644\u0641\u0631\u0639\u064a\u060c \u0645\u0646 \u0625\u062c\u0645\u0627\u0644\u064a 2.",
- "lang": "ar"
- },
- {
- "name": "ar category-subcat-count 3,30",
- "key": "ar_category-subcat-count",
- "args": [
- 3,
- 30
- ],
- "result": "\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0641\u064a\u0647 \u0647\u0630\u0647 \u0627\u06443 \u062a\u0635\u0627\u0646\u064a\u0641 \u0627\u0644\u0641\u0631\u0639\u064a\u0629\u060c \u0645\u0646 \u0625\u062c\u0645\u0627\u0644\u064a 30.",
- "lang": "ar"
- },
- {
- "name": "jp undelete_short 0",
- "key": "jp_undelete_short",
- "args": [
- 0
- ],
- "result": "Undelete 0 edits",
- "lang": "jp"
- },
- {
- "name": "jp undelete_short 1",
- "key": "jp_undelete_short",
- "args": [
- 1
- ],
- "result": "Undelete one edit",
- "lang": "jp"
- },
- {
- "name": "jp undelete_short 2",
- "key": "jp_undelete_short",
- "args": [
- 2
- ],
- "result": "Undelete 2 edits",
- "lang": "jp"
- },
- {
- "name": "jp undelete_short 5",
- "key": "jp_undelete_short",
- "args": [
- 5
- ],
- "result": "Undelete 5 edits",
- "lang": "jp"
- },
- {
- "name": "jp undelete_short 21",
- "key": "jp_undelete_short",
- "args": [
- 21
- ],
- "result": "Undelete 21 edits",
- "lang": "jp"
- },
- {
- "name": "jp undelete_short 101",
- "key": "jp_undelete_short",
- "args": [
- 101
- ],
- "result": "Undelete 101 edits",
- "lang": "jp"
- },
- {
- "name": "jp category-subcat-count 0,10",
- "key": "jp_category-subcat-count",
- "args": [
- 0,
- 10
- ],
- "result": "This category has the following 0 subcategories, out of 10 total.",
- "lang": "jp"
- },
- {
- "name": "jp category-subcat-count 1,1",
- "key": "jp_category-subcat-count",
- "args": [
- 1,
- 1
- ],
- "result": "This category has only the following subcategory.",
- "lang": "jp"
- },
- {
- "name": "jp category-subcat-count 1,2",
- "key": "jp_category-subcat-count",
- "args": [
- 1,
- 2
- ],
- "result": "This category has the following subcategory, out of 2 total.",
- "lang": "jp"
- },
- {
- "name": "jp category-subcat-count 3,30",
- "key": "jp_category-subcat-count",
- "args": [
- 3,
- 30
- ],
- "result": "This category has the following 3 subcategories, out of 30 total.",
- "lang": "jp"
- },
- {
- "name": "zh undelete_short 0",
- "key": "zh_undelete_short",
- "args": [
- 0
- ],
- "result": "\u6062\u590d\u88ab\u5220\u9664\u76840\u9879\u4fee\u8ba2",
- "lang": "zh"
- },
- {
- "name": "zh undelete_short 1",
- "key": "zh_undelete_short",
- "args": [
- 1
- ],
- "result": "\u6062\u590d\u88ab\u5220\u9664\u76841\u9879\u4fee\u8ba2",
- "lang": "zh"
- },
- {
- "name": "zh undelete_short 2",
- "key": "zh_undelete_short",
- "args": [
- 2
- ],
- "result": "\u6062\u590d\u88ab\u5220\u9664\u76842\u9879\u4fee\u8ba2",
- "lang": "zh"
- },
- {
- "name": "zh undelete_short 5",
- "key": "zh_undelete_short",
- "args": [
- 5
- ],
- "result": "\u6062\u590d\u88ab\u5220\u9664\u76845\u9879\u4fee\u8ba2",
- "lang": "zh"
- },
- {
- "name": "zh undelete_short 21",
- "key": "zh_undelete_short",
- "args": [
- 21
- ],
- "result": "\u6062\u590d\u88ab\u5220\u9664\u768421\u9879\u4fee\u8ba2",
- "lang": "zh"
- },
- {
- "name": "zh undelete_short 101",
- "key": "zh_undelete_short",
- "args": [
- 101
- ],
- "result": "\u6062\u590d\u88ab\u5220\u9664\u7684101\u9879\u4fee\u8ba2",
- "lang": "zh"
- },
- {
- "name": "zh category-subcat-count 0,10",
- "key": "zh_category-subcat-count",
- "args": [
- 0,
- 10
- ],
- "result": "\u672c\u5206\u7c7b\u5305\u542b\u4e0b\u52170\u4e2a\u5b50\u5206\u7c7b\uff0c\u5171\u670910\u4e2a\u5b50\u5206\u7c7b\u3002",
- "lang": "zh"
- },
- {
- "name": "zh category-subcat-count 1,1",
- "key": "zh_category-subcat-count",
- "args": [
- 1,
- 1
- ],
- "result": "\u672c\u5206\u7c7b\u53ea\u6709\u4e0b\u5217\u4e00\u4e2a\u5b50\u5206\u7c7b\u3002",
- "lang": "zh"
- },
- {
- "name": "zh category-subcat-count 1,2",
- "key": "zh_category-subcat-count",
- "args": [
- 1,
- 2
- ],
- "result": "\u672c\u5206\u7c7b\u5305\u542b\u4e0b\u52171\u4e2a\u5b50\u5206\u7c7b\uff0c\u5171\u67092\u4e2a\u5b50\u5206\u7c7b\u3002",
- "lang": "zh"
- },
- {
- "name": "zh category-subcat-count 3,30",
- "key": "zh_category-subcat-count",
- "args": [
- 3,
- 30
- ],
- "result": "\u672c\u5206\u7c7b\u5305\u542b\u4e0b\u52173\u4e2a\u5b50\u5206\u7c7b\uff0c\u5171\u670930\u4e2a\u5b50\u5206\u7c7b\u3002",
- "lang": "zh"
- }
-];
+++ /dev/null
-/* spec for language & message behaviour in MediaWiki */
-
-mw.messages.set( {
- "en_empty": "",
- "en_simple": "Simple message",
- "en_replace": "Simple $1 replacement",
- "en_replace2": "Simple $1 $2 replacements",
- "en_link": "Simple [http://example.com link to example].",
- "en_link_replace": "Complex [$1 $2] behaviour.",
- "en_simple_magic": "Simple {{ALOHOMORA}} message",
- "en_undelete_short": "Undelete {{PLURAL:$1|one edit|$1 edits}}",
- "en_undelete_empty_param": "Undelete{{PLURAL:$1|| multiple edits}}",
- "en_category-subcat-count": "{{PLURAL:$2|This category has only the following subcategory.|This category has the following {{PLURAL:$1|subcategory|$1 subcategories}}, out of $2 total.}}",
- "en_escape0": "Escape \\to fantasy island",
- "en_escape1": "I had \\$2.50 in my pocket",
- "en_escape2": "I had {{PLURAL:$1|the absolute \\|$1\\| which came out to \\$3.00 in my C:\\\\drive| some stuff}}",
- "en_fail": "This should fail to {{parse",
- "en_fail_magic": "There is no such magic word as {{SIETNAME}}",
- "en_evil": "This has <script type='text/javascript'>window.en_evil = true;</script> tags"
-} );
-
-/**
- * Tests
- */
-( function( mw, $, undefined ) {
-
- describe( "mediaWiki.jqueryMsg", function() {
-
- describe( "basic message functionality", function() {
-
- it( "should return identity for empty string", function() {
- var parser = new mw.jqueryMsg.parser();
- expect( parser.parse( 'en_empty' ).html() ).toEqual( '' );
- } );
-
-
- it( "should return identity for simple string", function() {
- var parser = new mw.jqueryMsg.parser();
- expect( parser.parse( 'en_simple' ).html() ).toEqual( 'Simple message' );
- } );
-
- } );
-
- describe( "escaping", function() {
-
- it ( "should handle simple escaping", function() {
- var parser = new mw.jqueryMsg.parser();
- expect( parser.parse( 'en_escape0' ).html() ).toEqual( 'Escape to fantasy island' );
- } );
-
- it ( "should escape dollar signs found in ordinary text when backslashed", function() {
- var parser = new mw.jqueryMsg.parser();
- expect( parser.parse( 'en_escape1' ).html() ).toEqual( 'I had $2.50 in my pocket' );
- } );
-
- it ( "should handle a complicated escaping case, including escaped pipe chars in template args", function() {
- var parser = new mw.jqueryMsg.parser();
- expect( parser.parse( 'en_escape2', [ 1 ] ).html() ).toEqual( 'I had the absolute |1| which came out to $3.00 in my C:\\drive' );
- } );
-
- } );
-
- describe( "replacing", function() {
-
- it ( "should handle simple replacing", function() {
- var parser = new mw.jqueryMsg.parser();
- expect( parser.parse( 'en_replace', [ 'foo' ] ).html() ).toEqual( 'Simple foo replacement' );
- } );
-
- it ( "should return $n if replacement not there", function() {
- var parser = new mw.jqueryMsg.parser();
- expect( parser.parse( 'en_replace', [] ).html() ).toEqual( 'Simple $1 replacement' );
- expect( parser.parse( 'en_replace2', [ 'bar' ] ).html() ).toEqual( 'Simple bar $2 replacements' );
- } );
-
- } );
-
- describe( "linking", function() {
-
- it ( "should handle a simple link", function() {
- var parser = new mw.jqueryMsg.parser();
- var parsed = parser.parse( 'en_link' );
- var contents = parsed.contents();
- expect( contents.length ).toEqual( 3 );
- expect( contents[0].nodeName ).toEqual( '#text' );
- expect( contents[0].nodeValue ).toEqual( 'Simple ' );
- expect( contents[1].nodeName ).toEqual( 'A' );
- expect( contents[1].getAttribute( 'href' ) ).toEqual( 'http://example.com' );
- expect( contents[1].childNodes[0].nodeValue ).toEqual( 'link to example' );
- expect( contents[2].nodeName ).toEqual( '#text' );
- expect( contents[2].nodeValue ).toEqual( '.' );
- } );
-
- it ( "should replace a URL into a link", function() {
- var parser = new mw.jqueryMsg.parser();
- var parsed = parser.parse( 'en_link_replace', [ 'http://example.com/foo', 'linking' ] );
- var contents = parsed.contents();
- expect( contents.length ).toEqual( 3 );
- expect( contents[0].nodeName ).toEqual( '#text' );
- expect( contents[0].nodeValue ).toEqual( 'Complex ' );
- expect( contents[1].nodeName ).toEqual( 'A' );
- expect( contents[1].getAttribute( 'href' ) ).toEqual( 'http://example.com/foo' );
- expect( contents[1].childNodes[0].nodeValue ).toEqual( 'linking' );
- expect( contents[2].nodeName ).toEqual( '#text' );
- expect( contents[2].nodeValue ).toEqual( ' behaviour.' );
- } );
-
- it ( "should bind a click handler into a link", function() {
- var parser = new mw.jqueryMsg.parser();
- var clicked = false;
- var click = function() { clicked = true; };
- var parsed = parser.parse( 'en_link_replace', [ click, 'linking' ] );
- var contents = parsed.contents();
- expect( contents.length ).toEqual( 3 );
- expect( contents[0].nodeName ).toEqual( '#text' );
- expect( contents[0].nodeValue ).toEqual( 'Complex ' );
- expect( contents[1].nodeName ).toEqual( 'A' );
- expect( contents[1].getAttribute( 'href' ) ).toEqual( '#' );
- expect( contents[1].childNodes[0].nodeValue ).toEqual( 'linking' );
- expect( contents[2].nodeName ).toEqual( '#text' );
- expect( contents[2].nodeValue ).toEqual( ' behaviour.' );
- // determining bindings is hard in IE
- var anchor = parsed.find( 'a' );
- if ( ( $.browser.mozilla || $.browser.webkit ) && anchor.click ) {
- expect( clicked ).toEqual( false );
- anchor.click();
- expect( clicked ).toEqual( true );
- }
- } );
-
- it ( "should wrap a jquery arg around link contents -- even another element", function() {
- var parser = new mw.jqueryMsg.parser();
- var clicked = false;
- var click = function() { clicked = true; };
- var button = $( '<button>' ).click( click );
- var parsed = parser.parse( 'en_link_replace', [ button, 'buttoning' ] );
- var contents = parsed.contents();
- expect( contents.length ).toEqual( 3 );
- expect( contents[0].nodeName ).toEqual( '#text' );
- expect( contents[0].nodeValue ).toEqual( 'Complex ' );
- expect( contents[1].nodeName ).toEqual( 'BUTTON' );
- expect( contents[1].childNodes[0].nodeValue ).toEqual( 'buttoning' );
- expect( contents[2].nodeName ).toEqual( '#text' );
- expect( contents[2].nodeValue ).toEqual( ' behaviour.' );
- // determining bindings is hard in IE
- if ( ( $.browser.mozilla || $.browser.webkit ) && button.click ) {
- expect( clicked ).toEqual( false );
- parsed.find( 'button' ).click();
- expect( clicked ).toEqual( true );
- }
- } );
-
-
- } );
-
-
- describe( "magic keywords", function() {
- it( "should substitute magic keywords", function() {
- var options = {
- magic: {
- 'alohomora' : 'open'
- }
- };
- var parser = new mw.jqueryMsg.parser( options );
- expect( parser.parse( 'en_simple_magic' ).html() ).toEqual( 'Simple open message' );
- } );
- } );
-
- describe( "error conditions", function() {
- it( "should return non-existent key in square brackets", function() {
- var parser = new mw.jqueryMsg.parser();
- expect( parser.parse( 'en_does_not_exist' ).html() ).toEqual( '[en_does_not_exist]' );
- } );
-
-
- it( "should fail to parse", function() {
- var parser = new mw.jqueryMsg.parser();
- expect( function() { parser.parse( 'en_fail' ); } ).toThrow(
- 'Parse error at position 20 in input: This should fail to {{parse'
- );
- } );
- } );
-
- describe( "empty parameters", function() {
- it( "should deal with empty parameters", function() {
- var parser = new mw.jqueryMsg.parser();
- var ast = parser.getAst( 'en_undelete_empty_param' );
- expect( parser.parse( 'en_undelete_empty_param', [ 1 ] ).html() ).toEqual( 'Undelete' );
- expect( parser.parse( 'en_undelete_empty_param', [ 3 ] ).html() ).toEqual( 'Undelete multiple edits' );
-
- } );
- } );
-
- describe( "easy message interface functions", function() {
- it( "should allow a global that returns strings", function() {
- var gM = mw.jqueryMsg.getMessageFunction();
- // passing this through jQuery and back to string, because browsers may have subtle differences, like the case of tag names.
- // a surrounding <SPAN> is needed for html() to work right
- var expectedHtml = $( '<span>Complex <a href="http://example.com/foo">linking</a> behaviour.</span>' ).html();
- var result = gM( 'en_link_replace', 'http://example.com/foo', 'linking' );
- expect( typeof result ).toEqual( 'string' );
- expect( result ).toEqual( expectedHtml );
- } );
-
- it( "should allow a jQuery plugin that appends to nodes", function() {
- $.fn.msg = mw.jqueryMsg.getPlugin();
- var $div = $( '<div>' ).append( $( '<p>' ).addClass( 'foo' ) );
- var clicked = false;
- var $button = $( '<button>' ).click( function() { clicked = true; } );
- $div.find( '.foo' ).msg( 'en_link_replace', $button, 'buttoning' );
- // passing this through jQuery and back to string, because browsers may have subtle differences, like the case of tag names.
- // a surrounding <SPAN> is needed for html() to work right
- var expectedHtml = $( '<span>Complex <button>buttoning</button> behaviour.</span>' ).html();
- var createdHtml = $div.find( '.foo' ).html();
- // it is hard to test for clicks with IE; also it inserts or removes spaces around nodes when creating HTML tags, depending on their type.
- // so need to check the strings stripped of spaces.
- if ( ( $.browser.mozilla || $.browser.webkit ) && $button.click ) {
- expect( createdHtml ).toEqual( expectedHtml );
- $div.find( 'button ').click();
- expect( clicked ).toEqual( true );
- } else if ( $.browser.ie ) {
- expect( createdHtml.replace( /\s/, '' ) ).toEqual( expectedHtml.replace( /\s/, '' ) );
- }
- delete $.fn.msg;
- } );
-
- it( "jQuery plugin should escape incoming string arguments", function() {
- $.fn.msg = mw.jqueryMsg.getPlugin();
- var $div = $( '<div>' ).addClass( 'foo' );
- $div.msg( 'en_replace', '<p>x</p>' ); // looks like HTML, but as a string, should be escaped.
- // passing this through jQuery and back to string, because browsers may have subtle differences, like the case of tag names.
- var expectedHtml = $( '<div class="foo">Simple <p>x</p> replacement</div>' ).html();
- var createdHtml = $div.html();
- expect( expectedHtml ).toEqual( createdHtml );
- delete $.fn.msg;
- } );
-
-
- it( "jQuery plugin should never execute scripts", function() {
- window.en_evil = false;
- $.fn.msg = mw.jqueryMsg.getPlugin();
- var $div = $( '<div>' );
- $div.msg( 'en_evil' );
- expect( window.en_evil ).toEqual( false );
- delete $.fn.msg;
- } );
-
-
- // n.b. this passes because jQuery already seems to strip scripts away; however, it still executes them if they are appended to any element.
- it( "jQuery plugin should never emit scripts", function() {
- $.fn.msg = mw.jqueryMsg.getPlugin();
- var $div = $( '<div>' );
- $div.msg( 'en_evil' );
- // passing this through jQuery and back to string, because browsers may have subtle differences, like the case of tag names.
- var expectedHtml = $( '<div>This has tags</div>' ).html();
- var createdHtml = $div.html();
- expect( expectedHtml ).toEqual( createdHtml );
- console.log( 'expected: ' + expectedHtml );
- console.log( 'created: ' + createdHtml );
- delete $.fn.msg;
- } );
-
-
-
- } );
-
- // The parser functions can throw errors, but let's not actually blow up for the user -- instead dump the error into the interface so we have
- // a chance at fixing this
- describe( "easy message interface functions with graceful failures", function() {
- it( "should allow a global that returns strings, with graceful failure", function() {
- var gM = mw.jqueryMsg.getMessageFunction();
- // passing this through jQuery and back to string, because browsers may have subtle differences, like the case of tag names.
- // a surrounding <SPAN> is needed for html() to work right
- var expectedHtml = $( '<span>en_fail: Parse error at position 20 in input: This should fail to {{parse</span>' ).html();
- var result = gM( 'en_fail' );
- expect( typeof result ).toEqual( 'string' );
- expect( result ).toEqual( expectedHtml );
- } );
-
- it( "should allow a global that returns strings, with graceful failure on missing magic words", function() {
- var gM = mw.jqueryMsg.getMessageFunction();
- // passing this through jQuery and back to string, because browsers may have subtle differences, like the case of tag names.
- // a surrounding <SPAN> is needed for html() to work right
- var expectedHtml = $( '<span>en_fail_magic: unknown operation "sietname"</span>' ).html();
- var result = gM( 'en_fail_magic' );
- expect( typeof result ).toEqual( 'string' );
- expect( result ).toEqual( expectedHtml );
- } );
-
-
- it( "should allow a jQuery plugin, with graceful failure", function() {
- $.fn.msg = mw.jqueryMsg.getPlugin();
- var $div = $( '<div>' ).append( $( '<p>' ).addClass( 'foo' ) );
- $div.find( '.foo' ).msg( 'en_fail' );
- // passing this through jQuery and back to string, because browsers may have subtle differences, like the case of tag names.
- // a surrounding <SPAN> is needed for html() to work right
- var expectedHtml = $( '<span>en_fail: Parse error at position 20 in input: This should fail to {{parse</span>' ).html();
- var createdHtml = $div.find( '.foo' ).html();
- expect( createdHtml ).toEqual( expectedHtml );
- delete $.fn.msg;
- } );
-
- } );
-
-
-
-
- describe( "test plurals and other language-specific functions", function() {
- /* copying some language definitions in here -- it's hard to make this test fast and reliable
- otherwise, and we don't want to have to know the mediawiki URL from this kind of test either.
- We also can't preload the langs for the test since they clobber the same namespace.
- In principle Roan said it was okay to change how languages worked so that didn't happen... maybe
- someday. We'd have to the same kind of importing of the default rules for most rules, or maybe
- come up with some kind of subclassing scheme for languages */
- var languageClasses = {
- ar: {
- /**
- * Arabic (العربية) language functions
- */
-
- convertPlural: function( count, forms ) {
- forms = mw.language.preConvertPlural( forms, 6 );
- if ( count === 0 ) {
- return forms[0];
- }
- if ( count == 1 ) {
- return forms[1];
- }
- if ( count == 2 ) {
- return forms[2];
- }
- if ( count % 100 >= 3 && count % 100 <= 10 ) {
- return forms[3];
- }
- if ( count % 100 >= 11 && count % 100 <= 99 ) {
- return forms[4];
- }
- return forms[5];
- },
-
- digitTransformTable: {
- '0': '٠', // ٠
- '1': '١', // ١
- '2': '٢', // ٢
- '3': '٣', // ٣
- '4': '٤', // ٤
- '5': '٥', // ٥
- '6': '٦', // ٦
- '7': '٧', // ٧
- '8': '٨', // ٨
- '9': '٩', // ٩
- '.': '٫', // ٫ wrong table ?
- ',': '٬' // ٬
- }
-
- },
- en: { },
- fr: {
- convertPlural: function( count, forms ) {
- forms = mw.language.preConvertPlural( forms, 2 );
- return ( count <= 1 ) ? forms[0] : forms[1];
- }
- },
- jp: { },
- zh: { }
- };
-
- /* simulate how the language classes override, or don't, the standard functions in mw.language */
- $.each( languageClasses, function( langCode, rules ) {
- $.each( [ 'convertPlural', 'convertNumber' ], function( i, propertyName ) {
- if ( typeof rules[ propertyName ] === 'undefined' ) {
- rules[ propertyName ] = mw.language[ propertyName ];
- }
- } );
- } );
-
- $.each( jasmineMsgSpec, function( i, test ) {
- it( "should parse " + test.name, function() {
- // using language override so we don't have to muck with global namespace
- var parser = new mw.jqueryMsg.parser( { language: languageClasses[ test.lang ] } );
- var parsedHtml = parser.parse( test.key, test.args ).html();
- expect( parsedHtml ).toEqual( test.result );
- } );
- } );
-
- } );
-
- } );
-} )( window.mediaWiki, jQuery );
+++ /dev/null
-<?php
-
-/**
- * This PHP script defines the spec that the Javascript message parser should conform to.
- *
- * It does this by looking up the results of various string kinds of string parsing, with various languages,
- * in the current installation of MediaWiki. It then outputs a static specification, mapping expected inputs to outputs,
- * which can be used with the JasmineBDD framework. This specification can then be used by simply including it into
- * the SpecRunner.html file.
- *
- * This is similar to Michael Dale (mdale@mediawiki.org)'s parser tests, except that it doesn't look up the
- * API results while doing the test, so the Jasmine run is much faster(at the cost of being out of date in rare
- * circumstances. But mostly the parsing that we are doing in Javascript doesn't change much.)
- *
- */
-
-$maintenanceDir = dirname( dirname( dirname( __DIR__ ) ) ) . '/maintenance';
-
-require( "$maintenanceDir/Maintenance.php" );
-
-class MakeLanguageSpec extends Maintenance {
-
- static $keyToTestArgs = array(
- 'undelete_short' => array(
- array( 0 ),
- array( 1 ),
- array( 2 ),
- array( 5 ),
- array( 21 ),
- array( 101 )
- ),
- 'category-subcat-count' => array(
- array( 0, 10 ),
- array( 1, 1 ),
- array( 1, 2 ),
- array( 3, 30 )
- )
- );
-
- public function __construct() {
- parent::__construct();
- $this->mDescription = "Create a JasmineBDD-compatible specification for message parsing";
- // add any other options here
- }
-
- public function execute() {
- list( $messages, $tests ) = $this->getMessagesAndTests();
- $this->writeJavascriptFile( $messages, $tests, "spec/mediawiki.language.parser.spec.data.js" );
- }
-
- private function getMessagesAndTests() {
- $messages = array();
- $tests = array();
- foreach ( array( 'en', 'fr', 'ar', 'jp', 'zh' ) as $languageCode ) {
- foreach ( self::$keyToTestArgs as $key => $testArgs ) {
- foreach ($testArgs as $args) {
- // get the raw template, without any transformations
- $template = wfMessage( $key )->inLanguage( $languageCode )->plain();
-
- $result = wfMessage( $key, $args )->inLanguage( $languageCode )->text();
-
- // record the template, args, language, and expected result
- // fake multiple languages by flattening them together
- $langKey = $languageCode . '_' . $key;
- $messages[ $langKey ] = $template;
- $tests[] = array(
- 'name' => $languageCode . " " . $key . " " . join( ",", $args ),
- 'key' => $langKey,
- 'args' => $args,
- 'result' => $result,
- 'lang' => $languageCode
- );
- }
- }
- }
- return array( $messages, $tests );
- }
-
- private function writeJavascriptFile( $messages, $tests, $dataSpecFile ) {
- global $argv;
- $arguments = count($argv) ? $argv : $_SERVER[ 'argv' ];
-
- $json = new Services_JSON;
- $json->pretty = true;
- $javascriptPrologue = "// This file stores the results from the PHP parser for certain messages and arguments,\n"
- . "// so we can test the equivalent Javascript libraries.\n"
- . '// Last generated with ' . join(' ', $arguments) . ' at ' . gmdate('c') . "\n\n";
- $javascriptMessages = "mediaWiki.messages.set( " . $json->encode( $messages, true ) . " );\n";
- $javascriptTests = 'var jasmineMsgSpec = ' . $json->encode( $tests, true ) . ";\n";
-
- $fp = fopen( $dataSpecFile, 'w' );
- if ( !$fp ) {
- die( "couldn't open $dataSpecFile for writing" );
- }
- $success = fwrite( $fp, $javascriptPrologue . $javascriptMessages . $javascriptTests );
- if ( !$success ) {
- die( "couldn't write to $dataSpecFile" );
- }
- $success = fclose( $fp );
- if ( !$success ) {
- die( "couldn't close $dataSpecFile" );
- }
- }
-}
-
-$maintClass = "MakeLanguageSpec";
-require_once( "$maintenanceDir/doMaintenance.php" );
-
-
-
'wgNoFollowLinks' => true,
'wgNoFollowDomainExceptions' => array(),
'wgThumbnailScriptPath' => false,
- 'wgUseImageResize' => false,
+ 'wgUseImageResize' => true,
'wgLocaltimezone' => 'UTC',
'wgAllowExternalImages' => true,
'wgUseTidy' => false,
"$dir/thumb/3/3a/Foobar.jpg/200px-Foobar.jpg",
"$dir/thumb/3/3a/Foobar.jpg/640px-Foobar.jpg",
"$dir/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg",
+ "$dir/thumb/3/3a/Foobar.jpg/1280px-Foobar.jpg",
+ "$dir/thumb/3/3a/Foobar.jpg/20px-Foobar.jpg",
+ "$dir/thumb/3/3a/Foobar.jpg/270px-Foobar.jpg",
+ "$dir/thumb/3/3a/Foobar.jpg/300px-Foobar.jpg",
+ "$dir/thumb/3/3a/Foobar.jpg/30px-Foobar.jpg",
+ "$dir/thumb/3/3a/Foobar.jpg/360px-Foobar.jpg",
+ "$dir/thumb/3/3a/Foobar.jpg/400px-Foobar.jpg",
+ "$dir/thumb/3/3a/Foobar.jpg/40px-Foobar.jpg",
+ "$dir/thumb/3/3a/Foobar.jpg/70px-Foobar.jpg",
+ "$dir/thumb/3/3a/Foobar.jpg/960px-Foobar.jpg",
"$dir/0/09/Bad.jpg",
}
}
- $page->doEdit( $text, '', EDIT_NEW );
+ $page->doEditContent( ContentHandler::makeContent( $text, $title ), '', EDIT_NEW );
$wgCapitalLinks = $oldCapitalLinks;
}
{{{1}}}
!! endarticle
+!! article
+Template:echo_with_span
+!! text
+<span>{{{1}}}</span>
+!! endarticle
+
+!! article
+Template:echo_with_div
+!! text
+<div>{{{1}}}</div>
+!! endarticle
+
+!! article
+Template:attr_str
+!! text
+{{{1}}}="{{{2}}}"
+!! endarticle
+
###
### Basic tests
###
</p>
!! end
+!!test
+Template-generated attribute string (k='v')
+!!input
+<span {{attr_str|id|v1}}>bar</span>
+!!result
+<p><span id="v1">bar</span>
+</p>
+!!end
!!article
Template:paramtest2
### <includeonly> and <noinclude> in attributes
###
!!test
-1. includeonly around the entire attribute
+0. includeonly around the entire attribute
!!input
<span <includeonly>id="v1"</includeonly><noinclude>id="v2"</noinclude>>bar</span>
!!result
!!end
!!test
-2. includeonly in html attr key
+1. includeonly in html attr key
!!input
<span <noinclude>id</noinclude><includeonly>about</includeonly>="foo">bar</span>
!!result
!!end
!!test
-3. includeonly in html attr value
+2. includeonly in html attr value
!!input
<span id="<noinclude>v1</noinclude><includeonly>v2</includeonly>">bar</span>
<span id=<noinclude>"v1"</noinclude><includeonly>"v2"</includeonly>>bar</span>
!!end
!!test
-4. includeonly in part of an attr value
+3. includeonly in part of an attr value
!!input
<span style="color:<noinclude>red</noinclude><includeonly>blue</includeonly>;">bar</span>
!!result
!!end
+!!test
+Templates: Ugly nesting: 1. Quotes opened/closed across templates (echo)
+!!input
+{{echo|''a}}{{echo|b''c''d}}{{echo|''e}}
+!!result
+<p><i>ab</i>c<i>d</i>e
+</p>
+!!end
+
+!!test
+Templates: Ugly nesting: 2. Quotes opened/closed across templates (echo_with_span)
+(PHP parser generates misnested html)
+!! options
+disabled
+!!input
+{{echo_with_span|''a}}{{echo_with_span|b''c''d}}{{echo_with_span|''e}}
+!!result
+<p><span><i>a</i></span><i><span>b</span></i><span>c</span><i>d</i><span>e</span></p>
+!!end
+
+!!test
+Templates: Ugly nesting: 3. Quotes opened/closed across templates (echo_with_div)
+(PHP parser generates misnested html)
+!! options
+disabled
+!!input
+{{echo_with_div|''a}}{{echo_with_div|b''c''d}}{{echo_with_div|''e}}
+!!result
+<div><i>a</i></div>
+<div><i>b</i>c<i>d</i></div>
+<div>e</div>
+!!end
+
+!!test
+Templates: Ugly nesting: 4. Divs opened/closed across templates
+!!input
+a<div>b{{echo|c</div>d}}e
+!!result
+a<div>bc</div>de
+
+!!end
+
!!test
Parser Functions: 1. Simple example
!!input
!! end
+!! test
+Image with empty attribute
+!! input
+[[Image:foobar.jpg|right||Caption text]]
+!! result
+<div class="floatright"><a href="/wiki/File:Foobar.jpg" class="image" title="Caption text"><img alt="Caption text" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a></div>
+
+!! end
+
!! test
Image with link parameter, wiki target
!! input
!! input
[[Image:foobar.jpg|thumb|link=http://example.com/|Title]]
!! result
-<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="http://example.com/"><img alt="" src="http://example.com/images/3/3a/Foobar.jpg" width="180" height="20" class="thumbimage" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div>Title</div></div></div>
+<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="http://example.com/"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg" width="180" height="20" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/270px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/360px-Foobar.jpg 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div>Title</div></div></div>
!! end
!! input
[[Image:foobar.jpg|thumb|http://example.com]]
!! result
-<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/3/3a/Foobar.jpg" width="180" height="20" class="thumbimage" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div><a rel="nofollow" class="external free" href="http://example.com">http://example.com</a></div></div></div>
+<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg" width="180" height="20" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/270px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/360px-Foobar.jpg 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div><a rel="nofollow" class="external free" href="http://example.com">http://example.com</a></div></div></div>
!! end
!! input
[[Image:foobar.jpg|thumb|http://example.com|alt=Alteration]]
!! result
-<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Alteration" src="http://example.com/images/3/3a/Foobar.jpg" width="180" height="20" class="thumbimage" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div><a rel="nofollow" class="external free" href="http://example.com">http://example.com</a></div></div></div>
+<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Alteration" src="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg" width="180" height="20" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/270px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/360px-Foobar.jpg 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div><a rel="nofollow" class="external free" href="http://example.com">http://example.com</a></div></div></div>
!! end
!! input
[[Image:foobar.jpg|thumb|ISBN 1235467890]]
!! result
-<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/3/3a/Foobar.jpg" width="180" height="20" class="thumbimage" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div><a href="/wiki/Special:BookSources/1235467890" class="internal mw-magiclink-isbn">ISBN 1235467890</a></div></div></div>
+<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg" width="180" height="20" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/270px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/360px-Foobar.jpg 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div><a href="/wiki/Special:BookSources/1235467890" class="internal mw-magiclink-isbn">ISBN 1235467890</a></div></div></div>
!! end
!! input
[[Image:foobar.jpg|thumb|This is RFC 12354]]
!! result
-<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/3/3a/Foobar.jpg" width="180" height="20" class="thumbimage" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div>This is <a class="external mw-magiclink-rfc" href="//tools.ietf.org/html/rfc12354">RFC 12354</a></div></div></div>
+<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg" width="180" height="20" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/270px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/360px-Foobar.jpg 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div>This is <a class="external mw-magiclink-rfc" href="//tools.ietf.org/html/rfc12354">RFC 12354</a></div></div></div>
!! end
!! input
[[Image:foobar.jpg|thumb|Please mailto:nobody@example.com]]
!! result
-<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/3/3a/Foobar.jpg" width="180" height="20" class="thumbimage" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div>Please <a rel="nofollow" class="external free" href="mailto:nobody@example.com">mailto:nobody@example.com</a></div></div></div>
+<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg" width="180" height="20" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/270px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/360px-Foobar.jpg 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div>Please <a rel="nofollow" class="external free" href="mailto:nobody@example.com">mailto:nobody@example.com</a></div></div></div>
!! end
!! input
[[Image:Foobar.jpg|thumb|This is a caption with another [[Image:icon.png|image]] inside it!]]
!! result
-<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/3/3a/Foobar.jpg" width="180" height="20" class="thumbimage" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div>This is a caption with another <a href="/index.php?title=Special:Upload&wpDestFile=Icon.png" class="new" title="File:Icon.png">image</a> inside it!</div></div></div>
+<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg" width="180" height="20" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/270px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/360px-Foobar.jpg 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div>This is a caption with another <a href="/index.php?title=Special:Upload&wpDestFile=Icon.png" class="new" title="File:Icon.png">image</a> inside it!</div></div></div>
!! end
!! input
[[Image:Foobar.jpg|thumb|200px|This caption has [irc://example.net irc] and [https://example.com Secure] ext links in it.]]
!! result
-<div class="thumb tright"><div class="thumbinner" style="width:202px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/3/3a/Foobar.jpg" width="200" height="23" class="thumbimage" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div>This caption has <a rel="nofollow" class="external text" href="irc://example.net">irc</a> and <a rel="nofollow" class="external text" href="https://example.com">Secure</a> ext links in it.</div></div></div>
+<div class="thumb tright"><div class="thumbinner" style="width:202px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/200px-Foobar.jpg" width="200" height="23" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/300px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/400px-Foobar.jpg 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div>This caption has <a rel="nofollow" class="external text" href="irc://example.net">irc</a> and <a rel="nofollow" class="external text" href="https://example.com">Secure</a> ext links in it.</div></div></div>
!! end
[[Category:Foo (bar)|Foo]]
!! end
+!! test
+Category with template
+!! options
+cat
+pst
+!! input
+[[Category:{{echo|Foo}}]]
+!! result
+[[Category:{{echo|Foo}}]]
+!! end
+
+!! test
+Category with template in sort key
+!! options
+cat
+pst
+!! input
+[[Category:Foo|{{echo|Bar}}]]
+!! result
+[[Category:Foo|{{echo|Bar}}]]
+!! end
+
+!! test
+Category with template in sort key and title
+!! options
+cat
+pst
+!! input
+[[Category:{{echo|Foo}}|{{echo|Bar}}]]
+!! result
+[[Category:{{echo|Foo}}|{{echo|Bar}}]]
+!! end
+
###
### Inter-language links
###
</div>
</div></li>
<li class="gallerybox" style="width: 105px"><div style="width: 105px">
- <div class="thumb" style="width: 100px;"><div style="margin:31px auto;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/3/3a/Foobar.jpg" width="70" height="8" /></a></div></div>
+ <div class="thumb" style="width: 100px;"><div style="margin:31px auto;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/70px-Foobar.jpg" width="70" height="8" /></a></div></div>
<div class="gallerytext">
<p>some <b>caption</b> <a href="/wiki/Main_Page" title="Main Page">Main Page</a>
</p>
</div>
</div></li>
<li class="gallerybox" style="width: 105px"><div style="width: 105px">
- <div class="thumb" style="width: 100px;"><div style="margin:31px auto;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="70" height="8" /></a></div></div>
+ <div class="thumb" style="width: 100px;"><div style="margin:31px auto;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/thumb/3/3a/Foobar.jpg/70px-Foobar.jpg" width="70" height="8" /></a></div></div>
<div class="gallerytext">
</div>
</div></li>
<li class="gallerybox" style="width: 105px"><div style="width: 105px">
- <div class="thumb" style="width: 100px;"><div style="margin:31px auto;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="This is a foo-bar." src="http://example.com/images/3/3a/Foobar.jpg" width="70" height="8" /></a></div></div>
+ <div class="thumb" style="width: 100px;"><div style="margin:31px auto;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="This is a foo-bar." src="http://example.com/images/thumb/3/3a/Foobar.jpg/70px-Foobar.jpg" width="70" height="8" /></a></div></div>
<div class="gallerytext">
<p>Blabla|blabla.
</p>
!! result
<ul class="gallery">
<li class="gallerybox" style="width: 155px"><div style="width: 155px">
- <div class="thumb" style="width: 150px;"><div style="margin:68px auto;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="galleryalt" src="http://example.com/images/3/3a/Foobar.jpg" width="120" height="14" /></a></div></div>
+ <div class="thumb" style="width: 150px;"><div style="margin:68px auto;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="galleryalt" src="http://example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" width="120" height="14" /></a></div></div>
<div class="gallerytext">
-<p><a href="/wiki/File:Foobar.jpg" class="image" title="desc"><img alt="inneralt" src="http://example.com/images/3/3a/Foobar.jpg" width="20" height="2" /></a>
+<p><a href="/wiki/File:Foobar.jpg" class="image" title="desc"><img alt="inneralt" src="http://example.com/images/thumb/3/3a/Foobar.jpg/20px-Foobar.jpg" width="20" height="2" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/30px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/40px-Foobar.jpg 2x" /></a>
</p>
</div>
</div></li>
<li class="gallerybox" style="width: 155px"><div style="width: 155px">
- <div class="thumb" style="width: 150px;"><div style="margin:68px auto;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="galleryalt" src="http://example.com/images/3/3a/Foobar.jpg" width="120" height="14" /></a></div></div>
+ <div class="thumb" style="width: 150px;"><div style="margin:68px auto;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="galleryalt" src="http://example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" width="120" height="14" /></a></div></div>
<div class="gallerytext">
<p>This is a test template
</p>
</div>
</div></li>
<li class="gallerybox" style="width: 155px"><div style="width: 155px">
- <div class="thumb" style="width: 150px;"><div style="margin:68px auto;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/3/3a/Foobar.jpg" width="120" height="14" /></a></div></div>
+ <div class="thumb" style="width: 150px;"><div style="margin:68px auto;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" width="120" height="14" /></a></div></div>
<div class="gallerytext">
<p><a href="/wiki/File:Foobar.jpg" title="File:Foobar.jpg">Foobar.jpg</a><br />
some <b>caption</b> <a href="/wiki/Main_Page" title="Main Page">Main Page</a>
</div>
</div></li>
<li class="gallerybox" style="width: 155px"><div style="width: 155px">
- <div class="thumb" style="width: 150px;"><div style="margin:68px auto;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="120" height="14" /></a></div></div>
+ <div class="thumb" style="width: 150px;"><div style="margin:68px auto;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" width="120" height="14" /></a></div></div>
<div class="gallerytext">
<p><a href="/wiki/File:Foobar.jpg" title="File:Foobar.jpg">Foobar.jpg</a><br />
</p>
</div>
</div></li>
<li class="gallerybox" style="width: 155px"><div style="width: 155px">
- <div class="thumb" style="width: 150px;"><div style="margin:68px auto;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="120" height="14" /></a></div></div>
+ <div class="thumb" style="width: 150px;"><div style="margin:68px auto;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" width="120" height="14" /></a></div></div>
<div class="gallerytext">
</div>
</div></li>
<li class="gallerybox" style="width: 155px"><div style="width: 155px">
- <div class="thumb" style="width: 150px;"><div style="margin:68px auto;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="120" height="14" /></a></div></div>
+ <div class="thumb" style="width: 150px;"><div style="margin:68px auto;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" width="120" height="14" /></a></div></div>
<div class="gallerytext">
</div>
</div></li>
!! input
[[Image:foobar.jpg|640x480px]]
!! result
-<p><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="640" height="73" /></a>
+<p><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/thumb/3/3a/Foobar.jpg/640px-Foobar.jpg" width="640" height="73" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/960px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/1280px-Foobar.jpg 2x" /></a>
</p>
!!end
!! input
[[Image:foobar.jpg|640px]]
!! result
-<p><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="640" height="73" /></a>
+<p><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/thumb/3/3a/Foobar.jpg/640px-Foobar.jpg" width="640" height="73" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/960px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/1280px-Foobar.jpg 2x" /></a>
</p>
!!end
!! input
[[Image:foobar.jpg|640px ]]
!! result
-<p><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="640" height="73" /></a>
+<p><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/thumb/3/3a/Foobar.jpg/640px-Foobar.jpg" width="640" height="73" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/960px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/1280px-Foobar.jpg 2x" /></a>
</p>
!!end
!! input
[[Image:foobar.jpg| 640px]]
!! result
-<p><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="640" height="73" /></a>
+<p><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/thumb/3/3a/Foobar.jpg/640px-Foobar.jpg" width="640" height="73" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/960px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/1280px-Foobar.jpg 2x" /></a>
</p>
!!end
!! input
[[image:Foobar.jpg|thumb|An [http://test/?param1=|left|¶m2=|x external] URL]]
!! result
-<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/3/3a/Foobar.jpg" width="180" height="20" class="thumbimage" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div>An <a rel="nofollow" class="external text" href="http://test/?param1=%7Cleft%7C&param2=%7Cx">external</a> URL</div></div></div>
+<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg" width="180" height="20" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/270px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/360px-Foobar.jpg 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div>An <a rel="nofollow" class="external text" href="http://test/?param1=%7Cleft%7C&param2=%7Cx">external</a> URL</div></div></div>
!!end
!! input
[[Image:Foobar.jpg|thumb|http://x|hello]]
!! result
-<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/3/3a/Foobar.jpg" width="180" height="20" class="thumbimage" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div>hello</div></div></div>
+<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg" width="180" height="20" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/270px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/360px-Foobar.jpg 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div>hello</div></div></div>
!! end
!! result
<ul class="gallery">
<li class="gallerybox" style="width: 155px"><div style="width: 155px">
- <div class="thumb" style="width: 150px;"><div style="margin:68px auto;"><a href="/wiki/InterWikiLink"><img alt="galleryalt" src="http://example.com/images/3/3a/Foobar.jpg" width="120" height="14" /></a></div></div>
+ <div class="thumb" style="width: 150px;"><div style="margin:68px auto;"><a href="/wiki/InterWikiLink"><img alt="galleryalt" src="http://example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" width="120" height="14" /></a></div></div>
<div class="gallerytext">
<p>caption
</p>
!! result
<ul class="gallery">
<li class="gallerybox" style="width: 155px"><div style="width: 155px">
- <div class="thumb" style="width: 150px;"><div style="margin:68px auto;"><a href="http://www.example.org"><img alt="galleryalt" src="http://example.com/images/3/3a/Foobar.jpg" width="120" height="14" /></a></div></div>
+ <div class="thumb" style="width: 150px;"><div style="margin:68px auto;"><a href="http://www.example.org"><img alt="galleryalt" src="http://example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" width="120" height="14" /></a></div></div>
<div class="gallerytext">
<p>caption
</p>
!! result
<ul class="gallery">
<li class="gallerybox" style="width: 155px"><div style="width: 155px">
- <div class="thumb" style="width: 150px;"><div style="margin:68px auto;"><a href="/wiki/%22_onclick%3D%22alert(%27malicious_javascript_code!%27);"><img alt="galleryalt" src="http://example.com/images/3/3a/Foobar.jpg" width="120" height="14" /></a></div></div>
+ <div class="thumb" style="width: 150px;"><div style="margin:68px auto;"><a href="/wiki/%22_onclick%3D%22alert(%27malicious_javascript_code!%27);"><img alt="galleryalt" src="http://example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" width="120" height="14" /></a></div></div>
<div class="gallerytext">
<p>caption
</p>
</p>
!! end
+###
+### Parsoids-specific tests
+### Parsoid-PHP parser incompatibilities
+###
+!!test
+1. SOL-sensitive wikitext tokens as template-args
+!!options
+disabled
+!!input
+{{echo|*a}}
+{{echo|#a}}
+{{echo|:a}}
+!!result
+<p>*a
+#a
+:a
+</p>
+!!end
TODO:
more images
* Base class that store and restore the Language objects
*/
abstract class MediaWikiLangTestCase extends MediaWikiTestCase {
- private static $oldLang;
- private static $oldContLang;
-
- public function setUp() {
- global $wgLanguageCode, $wgLang, $wgContLang;
+ protected function setUp() {
+ global $wgLanguageCode, $wgContLang;
parent::setUp();
- self::$oldLang = $wgLang;
- self::$oldContLang = $wgContLang;
-
- if( $wgLanguageCode != $wgContLang->getCode() ) {
+ if ( $wgLanguageCode != $wgContLang->getCode() ) {
throw new MWException("Error in MediaWikiLangTestCase::setUp(): " .
"\$wgLanguageCode ('$wgLanguageCode') is different from " .
"\$wgContLang->getCode() (" . $wgContLang->getCode() . ")" );
}
- $wgLanguageCode = 'en'; # For mainpage to be 'Main Page'
+ $langCode = 'en'; # For mainpage to be 'Main Page'
+ $langObj = Language::factory( $langCode );
- $wgContLang = $wgLang = Language::factory( $wgLanguageCode );
- MessageCache::singleton()->disable();
-
- }
+ $this->setMwGlobals( array(
+ 'wgLanguageCode' => $langCode,
+ 'wgLang' => $langObj,
+ 'wgContLang' => $langObj,
+ ) );
- public function tearDown() {
- global $wgContLang, $wgLang, $wgLanguageCode;
- $wgLang = self::$oldLang;
-
- $wgContLang = self::$oldContLang;
- $wgLanguageCode = $wgContLang->getCode();
- self::$oldContLang = self::$oldLang = null;
-
- parent::tearDown();
+ MessageCache::singleton()->disable();
}
-
}
*/
private $tmpfiles = array();
+ /**
+ * Holds original values of MediaWiki configuration settings
+ * to be restored in tearDown().
+ * See also setMwGlobal().
+ * @var array
+ */
+ private $mwGlobals = array();
/**
* Table name prefixes. Oracle likes it shorter.
return $fname;
}
+ /**
+ * setUp and tearDown should (where significant)
+ * happen in reverse order.
+ */
+ protected function setUp() {
+ parent::setUp();
+
+ /*
+ //@todo: global variables to restore for *every* test
+ array(
+ 'wgLang',
+ 'wgContLang',
+ 'wgLanguageCode',
+ 'wgUser',
+ 'wgTitle',
+ );
+ */
+
+ // Cleaning up temporary files
+ foreach ( $this->tmpfiles as $fname ) {
+ if ( is_file( $fname ) || ( is_link( $fname ) ) ) {
+ unlink( $fname );
+ } elseif ( is_dir( $fname ) ) {
+ wfRecursiveRemoveDir( $fname );
+ }
+ }
+
+ // Clean up open transactions
+ if ( $this->needsDB() && $this->db ) {
+ while( $this->db->trxLevel() > 0 ) {
+ $this->db->rollback();
+ }
+ }
+ }
+
protected function tearDown() {
// Cleaning up temporary files
foreach ( $this->tmpfiles as $fname ) {
}
}
- // clean up open transactions
- if( $this->needsDB() && $this->db ) {
+ // Clean up open transactions
+ if ( $this->needsDB() && $this->db ) {
while( $this->db->trxLevel() > 0 ) {
$this->db->rollback();
}
}
+ // Restore mw globals
+ foreach ( $this->mwGlobals as $key => $value ) {
+ $GLOBALS[$key] = $value;
+ }
+ $this->mwGlobals = array();
+
parent::tearDown();
}
+ /**
+ * Individual test functions may override globals (either directly or through this
+ * setMwGlobals() function), however one must call this method at least once for
+ * each key within the setUp().
+ * That way the key is added to the array of globals that will be reset afterwards
+ * in the tearDown(). And, equally important, that way all other tests are executed
+ * with the same settings (instead of using the unreliable local settings for most
+ * tests and fix it only for some tests).
+ *
+ * @example
+ * <code>
+ * protected function setUp() {
+ * $this->setMwGlobals( 'wgRestrictStuff', true );
+ * }
+ *
+ * function testFoo() {}
+ *
+ * function testBar() {}
+ * $this->assertTrue( self::getX()->doStuff() );
+ *
+ * $this->setMwGlobals( 'wgRestrictStuff', false );
+ * $this->assertTrue( self::getX()->doStuff() );
+ * }
+ *
+ * function testQuux() {}
+ * </code>
+ *
+ * @param array|string $pairs Key to the global variable, or an array
+ * of key/value pairs.
+ * @param mixed $value Value to set the global to (ignored
+ * if an array is given as first argument).
+ */
+ protected function setMwGlobals( $pairs, $value = null ) {
+ if ( !is_array( $pairs ) ) {
+ $key = $pairs;
+ $this->mwGlobals[$key] = $GLOBALS[$key];
+ $GLOBALS[$key] = $value;
+ } else {
+ foreach ( $pairs as $key => $value ) {
+ $this->mwGlobals[$key] = $GLOBALS[$key];
+ $GLOBALS[$key] = $value;
+ }
+ }
+ }
+
+ /**
+ * Merges the given values into a MW global array variable.
+ * Useful for setting some entries in a configuration array, instead of
+ * setting the entire array.
+ *
+ * @param String $name The name of the global, as in wgFooBar
+ * @param Array $values The array containing the entries to set in that global
+ *
+ * @throws MWException if the designated global is not an array.
+ */
+ protected function mergeMwGlobalArrayValue( $name, $values ) {
+ if ( !isset( $GLOBALS[$name] ) ) {
+ $merged = $values;
+ } else {
+ if ( !is_array( $GLOBALS[$name] ) ) {
+ throw new MWException( "MW global $name is not an array." );
+ }
+
+ //NOTE: do not use array_merge, it screws up for numeric keys.
+ $merged = $GLOBALS[$name];
+ foreach ( $values as $k => $v ) {
+ $merged[$k] = $v;
+ }
+ }
+
+ $this->setMwGlobals( $name, $merged );
+ }
+
function dbPrefix() {
return $this->db->getType() == 'oracle' ? self::ORA_DB_PREFIX : self::DB_PREFIX;
}
//Make 1 page with 1 revision
$page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
if ( !$page->getId() == 0 ) {
- $page->doEdit( 'UTContent',
- 'UTPageSummary',
- EDIT_NEW,
- false,
- User::newFromName( 'UTSysop' ) );
+ $page->doEditContent(
+ new WikitextContent( 'UTContent' ),
+ 'UTPageSummary',
+ EDIT_NEW,
+ false,
+ User::newFromName( 'UTSysop' ) );
}
}
}
}
+ /**
+ * Returns true iff the given namespace defaults to Wikitext
+ * according to $wgNamespaceContentModels
+ *
+ * @param int $ns The namespace ID to check
+ *
+ * @return bool
+ * @since 1.21
+ */
+ protected function isWikitextNS( $ns ) {
+ global $wgNamespaceContentModels;
+
+ if ( isset( $wgNamespaceContentModels[$ns] ) ) {
+ return $wgNamespaceContentModels[$ns] === CONTENT_MODEL_WIKITEXT;
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns the ID of a namespace that defaults to Wikitext.
+ * Throws an MWException if there is none.
+ *
+ * @return int the ID of the wikitext Namespace
+ * @since 1.21
+ */
+ protected function getDefaultWikitextNS() {
+ global $wgNamespaceContentModels;
+
+ static $wikitextNS = null; // this is not going to change
+ if ( $wikitextNS !== null ) {
+ return $wikitextNS;
+ }
+
+ // quickly short out on most common case:
+ if ( !isset( $wgNamespaceContentModels[NS_MAIN] ) ) {
+ return NS_MAIN;
+ }
+
+ // NOTE: prefer content namespaces
+ $namespaces = array_unique( array_merge(
+ MWNamespace::getContentNamespaces(),
+ array( NS_MAIN, NS_HELP, NS_PROJECT ), // prefer these
+ MWNamespace::getValidNamespaces()
+ ) );
+
+ $namespaces = array_diff( $namespaces, array(
+ NS_FILE, NS_CATEGORY, NS_MEDIAWIKI, NS_USER // don't mess with magic namespaces
+ ));
+
+ $talk = array_filter( $namespaces, function ( $ns ) {
+ return MWNamespace::isTalk( $ns );
+ } );
+
+ // prefer non-talk pages
+ $namespaces = array_diff( $namespaces, $talk );
+ $namespaces = array_merge( $namespaces, $talk );
+
+ // check default content model of each namespace
+ foreach ( $namespaces as $ns ) {
+ if ( !isset( $wgNamespaceContentModels[$ns] ) ||
+ $wgNamespaceContentModels[$ns] === CONTENT_MODEL_WIKITEXT ) {
+
+ $wikitextNS = $ns;
+ return $wikitextNS;
+ }
+ }
+
+ // give up
+ // @todo: Inside a test, we could skip the test as incomplete.
+ // But frequently, this is used in fixture setup.
+ throw new MWException( "No namespace defaults to wikitext!" );
+ }
}
$wgContLang = Language::factory( 'es' );
$wgLang = Language::factory( 'fr' );
- $status = $page->doEdit( '{{:{{int:history}}}}', 'Test code for bug 14404', 0, false, $user );
+ $status = $page->doEditContent( new WikitextContent( '{{:{{int:history}}}}' ), 'Test code for bug 14404', 0, false, $user );
$templates1 = $title->getTemplateLinksFrom();
$wgLang = Language::factory( 'de' );
$page->mPreparedEdit = false; // In order to force the rerendering of the same wikitext
// We need an edit, a purge is not enough to regenerate the tables
- $status = $page->doEdit( '{{:{{int:history}}}}', 'Test code for bug 14404', EDIT_UPDATE, false, $user );
+ $status = $page->doEditContent( new WikitextContent( '{{:{{int:history}}}}' ), 'Test code for bug 14404', EDIT_UPDATE, false, $user );
$templates2 = $title->getTemplateLinksFrom();
$this->assertEquals( $templates1, $templates2 );
class ArticleTest extends MediaWikiTestCase {
- private $title; // holds a Title object
- private $article; // holds an article
+ /**
+ * @var Title
+ */
+ private $title;
+ /**
+ * @var Article
+ */
+ private $article;
/** creates a title object and its article object */
- function setUp() {
- $this->title = Title::makeTitle( NS_MAIN, 'SomePage' );
+ protected function setUp() {
+ $this->title = Title::makeTitle( NS_MAIN, 'SomePage' );
$this->article = new Article( $this->title );
}
/** cleanup title object and its article object */
- function tearDown() {
- $this->title = null;
+ protected function tearDown() {
+ $this->title = null;
$this->article = null;
}
* Checks for the existence of the backwards compatibility static functions (forwarders to WikiPage class)
*/
function testStaticFunctions() {
+ $this->hideDeprecated( 'Article::getAutosummary' );
+ $this->hideDeprecated( 'WikiPage::getAutosummary' );
+
$this->assertEquals( WikiPage::selectFields(), Article::selectFields(),
"Article static functions" );
$this->assertEquals( true, is_callable( "Article::onArticleCreate" ),
/* variable used to save up the blockID we insert in this test suite */
private $blockId;
- function setUp() {
- global $wgContLang;
+ protected function setUp() {
parent::setUp();
- $wgContLang = Language::factory( 'en' );
+ $this->setMwGlobals( 'wgContLang', Language::factory( 'en' ) );
}
function addDBData() {
- //$this->dumpBlocks();
$user = User::newFromName( 'UTBlockee' );
- if( $user->getID() == 0 ) {
+ if ( $user->getID() == 0 ) {
$user->addToDatabase();
$user->setPassword( 'UTBlockeePassword' );
// its value might change depending on the order the tests are run.
// ApiBlockTest insert its own blocks!
$newBlockId = $this->block->getId();
- if ($newBlockId) {
+ if ( $newBlockId ) {
$this->blockId = $newBlockId;
} else {
throw new MWException( "Failed to insert block for BlockTest; old leftover block remaining?" );
*
* This stopped working with r84475 and friends: regression being fixed for bug 29116.
*
- * @dataProvider dataBug29116
+ * @dataProvider provideBug29116Data
*/
function testBug29116LoadWithEmptyIp( $vagueTarget ) {
$this->hideDeprecated( 'Block::load' );
* because the new function didn't accept empty strings like Block::load()
* had. Regression bug 29116.
*
- * @dataProvider dataBug29116
+ * @dataProvider provideBug29116Data
*/
function testBug29116NewFromTargetWithEmptyIp( $vagueTarget ) {
$block = Block::newFromTarget('UTBlockee', $vagueTarget);
$this->assertTrue( $this->block->equals( $block ), "newFromTarget() returns the same block as the one that was made when given empty vagueTarget param " . var_export( $vagueTarget, true ) );
}
- function dataBug29116() {
+ public static function provideBug29116Data() {
return array(
array( null ),
array( '' ),
class CdbTest extends MediaWikiTestCase {
- public function setUp() {
+ protected function setUp() {
if ( !CdbReader::haveExtension() ) {
$this->markTestSkipped( 'Native CDB support is not available' );
}
--- /dev/null
+<?php
+
+/**
+ * @group ContentHandler
+ *
+ * @note: Declare that we are using the database, because otherwise we'll fail in the "databaseless" test run.
+ * This is because the LinkHolderArray used by the parser needs database access.
+ *
+ * @group Database
+ */
+class ContentHandlerTest extends MediaWikiTestCase {
+
+ public function setup() {
+ parent::setup();
+
+ global $wgExtraNamespaces, $wgNamespaceContentModels, $wgContentHandlers, $wgContLang;
+
+ $wgExtraNamespaces[ 12312 ] = 'Dummy';
+ $wgExtraNamespaces[ 12313 ] = 'Dummy_talk';
+
+ $wgNamespaceContentModels[ 12312 ] = "testing";
+ $wgContentHandlers[ "testing" ] = 'DummyContentHandlerForTesting';
+
+ MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache
+ $wgContLang->resetNamespaces(); # reset namespace cache
+ }
+
+ public function teardown() {
+ global $wgExtraNamespaces, $wgNamespaceContentModels, $wgContentHandlers, $wgContLang;
+
+ unset( $wgExtraNamespaces[ 12312 ] );
+ unset( $wgExtraNamespaces[ 12313 ] );
+
+ unset( $wgNamespaceContentModels[ 12312 ] );
+ unset( $wgContentHandlers[ "testing" ] );
+
+ MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache
+ $wgContLang->resetNamespaces(); # reset namespace cache
+
+ parent::teardown();
+ }
+
+ public function dataGetDefaultModelFor() {
+ //NOTE: assume that the Help namespace default to wikitext content
+ return array(
+ array( 'Help:Foo', CONTENT_MODEL_WIKITEXT ),
+ array( 'Help:Foo.js', CONTENT_MODEL_WIKITEXT ),
+ array( 'Help:Foo/bar.js', CONTENT_MODEL_WIKITEXT ),
+ array( 'User:Foo', CONTENT_MODEL_WIKITEXT ),
+ array( 'User:Foo.js', CONTENT_MODEL_WIKITEXT ),
+ array( 'User:Foo/bar.js', CONTENT_MODEL_JAVASCRIPT ),
+ array( 'User:Foo/bar.css', CONTENT_MODEL_CSS ),
+ array( 'User talk:Foo/bar.css', CONTENT_MODEL_WIKITEXT ),
+ array( 'User:Foo/bar.js.xxx', CONTENT_MODEL_WIKITEXT ),
+ array( 'User:Foo/bar.xxx', CONTENT_MODEL_WIKITEXT ),
+ array( 'MediaWiki:Foo.js', CONTENT_MODEL_JAVASCRIPT ),
+ array( 'MediaWiki:Foo.css', CONTENT_MODEL_CSS ),
+ array( 'MediaWiki:Foo.JS', CONTENT_MODEL_WIKITEXT ),
+ array( 'MediaWiki:Foo.CSS', CONTENT_MODEL_WIKITEXT ),
+ array( 'MediaWiki:Foo.css.xxx', CONTENT_MODEL_WIKITEXT ),
+ );
+ }
+
+ /**
+ * @dataProvider dataGetDefaultModelFor
+ */
+ public function testGetDefaultModelFor( $title, $expectedModelId ) {
+ $title = Title::newFromText( $title );
+ $this->assertEquals( $expectedModelId, ContentHandler::getDefaultModelFor( $title ) );
+ }
+ /**
+ * @dataProvider dataGetDefaultModelFor
+ */
+ public function testGetForTitle( $title, $expectedContentModel ) {
+ $title = Title::newFromText( $title );
+ $handler = ContentHandler::getForTitle( $title );
+ $this->assertEquals( $expectedContentModel, $handler->getModelID() );
+ }
+
+ public function dataGetLocalizedName() {
+ return array(
+ array( null, null ),
+ array( "xyzzy", null ),
+
+ array( CONTENT_MODEL_JAVASCRIPT, '/javascript/i' ), //XXX: depends on content language
+ );
+ }
+
+ /**
+ * @dataProvider dataGetLocalizedName
+ */
+ public function testGetLocalizedName( $id, $expected ) {
+ $name = ContentHandler::getLocalizedName( $id );
+
+ if ( $expected ) {
+ $this->assertNotNull( $name, "no name found for content model $id" );
+ $this->assertTrue( preg_match( $expected, $name ) > 0 ,
+ "content model name for #$id did not match pattern $expected" );
+ } else {
+ $this->assertEquals( $id, $name, "localization of unknown model $id should have "
+ . "fallen back to use the model id directly." );
+ }
+ }
+
+ public function dataGetPageLanguage() {
+ global $wgLanguageCode;
+
+ return array(
+ array( "Main", $wgLanguageCode ),
+ array( "Dummy:Foo", $wgLanguageCode ),
+ array( "MediaWiki:common.js", 'en' ),
+ array( "User:Foo/common.js", 'en' ),
+ array( "MediaWiki:common.css", 'en' ),
+ array( "User:Foo/common.css", 'en' ),
+ array( "User:Foo", $wgLanguageCode ),
+
+ array( CONTENT_MODEL_JAVASCRIPT, 'javascript' ),
+ );
+ }
+
+ /**
+ * @dataProvider dataGetPageLanguage
+ */
+ public function testGetPageLanguage( $title, $expected ) {
+ if ( is_string( $title ) ) {
+ $title = Title::newFromText( $title );
+ }
+
+ $expected = wfGetLangObj( $expected );
+
+ $handler = ContentHandler::getForTitle( $title );
+ $lang = $handler->getPageLanguage( $title );
+
+ $this->assertEquals( $expected->getCode(), $lang->getCode() );
+ }
+
+ public function testGetContentText_Null( ) {
+ global $wgContentHandlerTextFallback;
+
+ $content = null;
+
+ $wgContentHandlerTextFallback = 'fail';
+ $text = ContentHandler::getContentText( $content );
+ $this->assertEquals( '', $text );
+
+ $wgContentHandlerTextFallback = 'serialize';
+ $text = ContentHandler::getContentText( $content );
+ $this->assertEquals( '', $text );
+
+ $wgContentHandlerTextFallback = 'ignore';
+ $text = ContentHandler::getContentText( $content );
+ $this->assertEquals( '', $text );
+ }
+
+ public function testGetContentText_TextContent( ) {
+ global $wgContentHandlerTextFallback;
+
+ $content = new WikitextContent( "hello world" );
+
+ $wgContentHandlerTextFallback = 'fail';
+ $text = ContentHandler::getContentText( $content );
+ $this->assertEquals( $content->getNativeData(), $text );
+
+ $wgContentHandlerTextFallback = 'serialize';
+ $text = ContentHandler::getContentText( $content );
+ $this->assertEquals( $content->serialize(), $text );
+
+ $wgContentHandlerTextFallback = 'ignore';
+ $text = ContentHandler::getContentText( $content );
+ $this->assertEquals( $content->getNativeData(), $text );
+ }
+
+ public function testGetContentText_NonTextContent( ) {
+ global $wgContentHandlerTextFallback;
+
+ $content = new DummyContentForTesting( "hello world" );
+
+ $wgContentHandlerTextFallback = 'fail';
+
+ try {
+ $text = ContentHandler::getContentText( $content );
+
+ $this->fail( "ContentHandler::getContentText should have thrown an exception for non-text Content object" );
+ } catch (MWException $ex) {
+ // as expected
+ }
+
+ $wgContentHandlerTextFallback = 'serialize';
+ $text = ContentHandler::getContentText( $content );
+ $this->assertEquals( $content->serialize(), $text );
+
+ $wgContentHandlerTextFallback = 'ignore';
+ $text = ContentHandler::getContentText( $content );
+ $this->assertNull( $text );
+ }
+
+ #public static function makeContent( $text, Title $title, $modelId = null, $format = null )
+
+ public function dataMakeContent() {
+ //NOTE: assume the Help namespace defaults to wikitext content
+ return array(
+ array( 'hallo', 'Help:Test', null, null, CONTENT_MODEL_WIKITEXT, 'hallo', false ),
+ array( 'hallo', 'MediaWiki:Test.js', null, null, CONTENT_MODEL_JAVASCRIPT, 'hallo', false ),
+ array( serialize('hallo'), 'Dummy:Test', null, null, "testing", 'hallo', false ),
+
+ array( 'hallo', 'Help:Test', null, CONTENT_FORMAT_WIKITEXT, CONTENT_MODEL_WIKITEXT, 'hallo', false ),
+ array( 'hallo', 'MediaWiki:Test.js', null, CONTENT_FORMAT_JAVASCRIPT, CONTENT_MODEL_JAVASCRIPT, 'hallo', false ),
+ array( serialize('hallo'), 'Dummy:Test', null, "testing", "testing", 'hallo', false ),
+
+ array( 'hallo', 'Help:Test', CONTENT_MODEL_CSS, null, CONTENT_MODEL_CSS, 'hallo', false ),
+ array( 'hallo', 'MediaWiki:Test.js', CONTENT_MODEL_CSS, null, CONTENT_MODEL_CSS, 'hallo', false ),
+ array( serialize('hallo'), 'Dummy:Test', CONTENT_MODEL_CSS, null, CONTENT_MODEL_CSS, serialize('hallo'), false ),
+
+ array( 'hallo', 'Help:Test', CONTENT_MODEL_WIKITEXT, "testing", null, null, true ),
+ array( 'hallo', 'MediaWiki:Test.js', CONTENT_MODEL_CSS, "testing", null, null, true ),
+ array( 'hallo', 'Dummy:Test', CONTENT_MODEL_JAVASCRIPT, "testing", null, null, true ),
+ );
+ }
+
+ /**
+ * @dataProvider dataMakeContent
+ */
+ public function testMakeContent( $data, $title, $modelId, $format, $expectedModelId, $expectedNativeData, $shouldFail ) {
+ global $wgExtraNamespaces, $wgNamespaceContentModels, $wgContentHandlers;
+
+ $title = Title::newFromText( $title );
+
+ try {
+ $content = ContentHandler::makeContent( $data, $title, $modelId, $format );
+
+ if ( $shouldFail ) $this->fail( "ContentHandler::makeContent should have failed!" );
+
+ $this->assertEquals( $expectedModelId, $content->getModel(), 'bad model id' );
+ $this->assertEquals( $expectedNativeData, $content->getNativeData(), 'bads native data' );
+ } catch ( MWException $ex ) {
+ if ( !$shouldFail ) $this->fail( "ContentHandler::makeContent failed unexpectedly: " . $ex->getMessage() );
+ else $this->assertTrue( true ); // dummy, so we don't get the "test did not perform any assertions" message.
+ }
+
+ }
+
+ public function testSupportsSections() {
+ $this->markTestIncomplete( "not yet implemented" );
+ }
+
+ public function testRunLegacyHooks() {
+ Hooks::register( 'testRunLegacyHooks', __CLASS__ . '::dummyHookHandler' );
+
+ $content = new WikitextContent( 'test text' );
+ $ok = ContentHandler::runLegacyHooks( 'testRunLegacyHooks', array( 'foo', &$content, 'bar' ), false );
+
+ $this->assertTrue( $ok, "runLegacyHooks should have returned true" );
+ $this->assertEquals( "TEST TEXT", $content->getNativeData() );
+ }
+
+ public static function dummyHookHandler( $foo, &$text, $bar ) {
+ if ( $text === null || $text === false ) {
+ return false;
+ }
+
+ $text = strtoupper( $text );
+
+ return true;
+ }
+}
+
+class DummyContentHandlerForTesting extends ContentHandler {
+
+ public function __construct( $dataModel ) {
+ parent::__construct( $dataModel, array( "testing" ) );
+ }
+
+ /**
+ * Serializes Content object of the type supported by this ContentHandler.
+ *
+ * @param Content $content the Content object to serialize
+ * @param null $format the desired serialization format
+ * @return String serialized form of the content
+ */
+ public function serializeContent( Content $content, $format = null )
+ {
+ return $content->serialize();
+ }
+
+ /**
+ * Unserializes a Content object of the type supported by this ContentHandler.
+ *
+ * @param $blob String serialized form of the content
+ * @param null $format the format used for serialization
+ * @return Content the Content object created by deserializing $blob
+ */
+ public function unserializeContent( $blob, $format = null )
+ {
+ $d = unserialize( $blob );
+ return new DummyContentForTesting( $d );
+ }
+
+ /**
+ * Creates an empty Content object of the type supported by this ContentHandler.
+ *
+ */
+ public function makeEmptyContent()
+ {
+ return new DummyContentForTesting( '' );
+ }
+}
+
+class DummyContentForTesting extends AbstractContent {
+
+ public function __construct( $data ) {
+ parent::__construct( "testing" );
+
+ $this->data = $data;
+ }
+
+ public function serialize( $format = null ) {
+ return serialize( $this->data );
+ }
+
+ /**
+ * @return String a string representing the content in a way useful for building a full text search index.
+ * If no useful representation exists, this method returns an empty string.
+ */
+ public function getTextForSearchIndex() {
+ return '';
+ }
+
+ /**
+ * @return String the wikitext to include when another page includes this content, or false if the content is not
+ * includable in a wikitext page.
+ */
+ public function getWikitextForTransclusion() {
+ return false;
+ }
+
+ /**
+ * Returns a textual representation of the content suitable for use in edit summaries and log messages.
+ *
+ * @param int $maxlength maximum length of the summary text
+ * @return String the summary text
+ */
+ public function getTextForSummary( $maxlength = 250 ) {
+ return '';
+ }
+
+ /**
+ * Returns native represenation of the data. Interpretation depends on the data model used,
+ * as given by getDataModel().
+ *
+ * @return mixed the native representation of the content. Could be a string, a nested array
+ * structure, an object, a binary blob... anything, really.
+ */
+ public function getNativeData()
+ {
+ return $this->data;
+ }
+
+ /**
+ * returns the content's nominal size in bogo-bytes.
+ *
+ * @return int
+ */
+ public function getSize() {
+ return strlen( $this->data );
+ }
+
+ /**
+ * Return a copy of this Content object. The following must be true for the object returned
+ * if $copy = $original->copy()
+ *
+ * * get_class($original) === get_class($copy)
+ * * $original->getModel() === $copy->getModel()
+ * * $original->equals( $copy )
+ *
+ * If and only if the Content object is imutable, the copy() method can and should
+ * return $this. That is, $copy === $original may be true, but only for imutable content
+ * objects.
+ *
+ * @return Content. A copy of this object
+ */
+ public function copy() {
+ return $this;
+ }
+
+ /**
+ * Returns true if this content is countable as a "real" wiki page, provided
+ * that it's also in a countable location (e.g. a current revision in the main namespace).
+ *
+ * @param $hasLinks Bool: if it is known whether this content contains links, provide this information here,
+ * to avoid redundant parsing to find out.
+ * @return boolean
+ */
+ public function isCountable( $hasLinks = null ) {
+ return false;
+ }
+
+ /**
+ * @param Title $title
+ * @param null $revId
+ * @param null|ParserOptions $options
+ * @param Boolean $generateHtml whether to generate Html (default: true). If false,
+ * the result of calling getText() on the ParserOutput object returned by
+ * this method is undefined.
+ *
+ * @return ParserOutput
+ */
+ public function getParserOutput( Title $title, $revId = null, ParserOptions $options = NULL, $generateHtml = true ) {
+ return new ParserOutput( $this->getNativeData() );
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * @group ContentHandler
+ *
+ * @group Database
+ * ^--- needed, because we do need the database to test link updates
+ */
+class CssContentTest extends JavascriptContentTest {
+
+ public function newContent( $text ) {
+ return new CssContent( $text );
+ }
+
+
+ public function dataGetParserOutput() {
+ return array(
+ array("MediaWiki:Test.css", null, "hello <world>\n",
+ "<pre class=\"mw-code mw-css\" dir=\"ltr\">\nhello <world>\n\n</pre>"),
+ // @todo: more...?
+ );
+ }
+
+
+ # =================================================================================================================
+
+ public function testGetModel() {
+ $content = $this->newContent( "hello world." );
+
+ $this->assertEquals( CONTENT_MODEL_CSS, $content->getModel() );
+ }
+
+ public function testGetContentHandler() {
+ $content = $this->newContent( "hello world." );
+
+ $this->assertEquals( CONTENT_MODEL_CSS, $content->getContentHandler()->getModelID() );
+ }
+
+ public function dataEquals( ) {
+ return array(
+ array( new CssContent( "hallo" ), null, false ),
+ array( new CssContent( "hallo" ), new CssContent( "hallo" ), true ),
+ array( new CssContent( "hallo" ), new WikitextContent( "hallo" ), false ),
+ array( new CssContent( "hallo" ), new CssContent( "HALLO" ), false ),
+ );
+ }
+
+}
<?php
class DiffHistoryBlobTest extends MediaWikiTestCase {
- function setUp() {
+ protected function setUp() {
if ( !extension_loaded( 'xdiff' ) ) {
$this->markTestSkipped( 'The xdiff extension is not available' );
return;
"Hash of " . addcslashes( $input, "\0..\37!@\@\177..\377" ) );
}
- function provideXdiffAdler32() {
+ public static function provideXdiffAdler32() {
return array(
array( '', 'Empty string' ),
array( "\0", 'Null' ),
class EditPageTest extends MediaWikiTestCase {
/**
- * @dataProvider dataExtractSectionTitle
+ * @dataProvider provideExtractSectionTitle
*/
function testExtractSectionTitle( $section, $title ) {
$extracted = EditPage::extractSectionTitle( $section );
$this->assertEquals( $title, $extracted );
}
- function dataExtractSectionTitle() {
+ public static function provideExtractSectionTitle() {
return array(
array(
"== Test ==\n\nJust a test section.",
*/
class ExternalStoreTest extends MediaWikiTestCase {
- private $saved_wgExternalStores;
- function setUp() {
- global $wgExternalStores;
- $this->saved_wgExternalStores = $wgExternalStores ;
- }
+ function testExternalFetchFromURL() {
+ $this->setMwGlobals( 'wgExternalStores', false );
- function tearDown() {
- global $wgExternalStores;
- $wgExternalStores = $this->saved_wgExternalStores ;
- }
+ $this->assertFalse(
+ ExternalStore::fetchFromURL( 'FOO://cluster1/200' ),
+ 'Deny if wgExternalStores is not set to a non-empty array'
+ );
- function testExternalStoreDoesNotFetchIncorrectURL() {
- global $wgExternalStores;
- $wgExternalStores = true;
+ $this->setMwGlobals( 'wgExternalStores', array( 'FOO' ) );
+ $this->assertEquals(
+ ExternalStore::fetchFromURL( 'FOO://cluster1/200' ),
+ 'Hello',
+ 'Allow FOO://cluster1/200'
+ );
+ $this->assertEquals(
+ ExternalStore::fetchFromURL( 'FOO://cluster1/300/0' ),
+ 'Hello',
+ 'Allow FOO://cluster1/300/0'
+ );
# Assertions for r68900
$this->assertFalse(
- ExternalStore::fetchFromURL( 'http://' ) );
+ ExternalStore::fetchFromURL( 'ftp.example.org' ),
+ 'Deny domain ftp.example.org'
+ );
$this->assertFalse(
- ExternalStore::fetchFromURL( 'ftp.wikimedia.org' ) );
+ ExternalStore::fetchFromURL( '/example.txt' ),
+ 'Deny path /example.txt'
+ );
$this->assertFalse(
- ExternalStore::fetchFromURL( '/super.txt' ) );
+ ExternalStore::fetchFromURL( 'http://' ),
+ 'Deny protocol http://'
+ );
}
}
+class ExternalStoreFOO {
+
+ protected $data = array(
+ 'cluster1' => array(
+ '200' => 'Hello',
+ '300' => array(
+ 'Hello', 'World',
+ ),
+ ),
+ );
+
+ /**
+ * Fetch data from given URL
+ * @param $url String: an url of the form FOO://cluster/id or FOO://cluster/id/itemid.
+ * @return mixed
+ */
+ function fetchFromURL( $url ) {
+ // Based on ExternalStoreDB
+ $path = explode( '/', $url );
+ $cluster = $path[2];
+ $id = $path[3];
+ if ( isset( $path[4] ) ) {
+ $itemID = $path[4];
+ } else {
+ $itemID = false;
+ }
+
+ if ( !isset( $this->data[$cluster][$id] ) ) {
+ return null;
+ }
+
+ if ( $itemID !== false && is_array( $this->data[$cluster][$id] ) && isset( $this->data[$cluster][$id][$itemID] ) ) {
+ return $this->data[$cluster][$id][$itemID];
+ }
+
+ return $this->data[$cluster][$id];
+ }
+}
\ No newline at end of file
*/
class ExtraParserTest extends MediaWikiTestCase {
- function setUp() {
- global $wgMemc;
- global $wgContLang;
- global $wgShowDBErrorBacktrace;
- global $wgLanguageCode;
- global $wgAlwaysUseTidy;
+ protected function setUp() {
+ parent::setUp();
- $wgShowDBErrorBacktrace = true;
- $wgLanguageCode = 'en';
- $wgContLang = new Language( 'en' );
- $wgMemc = new EmptyBagOStuff;
- $wgAlwaysUseTidy = false;
+ $this->setMwGlobals( array(
+ 'wgShowDBErrorBacktrace' => true,
+ 'wgLanguageCode' => 'en',
+ 'wgContLang' => Language::factory( 'en' ),
+ 'wgLang' => Language::factory( 'en' ),
+ 'wgMemc' => new EmptyBagOStuff,
+ 'wgAlwaysUseTidy' => false,
+ 'wgCleanSignatures' => true,
+ ) );
$this->options = new ParserOptions;
$this->options->setTemplateCallback( array( __CLASS__, 'statelessFetchTemplate' ) );
// Bug 8689 - Long numeric lines kill the parser
function testBug8689() {
- global $wgLang;
global $wgUser;
$longLine = '1.' . str_repeat( '1234567890', 100000 ) . "\n";
-
- if ( $wgLang === null ) $wgLang = new Language;
$t = Title::newFromText( 'Unit test' );
$options = ParserOptions::newFromUser( $wgUser );
* cleanSig() makes all templates substs and removes tildes
*/
function testCleanSig() {
- global $wgCleanSignatures;
- $oldCleanSignature = $wgCleanSignatures;
- $wgCleanSignatures = true;
-
$title = Title::newFromText( __FUNCTION__ );
$outputText = $this->parser->cleanSig( "{{Foo}} ~~~~" );
-
- $wgCleanSignatures = $oldCleanSignature;
$this->assertEquals( "{{SUBST:Foo}} ", $outputText );
}
*/
function testCleanSigDisabled() {
global $wgCleanSignatures;
- $oldCleanSignature = $wgCleanSignatures;
$wgCleanSignatures = false;
$title = Title::newFromText( __FUNCTION__ );
$outputText = $this->parser->cleanSig( "{{Foo}} ~~~~" );
-
- $wgCleanSignatures = $oldCleanSignature;
$this->assertEquals( "{{Foo}} ~~~~", $outputText );
}
$this->assertEquals( Parser::cleanSigInSig( $in), $out );
}
- function provideStringsForCleanSigInSig() {
+ public static function provideStringsForCleanSigInSig() {
return array(
array( "{{Foo}} ~~~~", "{{Foo}} " ),
array( "~~~", "" ),
class FauxResponseTest extends MediaWikiTestCase {
var $response;
- function setUp() {
+ protected function setUp() {
$this->response = new FauxResponse;
}
<?php
class GlobalTest extends MediaWikiTestCase {
- function setUp() {
- global $wgReadOnlyFile, $wgUrlProtocols;
- $this->originals['wgReadOnlyFile'] = $wgReadOnlyFile;
- $this->originals['wgUrlProtocols'] = $wgUrlProtocols;
- $wgReadOnlyFile = tempnam( wfTempDir(), "mwtest_readonly" );
- $wgUrlProtocols[] = 'file://';
- unlink( $wgReadOnlyFile );
+ protected function setUp() {
+ parent::setUp();
+
+ $readOnlyFile = tempnam( wfTempDir(), "mwtest_readonly" );
+ unlink( $readOnlyFile );
+
+ $this->setMwGlobals( array(
+ 'wgReadOnlyFile' => $readOnlyFile,
+ 'wgUrlProtocols' => array(
+ 'http://',
+ 'https://',
+ 'mailto:',
+ '//',
+ 'file://', # Non-default
+ ),
+ ) );
}
- function tearDown() {
- global $wgReadOnlyFile, $wgUrlProtocols;
+ protected function tearDown() {
+ global $wgReadOnlyFile;
+
if ( file_exists( $wgReadOnlyFile ) ) {
unlink( $wgReadOnlyFile );
}
- $wgReadOnlyFile = $this->originals['wgReadOnlyFile'];
- $wgUrlProtocols = $this->originals['wgUrlProtocols'];
+
+ parent::tearDown();
}
/** @dataProvider provideForWfArrayDiff2 */
}
// @todo Provide more tests
- public function provideForWfArrayDiff2() {
+ public static function provideForWfArrayDiff2() {
// $a $b $expected
return array(
array(
$this->assertTrue( $end > $start, "Time is running backwards!" );
}
- function dataArrayToCGI() {
+ public static function provideArrayToCGI() {
return array(
array( array(), '' ), // empty
array( array( 'foo' => 'bar' ), 'foo=bar' ), // string test
}
/**
- * @dataProvider dataArrayToCGI
+ * @dataProvider provideArrayToCGI
*/
function testArrayToCGI( $array, $result ) {
$this->assertEquals( $result, wfArrayToCGI( $array ) );
array( 'foo' => 'bar', 'baz' => 'overridden value' ) ) );
}
- function dataCgiToArray() {
+ public static function provideCgiToArray() {
return array(
array( '', array() ), // empty
array( 'foo=bar', array( 'foo' => 'bar' ) ), // string
}
/**
- * @dataProvider dataCgiToArray
+ * @dataProvider provideCgiToArray
*/
function testCgiToArray( $cgi, $result ) {
$this->assertEquals( $result, wfCgiToArray( $cgi ) );
}
- function dataCgiRoundTrip() {
+ public static function provideCgiRoundTrip() {
return array(
array( '' ),
array( 'foo=bar' ),
}
/**
- * @dataProvider dataCgiRoundTrip
+ * @dataProvider provideCgiRoundTrip
*/
function testCgiRoundTrip( $cgi ) {
$this->assertEquals( $cgi, wfArrayToCGI( wfCgiToArray( $cgi ) ) );
}
}
-
-
+
+
function testDebugFunctionTest() {
-
+
global $wgDebugLogFile, $wgDebugTimestamps;
-
+
$old_log_file = $wgDebugLogFile;
$wgDebugLogFile = tempnam( wfTempDir(), 'mw-' );
- # @todo FIXME: This setting should be tested
+ # @todo FIXME: $wgDebugTimestamps should be tested
+ $old_wgDebugTimestamps = $wgDebugTimestamps;
$wgDebugTimestamps = false;
-
-
-
+
+
wfDebug( "This is a normal string" );
$this->assertEquals( "This is a normal string", file_get_contents( $wgDebugLogFile ) );
unlink( $wgDebugLogFile );
-
-
+
wfDebug( "This is nöt an ASCII string" );
$this->assertEquals( "This is nöt an ASCII string", file_get_contents( $wgDebugLogFile ) );
unlink( $wgDebugLogFile );
-
-
+
+
wfDebug( "\00305This has böth UTF and control chars\003" );
$this->assertEquals( " 05This has böth UTF and control chars ", file_get_contents( $wgDebugLogFile ) );
unlink( $wgDebugLogFile );
wfDebugMem();
$this->assertGreaterThan( 5000, preg_replace( '/\D/', '', file_get_contents( $wgDebugLogFile ) ) );
unlink( $wgDebugLogFile );
-
+
wfDebugMem(true);
$this->assertGreaterThan( 5000000, preg_replace( '/\D/', '', file_get_contents( $wgDebugLogFile ) ) );
unlink( $wgDebugLogFile );
-
-
-
+
+
$wgDebugLogFile = $old_log_file;
-
+ $wgDebugTimestamps = $old_wgDebugTimestamps;
}
-
+
function testClientAcceptsGzipTest() {
$settings = array(
}
/** array( shorthand, expected integer ) */
- public function provideShorthand() {
+ public static function provideShorthand() {
return array(
# Null, empty ...
array( '', -1),
*
* @return array
*/
- public function provideURLParts() {
+ public static function provideURLParts() {
$schemes = array(
'' => array(),
'//' => array(
*
* @return array
*/
- public function provideExpandableUrls() {
+ public static function provideExpandableUrls() {
$modes = array( 'http', 'https' );
$servers = array(
'http' => 'http://example.com',
*
* @return array
*/
- public function providePaths() {
+ public static function providePaths() {
return array(
array( '/a/b/c/./../../g', '/a/g' ),
array( 'mid/content=5/../6', 'mid/6' ),
* If you want to add other HTTP server name, you will have to add a new
* testing method much like the testEncodingUrlWith() method above.
*/
- public function provideURLS() {
+ public static function provideURLS() {
return array(
### RFC 1738 chars
// + is not safe
$this->assertEquals( 'bah', $foo, 'Standard static method' );
$foo = 'Foo';
+
+ Hooks::clear( 'MediaWikiHooksTest001' );
+ }
+
+ public function testNewStyleHookInteraction() {
+ global $wgHooks;
+
+ $a = new NothingClass();
+ $b = new NothingClass();
+
+ // make sure to start with a clean slate
+ Hooks::clear( 'MediaWikiHooksTest001' );
+ unset( $wgHooks['MediaWikiHooksTest001'] );
+
+ $wgHooks['MediaWikiHooksTest001'][] = $a;
+ $this->assertTrue( Hooks::isRegistered( 'MediaWikiHooksTest001' ), 'Hook registered via $wgHooks should be noticed by Hooks::isRegistered' );
+
+ Hooks::register( 'MediaWikiHooksTest001', $b );
+ $this->assertEquals( 2, count( Hooks::getHandlers( 'MediaWikiHooksTest001' ) ), 'Hooks::getHandlers() should return hooks registered via wgHooks as well as Hooks::register' );
+
+ $foo = 'quux';
+ $bar = 'qaax';
+
+ Hooks::run( 'MediaWikiHooksTest001', array( &$foo, &$bar ) );
+ $this->assertEquals( 1, $a->calls, 'Hooks::run() should run hooks registered via wgHooks as well as Hooks::register' );
+ $this->assertEquals( 1, $b->calls, 'Hooks::run() should run hooks registered via wgHooks as well as Hooks::register' );
+
+ // clean up
+ Hooks::clear( 'MediaWikiHooksTest001' );
+ unset( $wgHooks['MediaWikiHooksTest001'] );
}
}
class NothingClass {
+ public $calls = 0;
+
static public function someStatic( &$foo, &$bar ) {
$foo = 'bah';
return true;
}
public function someNonStatic( &$foo, &$bar ) {
+ $this->calls++;
$foo = 'fOO';
$bar = 'bAR';
return true;
}
public function onMediaWikiHooksTest001( &$foo, &$bar ) {
+ $this->calls++;
$foo = 'foo';
return true;
}
public function someNonStaticWithData( $foo, &$bar ) {
+ $this->calls++;
$bar = $foo;
return true;
}
/** tests for includes/Html.php */
class HtmlTest extends MediaWikiTestCase {
- private static $oldLang;
- private static $oldContLang;
- private static $oldLanguageCode;
- private static $oldNamespaces;
- private static $oldHTML5;
- public function setUp() {
- global $wgLang, $wgContLang, $wgLanguageCode, $wgHtml5;
+ protected function setUp() {
+ parent::setUp();
- // Save globals
- self::$oldLang = $wgLang;
- self::$oldContLang = $wgContLang;
- self::$oldNamespaces = $wgContLang->getNamespaces();
- self::$oldLanguageCode = $wgLanguageCode;
- self::$oldHTML5 = $wgHtml5;
-
- $wgLanguageCode = 'en';
- $wgContLang = $wgLang = Language::factory( $wgLanguageCode );
+ $langCode = 'en';
+ $langObj = Language::factory( $langCode );
// Hardcode namespaces during test runs,
// so that html output based on existing namespaces
// can be properly evaluated.
- $wgContLang->setNamespaces( array(
+ $langObj->setNamespaces( array(
-2 => 'Media',
-1 => 'Special',
0 => '',
100 => 'Custom',
101 => 'Custom_talk',
) );
+
+ $this->setMwGlobals( array(
+ 'wgLanguageCode' => $langCode,
+ 'wgContLang' => $langObj,
+ 'wgLang' => $langObj,
+ 'wgHtml5' => true,
+ 'wgWellFormedXml' => false,
+ ) );
}
- public function tearDown() {
- global $wgLang, $wgContLang, $wgLanguageCode, $wgHtml5;
+ public function testElementBasics() {
+ global $wgWellFormedXml;
- // Restore globals
- $wgContLang->setNamespaces( self::$oldNamespaces );
- $wgLang = self::$oldLang;
- $wgContLang = self::$oldContLang;
- $wgLanguageCode = self::$oldLanguageCode;
- $wgHtml5 = self::$oldHTML5;
- }
+ $this->assertEquals(
+ '<img>',
+ Html::element( 'img', null, '' ),
+ 'No close tag for short-tag elements'
+ );
- /**
- * Wrapper to easily set $wgHtml5 = true.
- * Original value will be restored after test completion.
- * @todo Move to MediaWikiTestCase
- */
- public function enableHTML5() {
- global $wgHtml5;
- $wgHtml5 = true;
- }
- /**
- * Wrapper to easily set $wgHtml5 = false
- * Original value will be restored after test completion.
- * @todo Move to MediaWikiTestCase
- */
- public function disableHTML5() {
- global $wgHtml5;
- $wgHtml5 = false;
+ $this->assertEquals(
+ '<element></element>',
+ Html::element( 'element', null, null ),
+ 'Close tag for empty element (null, null)'
+ );
+
+ $this->assertEquals(
+ '<element></element>',
+ Html::element( 'element', array(), '' ),
+ 'Close tag for empty element (array, string)'
+ );
+
+ $wgWellFormedXml = true;
+
+ $this->assertEquals(
+ '<img />',
+ Html::element( 'img', null, '' ),
+ 'Self-closing tag for short-tag elements (wgWellFormedXml = true)'
+ );
}
public function testExpandAttributesSkipsNullAndFalse() {
### EMPTY ########
- $this->AssertEmpty(
+ $this->assertEmpty(
Html::expandAttributes( array( 'foo' => null ) ),
'skip keys with null value'
);
- $this->AssertEmpty(
+ $this->assertEmpty(
Html::expandAttributes( array( 'foo' => false ) ),
'skip keys with false value'
);
- $this->AssertNotEmpty(
+ $this->assertNotEmpty(
Html::expandAttributes( array( 'foo' => '' ) ),
'keep keys with an empty string'
);
}
public function testExpandAttributesForBooleans() {
- global $wgHtml5;
- $this->AssertEquals(
+ global $wgHtml5, $wgWellFormedXml;
+
+ $this->assertEquals(
'',
Html::expandAttributes( array( 'selected' => false ) ),
'Boolean attributes do not generates output when value is false'
);
- $this->AssertEquals(
+ $this->assertEquals(
'',
Html::expandAttributes( array( 'selected' => null ) ),
'Boolean attributes do not generates output when value is null'
);
- $this->AssertEquals(
- $wgHtml5 ? ' selected=""' : ' selected="selected"',
+ $this->assertEquals(
+ ' selected',
Html::expandAttributes( array( 'selected' => true ) ),
- 'Boolean attributes skip value output'
+ 'Boolean attributes have no value when value is true'
);
- $this->AssertEquals(
- $wgHtml5 ? ' selected=""' : ' selected="selected"',
+ $this->assertEquals(
+ ' selected',
Html::expandAttributes( array( 'selected' ) ),
- 'Boolean attributes (ex: selected) do not need a value'
+ 'Boolean attributes have no value when value is true (passed as numerical array)'
+ );
+
+ $wgWellFormedXml = true;
+
+ $this->assertEquals(
+ ' selected=""',
+ Html::expandAttributes( array( 'selected' => true ) ),
+ 'Boolean attributes have empty string value when value is true (wgWellFormedXml)'
+ );
+
+ $wgHtml5 = false;
+
+ $this->assertEquals(
+ ' selected="selected"',
+ Html::expandAttributes( array( 'selected' => true ) ),
+ 'Boolean attributes have their key as value when value is true (wgWellFormedXml, wgHTML5 = false)'
);
}
* Please note it output a string prefixed with a space!
*/
public function testExpandAttributesVariousExpansions() {
+ global $wgWellFormedXml;
+
### NOT EMPTY ####
- $this->AssertEquals(
+ $this->assertEquals(
+ ' empty_string=""',
+ Html::expandAttributes( array( 'empty_string' => '' ) ),
+ 'Empty string is always quoted'
+ );
+ $this->assertEquals(
+ ' key=value',
+ Html::expandAttributes( array( 'key' => 'value' ) ),
+ 'Simple string value needs no quotes'
+ );
+ $this->assertEquals(
+ ' one=1',
+ Html::expandAttributes( array( 'one' => 1 ) ),
+ 'Number 1 value needs no quotes'
+ );
+ $this->assertEquals(
+ ' zero=0',
+ Html::expandAttributes( array( 'zero' => 0 ) ),
+ 'Number 0 value needs no quotes'
+ );
+
+ $wgWellFormedXml = true;
+
+ $this->assertEquals(
' empty_string=""',
Html::expandAttributes( array( 'empty_string' => '' ) ),
- 'Value with an empty string'
+ 'Attribtue values are always quoted (wgWellFormedXml): Empty string'
);
- $this->AssertEquals(
+ $this->assertEquals(
' key="value"',
Html::expandAttributes( array( 'key' => 'value' ) ),
- 'Value is a string'
+ 'Attribtue values are always quoted (wgWellFormedXml): Simple string'
);
- $this->AssertEquals(
+ $this->assertEquals(
' one="1"',
Html::expandAttributes( array( 'one' => 1 ) ),
- 'Value is a numeric one'
+ 'Attribtue values are always quoted (wgWellFormedXml): Number 1'
);
- $this->AssertEquals(
+ $this->assertEquals(
' zero="0"',
Html::expandAttributes( array( 'zero' => 0 ) ),
- 'Value is a numeric zero'
+ 'Attribtue values are always quoted (wgWellFormedXml): Number 0'
);
}
*/
public function testExpandAttributesListValueAttributes() {
### STRING VALUES
- $this->AssertEquals(
+ $this->assertEquals(
' class="redundant spaces here"',
Html::expandAttributes( array( 'class' => ' redundant spaces here ' ) ),
'Normalization should strip redundant spaces'
);
- $this->AssertEquals(
+ $this->assertEquals(
' class="foo bar"',
Html::expandAttributes( array( 'class' => 'foo bar foo bar bar' ) ),
'Normalization should remove duplicates in string-lists'
);
### "EMPTY" ARRAY VALUES
- $this->AssertEquals(
+ $this->assertEquals(
' class=""',
Html::expandAttributes( array( 'class' => array() ) ),
'Value with an empty array'
);
- $this->AssertEquals(
+ $this->assertEquals(
' class=""',
Html::expandAttributes( array( 'class' => array( null, '', ' ', ' ' ) ) ),
'Array with null, empty string and spaces'
);
### NON-EMPTY ARRAY VALUES
- $this->AssertEquals(
+ $this->assertEquals(
' class="foo bar"',
Html::expandAttributes( array( 'class' => array(
'foo',
) ) ),
'Normalization should remove duplicates in the array'
);
- $this->AssertEquals(
+ $this->assertEquals(
' class="foo bar"',
Html::expandAttributes( array( 'class' => array(
'foo bar',
function testNamespaceSelector() {
$this->assertEquals(
- '<select id="namespace" name="namespace">' . "\n" .
-'<option value="0">(Main)</option>' . "\n" .
-'<option value="1">Talk</option>' . "\n" .
-'<option value="2">User</option>' . "\n" .
-'<option value="3">User talk</option>' . "\n" .
-'<option value="4">MyWiki</option>' . "\n" .
-'<option value="5">MyWiki Talk</option>' . "\n" .
-'<option value="6">File</option>' . "\n" .
-'<option value="7">File talk</option>' . "\n" .
-'<option value="8">MediaWiki</option>' . "\n" .
-'<option value="9">MediaWiki talk</option>' . "\n" .
-'<option value="10">Template</option>' . "\n" .
-'<option value="11">Template talk</option>' . "\n" .
-'<option value="14">Category</option>' . "\n" .
-'<option value="15">Category talk</option>' . "\n" .
-'<option value="100">Custom</option>' . "\n" .
-'<option value="101">Custom talk</option>' . "\n" .
+ '<select id=namespace name=namespace>' . "\n" .
+'<option value=0>(Main)</option>' . "\n" .
+'<option value=1>Talk</option>' . "\n" .
+'<option value=2>User</option>' . "\n" .
+'<option value=3>User talk</option>' . "\n" .
+'<option value=4>MyWiki</option>' . "\n" .
+'<option value=5>MyWiki Talk</option>' . "\n" .
+'<option value=6>File</option>' . "\n" .
+'<option value=7>File talk</option>' . "\n" .
+'<option value=8>MediaWiki</option>' . "\n" .
+'<option value=9>MediaWiki talk</option>' . "\n" .
+'<option value=10>Template</option>' . "\n" .
+'<option value=11>Template talk</option>' . "\n" .
+'<option value=14>Category</option>' . "\n" .
+'<option value=15>Category talk</option>' . "\n" .
+'<option value=100>Custom</option>' . "\n" .
+'<option value=101>Custom talk</option>' . "\n" .
'</select>',
Html::namespaceSelector(),
'Basic namespace selector without custom options'
);
$this->assertEquals(
- '<label for="mw-test-namespace">Select a namespace:</label> ' .
-'<select id="mw-test-namespace" name="wpNamespace">' . "\n" .
-'<option value="all">all</option>' . "\n" .
-'<option value="0">(Main)</option>' . "\n" .
-'<option value="1">Talk</option>' . "\n" .
-'<option value="2" selected="">User</option>' . "\n" .
-'<option value="3">User talk</option>' . "\n" .
-'<option value="4">MyWiki</option>' . "\n" .
-'<option value="5">MyWiki Talk</option>' . "\n" .
-'<option value="6">File</option>' . "\n" .
-'<option value="7">File talk</option>' . "\n" .
-'<option value="8">MediaWiki</option>' . "\n" .
-'<option value="9">MediaWiki talk</option>' . "\n" .
-'<option value="10">Template</option>' . "\n" .
-'<option value="11">Template talk</option>' . "\n" .
-'<option value="14">Category</option>' . "\n" .
-'<option value="15">Category talk</option>' . "\n" .
-'<option value="100">Custom</option>' . "\n" .
-'<option value="101">Custom talk</option>' . "\n" .
+ '<label for=mw-test-namespace>Select a namespace:</label> ' .
+'<select id=mw-test-namespace name=wpNamespace>' . "\n" .
+'<option value=all>all</option>' . "\n" .
+'<option value=0>(Main)</option>' . "\n" .
+'<option value=1>Talk</option>' . "\n" .
+'<option value=2 selected>User</option>' . "\n" .
+'<option value=3>User talk</option>' . "\n" .
+'<option value=4>MyWiki</option>' . "\n" .
+'<option value=5>MyWiki Talk</option>' . "\n" .
+'<option value=6>File</option>' . "\n" .
+'<option value=7>File talk</option>' . "\n" .
+'<option value=8>MediaWiki</option>' . "\n" .
+'<option value=9>MediaWiki talk</option>' . "\n" .
+'<option value=10>Template</option>' . "\n" .
+'<option value=11>Template talk</option>' . "\n" .
+'<option value=14>Category</option>' . "\n" .
+'<option value=15>Category talk</option>' . "\n" .
+'<option value=100>Custom</option>' . "\n" .
+'<option value=101>Custom talk</option>' . "\n" .
'</select>',
Html::namespaceSelector(
array( 'selected' => '2', 'all' => 'all', 'label' => 'Select a namespace:' ),
);
$this->assertEquals(
- '<label for="namespace">Select a namespace:</label> ' .
-'<select id="namespace" name="namespace">' . "\n" .
-'<option value="0">(Main)</option>' . "\n" .
-'<option value="1">Talk</option>' . "\n" .
-'<option value="2">User</option>' . "\n" .
-'<option value="3">User talk</option>' . "\n" .
-'<option value="4">MyWiki</option>' . "\n" .
-'<option value="5">MyWiki Talk</option>' . "\n" .
-'<option value="6">File</option>' . "\n" .
-'<option value="7">File talk</option>' . "\n" .
-'<option value="8">MediaWiki</option>' . "\n" .
-'<option value="9">MediaWiki talk</option>' . "\n" .
-'<option value="10">Template</option>' . "\n" .
-'<option value="11">Template talk</option>' . "\n" .
-'<option value="14">Category</option>' . "\n" .
-'<option value="15">Category talk</option>' . "\n" .
-'<option value="100">Custom</option>' . "\n" .
-'<option value="101">Custom talk</option>' . "\n" .
+ '<label for=namespace>Select a namespace:</label> ' .
+'<select id=namespace name=namespace>' . "\n" .
+'<option value=0>(Main)</option>' . "\n" .
+'<option value=1>Talk</option>' . "\n" .
+'<option value=2>User</option>' . "\n" .
+'<option value=3>User talk</option>' . "\n" .
+'<option value=4>MyWiki</option>' . "\n" .
+'<option value=5>MyWiki Talk</option>' . "\n" .
+'<option value=6>File</option>' . "\n" .
+'<option value=7>File talk</option>' . "\n" .
+'<option value=8>MediaWiki</option>' . "\n" .
+'<option value=9>MediaWiki talk</option>' . "\n" .
+'<option value=10>Template</option>' . "\n" .
+'<option value=11>Template talk</option>' . "\n" .
+'<option value=14>Category</option>' . "\n" .
+'<option value=15>Category talk</option>' . "\n" .
+'<option value=100>Custom</option>' . "\n" .
+'<option value=101>Custom talk</option>' . "\n" .
'</select>',
Html::namespaceSelector(
array( 'label' => 'Select a namespace:' )
function testCanFilterOutNamespaces() {
$this->assertEquals(
-'<select id="namespace" name="namespace">' . "\n" .
-'<option value="2">User</option>' . "\n" .
-'<option value="4">MyWiki</option>' . "\n" .
-'<option value="5">MyWiki Talk</option>' . "\n" .
-'<option value="6">File</option>' . "\n" .
-'<option value="7">File talk</option>' . "\n" .
-'<option value="8">MediaWiki</option>' . "\n" .
-'<option value="9">MediaWiki talk</option>' . "\n" .
-'<option value="10">Template</option>' . "\n" .
-'<option value="11">Template talk</option>' . "\n" .
-'<option value="14">Category</option>' . "\n" .
-'<option value="15">Category talk</option>' . "\n" .
+'<select id=namespace name=namespace>' . "\n" .
+'<option value=2>User</option>' . "\n" .
+'<option value=4>MyWiki</option>' . "\n" .
+'<option value=5>MyWiki Talk</option>' . "\n" .
+'<option value=6>File</option>' . "\n" .
+'<option value=7>File talk</option>' . "\n" .
+'<option value=8>MediaWiki</option>' . "\n" .
+'<option value=9>MediaWiki talk</option>' . "\n" .
+'<option value=10>Template</option>' . "\n" .
+'<option value=11>Template talk</option>' . "\n" .
+'<option value=14>Category</option>' . "\n" .
+'<option value=15>Category talk</option>' . "\n" .
'</select>',
Html::namespaceSelector(
array( 'exclude' => array( 0, 1, 3, 100, 101 ) )
function testCanDisableANamespaces() {
$this->assertEquals(
-'<select id="namespace" name="namespace">' . "\n" .
-'<option disabled="" value="0">(Main)</option>' . "\n" .
-'<option disabled="" value="1">Talk</option>' . "\n" .
-'<option disabled="" value="2">User</option>' . "\n" .
-'<option disabled="" value="3">User talk</option>' . "\n" .
-'<option disabled="" value="4">MyWiki</option>' . "\n" .
-'<option value="5">MyWiki Talk</option>' . "\n" .
-'<option value="6">File</option>' . "\n" .
-'<option value="7">File talk</option>' . "\n" .
-'<option value="8">MediaWiki</option>' . "\n" .
-'<option value="9">MediaWiki talk</option>' . "\n" .
-'<option value="10">Template</option>' . "\n" .
-'<option value="11">Template talk</option>' . "\n" .
-'<option value="14">Category</option>' . "\n" .
-'<option value="15">Category talk</option>' . "\n" .
-'<option value="100">Custom</option>' . "\n" .
-'<option value="101">Custom talk</option>' . "\n" .
+'<select id=namespace name=namespace>' . "\n" .
+'<option disabled value=0>(Main)</option>' . "\n" .
+'<option disabled value=1>Talk</option>' . "\n" .
+'<option disabled value=2>User</option>' . "\n" .
+'<option disabled value=3>User talk</option>' . "\n" .
+'<option disabled value=4>MyWiki</option>' . "\n" .
+'<option value=5>MyWiki Talk</option>' . "\n" .
+'<option value=6>File</option>' . "\n" .
+'<option value=7>File talk</option>' . "\n" .
+'<option value=8>MediaWiki</option>' . "\n" .
+'<option value=9>MediaWiki talk</option>' . "\n" .
+'<option value=10>Template</option>' . "\n" .
+'<option value=11>Template talk</option>' . "\n" .
+'<option value=14>Category</option>' . "\n" .
+'<option value=15>Category talk</option>' . "\n" .
+'<option value=100>Custom</option>' . "\n" .
+'<option value=101>Custom talk</option>' . "\n" .
'</select>',
Html::namespaceSelector( array(
'disable' => array( 0, 1, 2, 3, 4 )
}
/**
- * @dataProvider providesHtml5InputTypes
+ * @dataProvider provideHtml5InputTypes
*/
function testHtmlElementAcceptsNewHtml5TypesInHtml5Mode( $HTML5InputType ) {
- $this->enableHTML5();
$this->assertEquals(
- '<input type="' . $HTML5InputType . '" />',
+ '<input type=' . $HTML5InputType . '>',
Html::element( 'input', array( 'type' => $HTML5InputType ) ),
'In HTML5, HTML::element() should accept type="' . $HTML5InputType . '"'
);
* List of input element types values introduced by HTML5
* Full list at http://www.w3.org/TR/html-markup/input.html
*/
- function providesHtml5InputTypes() {
+ function provideHtml5InputTypes() {
$types = array(
'datetime',
'datetime-local',
* @cover Html::dropDefaults
* @dataProvider provideElementsWithAttributesHavingDefaultValues
*/
- function testDropDefaults( $expected, $element, $message = '' ) {
- $this->enableHTML5();
- $this->assertEquals( $expected, $element, $message );
+ function testDropDefaults( $expected, $element, $attribs, $message = '' ) {
+ $this->assertEquals( $expected, Html::element( $element, $attribs ), $message );
}
- function provideElementsWithAttributesHavingDefaultValues() {
+ public static function provideElementsWithAttributesHavingDefaultValues() {
# Use cases in a concise format:
# <expected>, <element name>, <array of attributes> [, <message>]
# Will be mapped to Html::element()
$cases = array();
### Generic cases, match $attribDefault static array
- $cases[] = array( '<area />',
+ $cases[] = array( '<area>',
'area', array( 'shape' => 'rect' )
);
'canvas', array( 'width' => 300 )
);
- $cases[] = array( '<command />',
+ $cases[] = array( '<command>',
'command', array( 'type' => 'command' )
);
'form', array( 'enctype' => 'application/x-www-form-urlencoded' )
);
- $cases[] = array( '<input />',
+ $cases[] = array( '<input>',
'input', array( 'formaction' => 'GET' )
);
- $cases[] = array( '<input />',
+ $cases[] = array( '<input>',
'input', array( 'type' => 'text' )
);
- $cases[] = array( '<keygen />',
+ $cases[] = array( '<keygen>',
'keygen', array( 'keytype' => 'rsa' )
);
- $cases[] = array( '<link />',
+ $cases[] = array( '<link>',
'link', array( 'media' => 'all' )
);
### SPECIFIC CASES
- # <link type="text/css" />
- $cases[] = array( '<link />',
+ # <link type="text/css">
+ $cases[] = array( '<link>',
'link', array( 'type' => 'text/css' )
);
- # <input /> specific handling
- $cases[] = array( '<input type="checkbox" />',
+ # <input> specific handling
+ $cases[] = array( '<input type=checkbox>',
'input', array( 'type' => 'checkbox', 'value' => 'on' ),
'Default value "on" is stripped of checkboxes',
);
- $cases[] = array( '<input type="radio" />',
+ $cases[] = array( '<input type=radio>',
'input', array( 'type' => 'radio', 'value' => 'on' ),
'Default value "on" is stripped of radio buttons',
);
- $cases[] = array( '<input type="submit" value="Submit" />',
+ $cases[] = array( '<input type=submit value=Submit>',
'input', array( 'type' => 'submit', 'value' => 'Submit' ),
'Default value "Submit" is kept on submit buttons (for possible l10n issues)',
);
- $cases[] = array( '<input type="color" />',
+ $cases[] = array( '<input type=color>',
'input', array( 'type' => 'color', 'value' => '' ),
);
- $cases[] = array( '<input type="range" />',
+ $cases[] = array( '<input type=range>',
'input', array( 'type' => 'range', 'value' => '' ),
);
- # <select /> specifc handling
- $cases[] = array( '<select multiple=""></select>',
+ # <select> specifc handling
+ $cases[] = array( '<select multiple></select>',
'select', array( 'size' => '4', 'multiple' => true ),
);
# .. with numeric value
- $cases[] = array( '<select multiple=""></select>',
+ $cases[] = array( '<select multiple></select>',
'select', array( 'size' => 4, 'multiple' => true ),
);
$cases[] = array( '<select></select>',
"dropDefaults accepts values given as an array"
);
-
# Craft the Html elements
$ret = array();
foreach( $cases as $case ) {
$ret[] = array(
$case[0],
- Html::element( $case[1], $case[2] )
+ $case[1], $case[2],
+ isset( $case[3] ) ? $case[3] : ''
);
}
return $ret;
$this->assertEquals( $expected, $ok, $msg );
}
- function cookieDomains() {
+ public static function cookieDomains() {
return array(
array( false, "org"),
array( false, ".org"),
/**
* Feeds URI to test a long regular expression in Http::isValidURI
*/
- function provideURI() {
+ public static function provideURI() {
/** Format: 'boolean expectation', 'URI to test', 'Optional message' */
return array(
array( false, '¿non sens before!! http://a', 'Allow anything before URI' ),
* handles header reporting on redirect pages, and will need to be
* rewritten when bug 29232 is taken care of (high-level handling of
* HTTP redirects).
+ * @group Broken
+ * MWHttpRequestTester's constructor is private, needs to use
+ * MWHttpRequestTester::factory instead. However the objects coming
+ * from that won't have MWHttpRequestTester::setRespHeaders...
*/
function testRelativeRedirections() {
$h = new MWHttpRequestTester( 'http://oldsite/file.ext' );
}
/** Provider for testIPIsInRange() */
- function provideIPsAndRanges() {
+ public static function provideIPsAndRanges() {
# Format: (expected boolean, address, range, optional message)
return array(
# IPv4
/**
* Provider for IP::splitHostAndPort()
*/
- function provideSplitHostAndPort() {
+ public static function provideSplitHostAndPort() {
return array(
array( false, '[', 'Unclosed square bracket' ),
array( false, '[::', 'Unclosed square bracket 2' ),
/**
* Provider for IP::combineHostAndPort()
*/
- function provideCombineHostAndPort() {
+ public static function provideCombineHostAndPort() {
return array(
array( '[::1]', array( '::1', 2, 2 ), 'IPv6 default port' ),
array( '[::1]:2', array( '::1', 2, 3 ), 'IPv6 non-default port' ),
/**
* Provider for IP::testSanitizeRange()
*/
- function provideIPCIDRs() {
+ public static function provideIPCIDRs() {
return array(
array( '35.56.31.252/16', '35.56.0.0/16', 'IPv4 range' ),
array( '135.16.21.252/24', '135.16.21.0/24', 'IPv4 range' ),
/**
* Provider for IP::testPrettifyIP()
*/
- function provideIPsToPrettify() {
+ public static function provideIPsToPrettify() {
return array(
array( '0:0:0:0:0:0:0:0', '::' ),
array( '0:0:0::0:0:0', '::' ),
--- /dev/null
+<?php
+
+/**
+ * @group ContentHandler
+ *
+ * @group Database
+ * ^--- needed, because we do need the database to test link updates
+ */
+class JavascriptContentTest extends TextContentTest {
+
+ public function newContent( $text ) {
+ return new JavascriptContent( $text );
+ }
+
+
+ public function dataGetParserOutput() {
+ return array(
+ array("MediaWiki:Test.js", null, "hello <world>\n",
+ "<pre class=\"mw-code mw-js\" dir=\"ltr\">\nhello <world>\n\n</pre>"),
+ // @todo: more...?
+ );
+ }
+
+ public function dataGetSection() {
+ return array(
+ array( WikitextContentTest::$sections,
+ "0",
+ null
+ ),
+ array( WikitextContentTest::$sections,
+ "2",
+ null
+ ),
+ array( WikitextContentTest::$sections,
+ "8",
+ null
+ ),
+ );
+ }
+
+ public function dataReplaceSection() {
+ return array(
+ array( WikitextContentTest::$sections,
+ "0",
+ "No more",
+ null,
+ null
+ ),
+ array( WikitextContentTest::$sections,
+ "",
+ "No more",
+ null,
+ null
+ ),
+ array( WikitextContentTest::$sections,
+ "2",
+ "== TEST ==\nmore fun",
+ null,
+ null
+ ),
+ array( WikitextContentTest::$sections,
+ "8",
+ "No more",
+ null,
+ null
+ ),
+ array( WikitextContentTest::$sections,
+ "new",
+ "No more",
+ "New",
+ null
+ ),
+ );
+ }
+
+ public function testAddSectionHeader( ) {
+ $content = $this->newContent( 'hello world' );
+ $c = $content->addSectionHeader( 'test' );
+
+ $this->assertTrue( $content->equals( $c ) );
+ }
+
+ // XXX: currently, preSaveTransform is applied to scripts. this may change or become optional.
+ public function dataPreSaveTransform() {
+ return array(
+ array( 'hello this is ~~~',
+ "hello this is [[Special:Contributions/127.0.0.1|127.0.0.1]]",
+ ),
+ array( 'hello \'\'this\'\' is <nowiki>~~~</nowiki>',
+ 'hello \'\'this\'\' is <nowiki>~~~</nowiki>',
+ ),
+ );
+ }
+
+ public function dataPreloadTransform() {
+ return array(
+ array( 'hello this is ~~~',
+ "hello this is ~~~",
+ ),
+ array( 'hello \'\'this\'\' is <noinclude>foo</noinclude><includeonly>bar</includeonly>',
+ 'hello \'\'this\'\' is <noinclude>foo</noinclude><includeonly>bar</includeonly>',
+ ),
+ );
+ }
+
+ public function dataGetRedirectTarget() {
+ return array(
+ array( '#REDIRECT [[Test]]',
+ null,
+ ),
+ array( '#REDIRECT Test',
+ null,
+ ),
+ array( '* #REDIRECT [[Test]]',
+ null,
+ ),
+ );
+ }
+
+ /**
+ * @todo: test needs database!
+ */
+ /*
+ public function getRedirectChain() {
+ $text = $this->getNativeData();
+ return Title::newFromRedirectArray( $text );
+ }
+ */
+
+ /**
+ * @todo: test needs database!
+ */
+ /*
+ public function getUltimateRedirectTarget() {
+ $text = $this->getNativeData();
+ return Title::newFromRedirectRecurse( $text );
+ }
+ */
+
+
+ public function dataIsCountable() {
+ return array(
+ array( '',
+ null,
+ 'any',
+ true
+ ),
+ array( 'Foo',
+ null,
+ 'any',
+ true
+ ),
+ array( 'Foo',
+ null,
+ 'comma',
+ false
+ ),
+ array( 'Foo, bar',
+ null,
+ 'comma',
+ false
+ ),
+ array( 'Foo',
+ null,
+ 'link',
+ false
+ ),
+ array( 'Foo [[bar]]',
+ null,
+ 'link',
+ false
+ ),
+ array( 'Foo',
+ true,
+ 'link',
+ false
+ ),
+ array( 'Foo [[bar]]',
+ false,
+ 'link',
+ false
+ ),
+ array( '#REDIRECT [[bar]]',
+ true,
+ 'any',
+ true
+ ),
+ array( '#REDIRECT [[bar]]',
+ true,
+ 'comma',
+ false
+ ),
+ array( '#REDIRECT [[bar]]',
+ true,
+ 'link',
+ false
+ ),
+ );
+ }
+
+ public function dataGetTextForSummary() {
+ return array(
+ array( "hello\nworld.",
+ 16,
+ 'hello world.',
+ ),
+ array( 'hello world.',
+ 8,
+ 'hello...',
+ ),
+ array( '[[hello world]].',
+ 8,
+ '[[hel...',
+ ),
+ );
+ }
+
+ public function testMatchMagicWord( ) {
+ $mw = MagicWord::get( "staticredirect" );
+
+ $content = $this->newContent( "#REDIRECT [[FOO]]\n__STATICREDIRECT__" );
+ $this->assertFalse( $content->matchMagicWord( $mw ), "should not have matched magic word, since it's not wikitext" );
+ }
+
+ public function testUpdateRedirect( ) {
+ $target = Title::newFromText( "testUpdateRedirect_target" );
+
+ $content = $this->newContent( "#REDIRECT [[Someplace]]" );
+ $newContent = $content->updateRedirect( $target );
+
+ $this->assertTrue( $content->equals( $newContent ), "content should be unchanged since it's not wikitext" );
+ }
+
+ # =================================================================================================================
+
+ public function testGetModel() {
+ $content = $this->newContent( "hello world." );
+
+ $this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $content->getModel() );
+ }
+
+ public function testGetContentHandler() {
+ $content = $this->newContent( "hello world." );
+
+ $this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $content->getContentHandler()->getModelID() );
+ }
+
+ public function dataEquals( ) {
+ return array(
+ array( new JavascriptContent( "hallo" ), null, false ),
+ array( new JavascriptContent( "hallo" ), new JavascriptContent( "hallo" ), true ),
+ array( new JavascriptContent( "hallo" ), new CssContent( "hallo" ), false ),
+ array( new JavascriptContent( "hallo" ), new JavascriptContent( "HALLO" ), false ),
+ );
+ }
+
+}
class JsonTest extends MediaWikiTestCase {
function testPhpBug46944Test() {
-
$this->assertNotEquals(
'\ud840\udc00',
strtolower( FormatJson::encode( "\xf0\xa0\x80\x80" ) ),
'Test encoding an broken json_encode character (U+20000)'
);
-
-
+
}
function testDecodeVarTypes() {
-
$this->assertInternalType(
'object',
FormatJson::decode( '{"Name": "Cheeso", "Rank": 7}' ),
'Default to object'
);
-
+
$this->assertInternalType(
'array',
FormatJson::decode( '{"Name": "Cheeso", "Rank": 7}', true ),
'Optional array'
);
-
}
-
}
-
protected $lang = null;
protected $lc = null;
- function setUp() {
+ protected function setUp() {
parent::setUp();
- global $wgMemc, $wgRequest, $wgUser, $wgContLang;
- $wgUser = new User;
- $wgRequest = new FauxRequest( array() );
- $wgMemc = new EmptyBagOStuff;
- $wgContLang = Language::factory( 'tg' );
+ $this->setMwGlobals( array(
+ 'wgContLang' => Language::factory( 'tg' ),
+ 'wgDefaultLanguageVariant' => false,
+ 'wgMemc' => new EmptyBagOStuff,
+ 'wgRequest' => new FauxRequest( array() ),
+ 'wgUser' => new User,
+ ) );
+
$this->lang = new LanguageToTest();
- $this->lc = new TestConverter( $this->lang, 'tg',
- array( 'tg', 'tg-latn' ) );
+ $this->lc = new TestConverter(
+ $this->lang, 'tg',
+ array( 'tg', 'tg-latn' )
+ );
}
- function tearDown() {
- global $wgMemc;
- unset( $wgMemc );
+ protected function tearDown() {
unset( $this->lc );
unset( $this->lang );
+
parent::tearDown();
}
}
function testGetPreferredVariantHeaderUserVsUrl() {
- global $wgRequest, $wgUser, $wgContLang;
+ global $wgContLang, $wgRequest, $wgUser;
$wgContLang = Language::factory( 'tg-latn' );
$wgRequest->setVal( 'variant', 'tg' );
);
}
- function setUp() {
+ protected function setUp() {
$dbw = wfGetDB( DB_MASTER );
$dbw->replace(
'interwiki',
protected function assertLinksUpdate( Title $title, ParserOutput $parserOutput, $table, $fields, $condition, array $expectedRows ) {
$update = new LinksUpdate( $title, $parserOutput );
+ //NOTE: make sure LinksUpdate does not generate warnings when called inside a transaction.
+ $update->beginTransaction();
$update->doUpdate();
+ $update->commitTransaction();
$this->assertSelect( $table, $fields, $condition, $expectedRows );
}
*/
class LocalFileTest extends MediaWikiTestCase {
- function setUp() {
- global $wgCapitalLinks;
- $wgCapitalLinks = true;
+ protected function setUp() {
+ parent::setUp();
+
+ $this->setMwGlobals( 'wgCapitalLinks', true );
$info = array(
'name' => 'test',
class MWFunctionTest extends MediaWikiTestCase {
function testCallUserFuncWorkarounds() {
-
$this->assertEquals(
call_user_func( array( 'MWFunctionTest', 'someMethod' ) ),
MWFunction::call( 'MWFunctionTest::someMethod' )
call_user_func( array( 'MWFunctionTest', 'someMethod' ), 'foo', 'bar', 'baz' ),
MWFunction::call( 'MWFunctionTest::someMethod', 'foo', 'bar', 'baz' )
);
-
-
-
+
$this->assertEquals(
call_user_func_array( array( 'MWFunctionTest', 'someMethod' ), array() ),
MWFunction::callArray( 'MWFunctionTest::someMethod', array() )
call_user_func_array( array( 'MWFunctionTest', 'someMethod' ), array( 'foo', 'bar', 'baz' ) ),
MWFunction::callArray( 'MWFunctionTest::someMethod', array( 'foo', 'bar', 'baz' ) )
);
-
}
function testNewObjFunction() {
-
$arg1 = 'Foo';
$arg2 = 'Bar';
$arg3 = array( 'Baz' );
$arg4 = new ExampleObject;
-
+
$args = array( $arg1, $arg2, $arg3, $arg4 );
-
+
$newObject = new MWBlankClass( $arg1, $arg2, $arg3, $arg4 );
-
$this->assertEquals(
MWFunction::newObj( 'MWBlankClass', $args )->args,
$newObject->args
);
-
+
$this->assertEquals(
MWFunction::newObj( 'MWBlankClass', $args, true )->args,
$newObject->args,
'Works even with PHP version < 5.1.3'
);
-
}
/**
* @expectedException MWException
*/
function testCallingParentFails() {
-
MWFunction::call( 'parent::foo' );
}
* @expectedException MWException
*/
function testCallingSelfFails() {
-
MWFunction::call( 'self::foo' );
}
}
class MWBlankClass {
-
+
public $args = array();
-
+
function __construct( $arg1, $arg2, $arg3, $arg4 ) {
$this->args = array( $arg1, $arg2, $arg3, $arg4 );
}
-
}
class ExampleObject {
*
*/
class MWNamespaceTest extends MediaWikiTestCase {
- /**
- * Sets up the fixture, for example, opens a network connection.
- * This method is called before a test is executed.
- */
protected function setUp() {
- }
+ parent::setUp();
- /**
- * Tears down the fixture, for example, closes a network connection.
- * This method is called after a test is executed.
- */
- protected function tearDown() {
+ $this->setMwGlobals( array(
+ 'wgContentNamespaces' => array( NS_MAIN ),
+ 'wgNamespacesWithSubpages' => array(
+ NS_TALK => true,
+ NS_USER => true,
+ NS_USER_TALK => true,
+ ),
+ 'wgCapitalLinks' => true,
+ 'wgCapitalLinkOverrides' => array(),
+ 'wgNonincludableNamespaces' => array(),
+ ) );
}
-
#### START OF TESTS #########################################################
/**
public function testIsContent() {
// NS_MAIN is a content namespace per DefaultSettings.php
// and per function definition.
- $this->assertIsContent( NS_MAIN );
- global $wgContentNamespaces;
-
- $saved = $wgContentNamespaces;
-
- $wgContentNamespaces[] = NS_MAIN;
$this->assertIsContent( NS_MAIN );
// Other namespaces which are not expected to be content
- if ( isset( $wgContentNamespaces[NS_MEDIA] ) ) {
- unset( $wgContentNamespaces[NS_MEDIA] );
- }
- $this->assertIsNotContent( NS_MEDIA );
- if ( isset( $wgContentNamespaces[NS_SPECIAL] ) ) {
- unset( $wgContentNamespaces[NS_SPECIAL] );
- }
+ $this->assertIsNotContent( NS_MEDIA );
$this->assertIsNotContent( NS_SPECIAL );
-
- if ( isset( $wgContentNamespaces[NS_TALK] ) ) {
- unset( $wgContentNamespaces[NS_TALK] );
- }
$this->assertIsNotContent( NS_TALK );
-
- if ( isset( $wgContentNamespaces[NS_USER] ) ) {
- unset( $wgContentNamespaces[NS_USER] );
- }
$this->assertIsNotContent( NS_USER );
-
- if ( isset( $wgContentNamespaces[NS_CATEGORY] ) ) {
- unset( $wgContentNamespaces[NS_CATEGORY] );
- }
$this->assertIsNotContent( NS_CATEGORY );
-
- if ( isset( $wgContentNamespaces[100] ) ) {
- unset( $wgContentNamespaces[100] );
- }
$this->assertIsNotContent( 100 );
-
- $wgContentNamespaces = $saved;
}
/**
* Similar to testIsContent() but alters the $wgContentNamespaces
* global variable.
*/
- public function testIsContentWithAdditionsInWgContentNamespaces() {
- // NS_MAIN is a content namespace per DefaultSettings.php
- // and per function definition.
- $this->assertIsContent( NS_MAIN );
+ public function testIsContentAdvanced() {
+ global $wgContentNamespaces;
- // Tests that user defined namespace #252 is not content:
+ // Test that user defined namespace #252 is not content
$this->assertIsNotContent( 252 );
- # @todo FIXME: Is global saving really required for PHPUnit?
// Bless namespace # 252 as a content namespace
- global $wgContentNamespaces;
- $savedGlobal = $wgContentNamespaces;
$wgContentNamespaces[] = 252;
+
$this->assertIsContent( 252 );
// Makes sure NS_MAIN was not impacted
$this->assertIsContent( NS_MAIN );
-
- // Restore global
- $wgContentNamespaces = $savedGlobal;
-
- // Verify namespaces after global restauration
- $this->assertIsContent( NS_MAIN );
- $this->assertIsNotContent( 252 );
}
public function testIsWatchable() {
}
public function testHasSubpages() {
+ global $wgNamespacesWithSubpages;
+
// Special namespaces:
$this->assertHasNotSubpages( NS_MEDIA );
$this->assertHasNotSubpages( NS_SPECIAL );
- // namespaces without subpages
- # save up global
- global $wgNamespacesWithSubpages;
- $saved = null;
- if( array_key_exists( NS_MAIN, $wgNamespacesWithSubpages ) ) {
- $saved = $wgNamespacesWithSubpages[NS_MAIN];
- unset( $wgNamespacesWithSubpages[NS_MAIN] );
- }
-
+ // Namespaces without subpages
$this->assertHasNotSubpages( NS_MAIN );
$wgNamespacesWithSubpages[NS_MAIN] = true;
$this->assertHasSubpages( NS_MAIN );
+
$wgNamespacesWithSubpages[NS_MAIN] = false;
$this->assertHasNotSubpages( NS_MAIN );
- # restore global
- if( $saved !== null ) {
- $wgNamespacesWithSubpages[NS_MAIN] = $saved;
- }
-
// Some namespaces with subpages
$this->assertHasSubpages( NS_TALK );
$this->assertHasSubpages( NS_USER );
/**
*/
public function testGetContentNamespaces() {
+ global $wgContentNamespaces;
+
$this->assertEquals(
array( NS_MAIN ),
MWNamespace::getcontentNamespaces(),
'$wgContentNamespaces is an array with only NS_MAIN by default'
);
- global $wgContentNamespaces;
- $saved = $wgContentNamespaces;
# test !is_array( $wgcontentNamespaces )
$wgContentNamespaces = '';
$this->assertEquals( NS_MAIN, MWNamespace::getcontentNamespaces() );
+
$wgContentNamespaces = false;
$this->assertEquals( NS_MAIN, MWNamespace::getcontentNamespaces() );
+
$wgContentNamespaces = null;
$this->assertEquals( NS_MAIN, MWNamespace::getcontentNamespaces() );
+
$wgContentNamespaces = 5;
$this->assertEquals( NS_MAIN, MWNamespace::getcontentNamespaces() );
array( NS_MAIN, NS_USER, NS_CATEGORY ),
MWNamespace::getcontentNamespaces()
);
-
- $wgContentNamespaces = $saved;
}
/**
*/
public function testIsCapitalizedWithWgCapitalLinks() {
global $wgCapitalLinks;
- // Save the global to easily reset to MediaWiki default settings
- $savedGlobal = $wgCapitalLinks;
- $wgCapitalLinks = true;
$this->assertIsCapitalized( NS_PROJECT );
$this->assertIsCapitalized( NS_PROJECT_TALK );
$wgCapitalLinks = false;
+
// hardcoded namespaces (see above function) are still capitalized:
$this->assertIsCapitalized( NS_SPECIAL );
$this->assertIsCapitalized( NS_USER );
$this->assertIsCapitalized( NS_MEDIAWIKI );
+
// setting is correctly applied
$this->assertIsNotCapitalized( NS_PROJECT );
$this->assertIsNotCapitalized( NS_PROJECT_TALK );
-
- // reset global state:
- $wgCapitalLinks = $savedGlobal;
}
/**
*/
public function testIsCapitalizedWithWgCapitalLinkOverrides() {
global $wgCapitalLinkOverrides;
- // Save the global to easily reset to MediaWiki default settings
- $savedGlobal = $wgCapitalLinkOverrides;
// Test default settings
$this->assertIsCapitalized( NS_PROJECT );
$this->assertIsCapitalized( NS_PROJECT_TALK );
+
// hardcoded namespaces (see above function) are capitalized:
$this->assertIsCapitalized( NS_SPECIAL );
$this->assertIsCapitalized( NS_USER );
$wgCapitalLinkOverrides[NS_SPECIAL] = false;
$wgCapitalLinkOverrides[NS_USER] = false;
$wgCapitalLinkOverrides[NS_MEDIAWIKI] = false;
+
$this->assertIsCapitalized( NS_SPECIAL );
$this->assertIsCapitalized( NS_USER );
$this->assertIsCapitalized( NS_MEDIAWIKI );
- $wgCapitalLinkOverrides = $savedGlobal;
$wgCapitalLinkOverrides[NS_PROJECT] = false;
$this->assertIsNotCapitalized( NS_PROJECT );
+
$wgCapitalLinkOverrides[NS_PROJECT] = true ;
$this->assertIsCapitalized( NS_PROJECT );
- unset( $wgCapitalLinkOverrides[NS_PROJECT] );
- $this->assertIsCapitalized( NS_PROJECT );
- // reset global state:
- $wgCapitalLinkOverrides = $savedGlobal;
+ unset( $wgCapitalLinkOverrides[NS_PROJECT] );
+ $this->assertIsCapitalized( NS_PROJECT );
}
public function testHasGenderDistinction() {
public function testIsNonincludable() {
global $wgNonincludableNamespaces;
+
$wgNonincludableNamespaces = array( NS_USER );
$this->assertTrue( MWNamespace::isNonincludable( NS_USER ) );
-
$this->assertFalse( MWNamespace::isNonincludable( NS_TEMPLATE ) );
}
<?php
class MessageTest extends MediaWikiLangTestCase {
+ protected function setUp() {
+ parent::setUp();
+
+ $this->setMwGlobals( array(
+ 'wgLang' => Language::factory( 'en' ),
+ 'wgForceUIMsgAsContentMsg' => array(),
+ ) );
+ }
function testExists() {
$this->assertTrue( wfMessage( 'mainpage' )->exists() );
function testInContentLanguage() {
global $wgLang, $wgForceUIMsgAsContentMsg;
- $oldLang = $wgLang;
$wgLang = Language::factory( 'fr' );
$this->assertEquals( 'Main Page', wfMessage( 'mainpage' )->inContentLanguage()->plain(), 'ForceUIMsg disabled' );
$wgForceUIMsgAsContentMsg['testInContentLanguage'] = 'mainpage';
$this->assertEquals( 'Accueil', wfMessage( 'mainpage' )->inContentLanguage()->plain(), 'ForceUIMsg enabled' );
-
- /* Restore globals */
- $wgLang = $oldLang;
- unset( $wgForceUIMsgAsContentMsg['testInContentLanguage'] );
}
/**
private $popts;
private $pcache;
- function setUp() {
- global $wgContLang, $wgUser, $wgLanguageCode;
- $wgContLang = Language::factory( $wgLanguageCode );
- $this->popts = ParserOptions::newFromUserAndLang( $wgUser, $wgContLang );
- $this->pcache = ParserCache::singleton();
- }
+ protected function setUp() {
+ global $wgLanguageCode, $wgUser;
+ parent::setUp();
+
+ $langObj = Language::factory( $wgLanguageCode );
+
+ $this->setMwGlobals( array(
+ 'wgContLang' => $langObj,
+ 'wgUseDynamicDates' => true,
+ ) );
- function tearDown() {
- parent::tearDown();
+ $this->popts = ParserOptions::newFromUserAndLang( $wgUser, $langObj );
+ $this->pcache = ParserCache::singleton();
}
/**
* @group Database
*/
function testGetParserCacheKeyWithDynamicDates() {
- global $wgUseDynamicDates;
- $wgUseDynamicDates = true;
-
$title = Title::newFromText( "Some test article" );
$page = WikiPage::factory( $title );
$pcacheKeyBefore = $this->pcache->getKey( $page, $this->popts );
$this->assertNotNull( $this->popts->getDateFormat() );
+
$pcacheKeyAfter = $this->pcache->getKey( $page, $this->popts );
$this->assertEquals( $pcacheKeyBefore, $pcacheKeyAfter );
}
class PathRouterTest extends MediaWikiTestCase {
- public function setUp() {
+ protected function setUp() {
$router = new PathRouter;
$router->add("/wiki/$1");
$this->basicRouter = $router;
$this->assertEquals( $matches, array( 'title' => "Title_With Space" ) );
}
- public function dataRegexpChars() {
+ public static function provideRegexpChars() {
return array(
array( "$" ),
array( "$1" ),
/**
* Make sure the router doesn't break on special characters like $ used in regexp replacements
- * @dataProvider dataRegexpChars
+ * @dataProvider provideRegexpChars
*/
public function testRegexpChars( $char ) {
$matches = $this->basicRouter->parse( "/wiki/$char" );
function __construct() {
parent::__construct();
- global $wgEnableEmail;
$this->prefUsers['noemail'] = new User;
$this->context = new RequestContext;
$this->context->setTitle( Title::newFromText('PreferencesTest') );
+ }
+
+ protected function setUp() {
+ parent::setUp();
- //some tests depends on email setting
- $wgEnableEmail = true;
+ $this->setMwGlobals( 'wgEnableEmail', true );
}
/**
* @todo: Emulate these edits somehow and extract
* raw edit summary from RecentChange object
* --
-
+ */
+/*
function testIrcMsgForBlankingAES() {
// $this->context->msg( 'autosumm-blank', .. );
}
// $this->context->msg( 'undo-summary', .. );
}
- * --
- */
+*/
/**
* @param $expected String Expected IRC text without colors codes
}
/* Provider Methods */
- public function provideValidModules() {
+ public static function provideValidModules() {
return array(
array( 'TEST.validModule1', new ResourceLoaderTestModule() ),
);
$this->assertEquals( $modules, ResourceLoaderContext::expandModuleNames( $packed ), $desc );
}
- public function providePackedModules() {
+ public static function providePackedModules() {
return array(
array(
'Example from makePackedModulesString doc comment',
/**
* Test class for Revision storage.
*
+ * @group ContentHandler
* @group Database
* ^--- important, causes temporary tables to be used instead of the real database
*
*/
class RevisionStorageTest extends MediaWikiTestCase {
+ /**
+ * @var WikiPage $the_page
+ */
var $the_page;
function __construct( $name = null, array $data = array(), $dataName = '' ) {
}
public function setUp() {
+ global $wgExtraNamespaces, $wgNamespaceContentModels, $wgContentHandlers, $wgContLang;
+
+ $wgExtraNamespaces[ 12312 ] = 'Dummy';
+ $wgExtraNamespaces[ 12313 ] = 'Dummy_talk';
+
+ $wgNamespaceContentModels[ 12312 ] = 'DUMMY';
+ $wgContentHandlers[ 'DUMMY' ] = 'DummyContentHandlerForTesting';
+
+ MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache
+ $wgContLang->resetNamespaces(); # reset namespace cache
if ( !$this->the_page ) {
- $this->the_page = $this->createPage( 'RevisionStorageTest_the_page', "just a dummy page" );
+ $this->the_page = $this->createPage( 'RevisionStorageTest_the_page', "just a dummy page", CONTENT_MODEL_WIKITEXT );
}
}
+ public function tearDown() {
+ global $wgExtraNamespaces, $wgNamespaceContentModels, $wgContentHandlers, $wgContLang;
+
+ unset( $wgExtraNamespaces[ 12312 ] );
+ unset( $wgExtraNamespaces[ 12313 ] );
+
+ unset( $wgNamespaceContentModels[ 12312 ] );
+ unset( $wgContentHandlers[ 'DUMMY' ] );
+
+ MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache
+ $wgContLang->resetNamespaces(); # reset namespace cache
+ }
+
protected function makeRevision( $props = null ) {
if ( $props === null ) $props = array();
}
protected function createPage( $page, $text, $model = null ) {
- if ( is_string( $page ) ) $page = Title::newFromText( $page );
- if ( $page instanceof Title ) $page = new WikiPage( $page );
+ if ( is_string( $page ) ) {
+ if ( !preg_match( '/:/', $page ) &&
+ ( $model === null || $model === CONTENT_MODEL_WIKITEXT ) ) {
+
+ $ns = $this->getDefaultWikitextNS();
+ $page = MWNamespace::getCanonicalName( $ns ) . ':' . $page;
+ }
+
+ $page = Title::newFromText( $page );
+ }
+
+ if ( $page instanceof Title ) {
+ $page = new WikiPage( $page );
+ }
if ( $page->exists() ) {
$page->doDeleteArticle( "done" );
}
- $page->doEdit( $text, "testing", EDIT_NEW );
+ $content = ContentHandler::makeContent( $text, $page->getTitle(), $model );
+ $page->doEditContent( $content, "testing", EDIT_NEW );
return $page;
}
$this->assertEquals( $orig->getPage(), $rev->getPage() );
$this->assertEquals( $orig->getTimestamp(), $rev->getTimestamp() );
$this->assertEquals( $orig->getUser(), $rev->getUser() );
+ $this->assertEquals( $orig->getContentModel(), $rev->getContentModel() );
+ $this->assertEquals( $orig->getContentFormat(), $rev->getContentFormat() );
$this->assertEquals( $orig->getSha1(), $rev->getSha1() );
}
*/
public function testNewFromArchiveRow()
{
- $page = $this->createPage( 'RevisionStorageTest_testNewFromArchiveRow', 'Lorem Ipsum' );
+ $page = $this->createPage( 'RevisionStorageTest_testNewFromArchiveRow', 'Lorem Ipsum', CONTENT_MODEL_WIKITEXT );
$orig = $page->getRevision();
$page->doDeleteArticle( 'test Revision::newFromArchiveRow' );
*/
public function testFetchRevision()
{
- $page = $this->createPage( 'RevisionStorageTest_testFetchRevision', 'one' );
+ $page = $this->createPage( 'RevisionStorageTest_testFetchRevision', 'one', CONTENT_MODEL_WIKITEXT );
$id1 = $page->getRevision()->getId();
- $page->doEdit( 'two', 'second rev' );
+ $page->doEditContent( new WikitextContent( 'two' ), 'second rev' );
$id2 = $page->getRevision()->getId();
$res = Revision::fetchRevision( $page->getTitle() );
*/
public function testSelectFields()
{
+ global $wgContentHandlerUseDB;
+
$fields = Revision::selectFields();
$this->assertTrue( in_array( 'rev_id', $fields ), 'missing rev_id in list of fields');
$this->assertTrue( in_array( 'rev_page', $fields ), 'missing rev_page in list of fields');
$this->assertTrue( in_array( 'rev_timestamp', $fields ), 'missing rev_timestamp in list of fields');
$this->assertTrue( in_array( 'rev_user', $fields ), 'missing rev_user in list of fields');
+
+ if ( $wgContentHandlerUseDB ) {
+ $this->assertTrue( in_array( 'rev_content_model', $fields ),
+ 'missing rev_content_model in list of fields');
+ $this->assertTrue( in_array( 'rev_content_format', $fields ),
+ 'missing rev_content_format in list of fields');
+ }
}
/**
*/
public function testGetText()
{
+ $this->hideDeprecated( 'Revision::getText' );
+
$orig = $this->makeRevision( array( 'text' => 'hello hello.' ) );
$rev = Revision::newFromId( $orig->getId() );
$this->assertEquals( 'hello hello.', $rev->getText() );
}
+ /**
+ * @covers Revision::getContent
+ */
+ public function testGetContent()
+ {
+ $orig = $this->makeRevision( array( 'text' => 'hello hello.' ) );
+ $rev = Revision::newFromId( $orig->getId() );
+
+ $this->assertEquals( 'hello hello.', $rev->getContent()->getNativeData() );
+ }
+
/**
* @covers Revision::revText
*/
*/
public function testGetRawText()
{
+ $this->hideDeprecated( 'Revision::getRawText' );
+
$orig = $this->makeRevision( array( 'text' => 'hello hello raw.' ) );
$rev = Revision::newFromId( $orig->getId() );
$this->assertEquals( 'hello hello raw.', $rev->getRawText() );
}
+
+ /**
+ * @covers Revision::getContentModel
+ */
+ public function testGetContentModel()
+ {
+ global $wgContentHandlerUseDB;
+
+ if ( !$wgContentHandlerUseDB ) {
+ $this->markTestSkipped( '$wgContentHandlerUseDB is disabled' );
+ }
+
+ $orig = $this->makeRevision( array( 'text' => 'hello hello.',
+ 'content_model' => CONTENT_MODEL_JAVASCRIPT ) );
+ $rev = Revision::newFromId( $orig->getId() );
+
+ $this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $rev->getContentModel() );
+ }
+
+ /**
+ * @covers Revision::getContentFormat
+ */
+ public function testGetContentFormat()
+ {
+ global $wgContentHandlerUseDB;
+
+ if ( !$wgContentHandlerUseDB ) {
+ $this->markTestSkipped( '$wgContentHandlerUseDB is disabled' );
+ }
+
+ $orig = $this->makeRevision( array( 'text' => 'hello hello.',
+ 'content_model' => CONTENT_MODEL_JAVASCRIPT,
+ 'content_format' => CONTENT_FORMAT_JAVASCRIPT ) );
+ $rev = Revision::newFromId( $orig->getId() );
+
+ $this->assertEquals( CONTENT_FORMAT_JAVASCRIPT, $rev->getContentFormat() );
+ }
+
/**
* @covers Revision::isCurrent
*/
public function testIsCurrent()
{
- $page = $this->createPage( 'RevisionStorageTest_testIsCurrent', 'Lorem Ipsum' );
+ $page = $this->createPage( 'RevisionStorageTest_testIsCurrent', 'Lorem Ipsum', CONTENT_MODEL_WIKITEXT );
$rev1 = $page->getRevision();
# @todo: find out if this should be true
$rev1x = Revision::newFromId( $rev1->getId() );
$this->assertTrue( $rev1x->isCurrent() );
- $page->doEdit( 'Bla bla', 'second rev' );
+ $page->doEditContent( ContentHandler::makeContent( 'Bla bla', $page->getTitle(), CONTENT_MODEL_WIKITEXT ), 'second rev' );
$rev2 = $page->getRevision();
# @todo: find out if this should be true
*/
public function testGetPrevious()
{
- $page = $this->createPage( 'RevisionStorageTest_testGetPrevious', 'Lorem Ipsum testGetPrevious' );
+ $page = $this->createPage( 'RevisionStorageTest_testGetPrevious', 'Lorem Ipsum testGetPrevious', CONTENT_MODEL_WIKITEXT );
$rev1 = $page->getRevision();
$this->assertNull( $rev1->getPrevious() );
- $page->doEdit( 'Bla bla', 'second rev testGetPrevious' );
+ $page->doEditContent( ContentHandler::makeContent( 'Bla bla', $page->getTitle(), CONTENT_MODEL_WIKITEXT ),
+ 'second rev testGetPrevious' );
$rev2 = $page->getRevision();
$this->assertNotNull( $rev2->getPrevious() );
*/
public function testGetNext()
{
- $page = $this->createPage( 'RevisionStorageTest_testGetNext', 'Lorem Ipsum testGetNext' );
+ $page = $this->createPage( 'RevisionStorageTest_testGetNext', 'Lorem Ipsum testGetNext', CONTENT_MODEL_WIKITEXT );
$rev1 = $page->getRevision();
$this->assertNull( $rev1->getNext() );
- $page->doEdit( 'Bla bla', 'second rev testGetNext' );
+ $page->doEditContent( ContentHandler::makeContent( 'Bla bla', $page->getTitle(), CONTENT_MODEL_WIKITEXT ),
+ 'second rev testGetNext' );
$rev2 = $page->getRevision();
$this->assertNotNull( $rev1->getNext() );
*/
public function testNewNullRevision()
{
- $page = $this->createPage( 'RevisionStorageTest_testNewNullRevision', 'some testing text' );
+ $page = $this->createPage( 'RevisionStorageTest_testNewNullRevision', 'some testing text', CONTENT_MODEL_WIKITEXT );
$orig = $page->getRevision();
$dbw = wfGetDB( DB_MASTER );
$rev = Revision::newNullRevision( $dbw, $page->getId(), 'a null revision', false );
- $this->assertNotEquals( $orig->getId(), $rev->getId(), 'new null revision shold have a different id from the original revision' );
- $this->assertEquals( $orig->getTextId(), $rev->getTextId(), 'new null revision shold have the same text id as the original revision' );
- $this->assertEquals( 'some testing text', $rev->getText() );
+ $this->assertNotEquals( $orig->getId(), $rev->getId(),
+ 'new null revision shold have a different id from the original revision' );
+ $this->assertEquals( $orig->getTextId(), $rev->getTextId(),
+ 'new null revision shold have the same text id as the original revision' );
+ $this->assertEquals( 'some testing text', $rev->getContent()->getNativeData() );
}
- public function dataUserWasLastToEdit() {
+ public static function provideUserWasLastToEdit() {
return array(
array( #0
3, true, # actually the last edit
}
/**
- * @dataProvider dataUserWasLastToEdit
+ * @dataProvider provideUserWasLastToEdit
*/
public function testUserWasLastToEdit( $sinceIdx, $expectedLast ) {
$userA = \User::newFromName( "RevisionStorageTest_userA" );
$userB = \User::createNew( $userB->getName() );
}
+ $ns = $this->getDefaultWikitextNS();
+
$dbw = wfGetDB( DB_MASTER );
$revisions = array();
// create revisions -----------------------------
- $page = WikiPage::factory( Title::newFromText( 'RevisionStorageTest_testUserWasLastToEdit' ) );
+ $page = WikiPage::factory( Title::newFromText(
+ 'RevisionStorageTest_testUserWasLastToEdit', $ns ) );
# zero
$revisions[0] = new Revision( array(
'page' => $page->getId(),
+ 'title' => $page->getTitle(), // we need the title to determine the page's default content model
'timestamp' => '20120101000000',
'user' => $userA->getId(),
'text' => 'zero',
+ 'content_model' => CONTENT_MODEL_WIKITEXT,
'summary' => 'edit zero'
) );
$revisions[0]->insertOn( $dbw );
# one
$revisions[1] = new Revision( array(
'page' => $page->getId(),
+ 'title' => $page->getTitle(), // still need the title, because $page->getId() is 0 (there's no entry in the page table)
'timestamp' => '20120101000100',
'user' => $userA->getId(),
'text' => 'one',
+ 'content_model' => CONTENT_MODEL_WIKITEXT,
'summary' => 'edit one'
) );
$revisions[1]->insertOn( $dbw );
# two
$revisions[2] = new Revision( array(
'page' => $page->getId(),
+ 'title' => $page->getTitle(),
'timestamp' => '20120101000200',
'user' => $userB->getId(),
'text' => 'two',
+ 'content_model' => CONTENT_MODEL_WIKITEXT,
'summary' => 'edit two'
) );
$revisions[2]->insertOn( $dbw );
# three
$revisions[3] = new Revision( array(
'page' => $page->getId(),
+ 'title' => $page->getTitle(),
'timestamp' => '20120101000300',
'user' => $userA->getId(),
'text' => 'three',
+ 'content_model' => CONTENT_MODEL_WIKITEXT,
'summary' => 'edit three'
) );
$revisions[3]->insertOn( $dbw );
# four
$revisions[4] = new Revision( array(
'page' => $page->getId(),
+ 'title' => $page->getTitle(),
'timestamp' => '20120101000200',
'user' => $userA->getId(),
'text' => 'zero',
+ 'content_model' => CONTENT_MODEL_WIKITEXT,
'summary' => 'edit four'
) );
$revisions[4]->insertOn( $dbw );
--- /dev/null
+<?php
+
+/**
+ * @group ContentHandler
+ * @group Database
+ * ^--- important, causes temporary tables to be used instead of the real database
+ */
+class RevisionTest_ContentHandlerUseDB extends RevisionStorageTest {
+ var $saveContentHandlerNoDB = null;
+
+ function setUp() {
+ global $wgContentHandlerUseDB;
+
+ $this->saveContentHandlerNoDB = $wgContentHandlerUseDB;
+
+ $wgContentHandlerUseDB = false;
+
+ $dbw = wfGetDB( DB_MASTER );
+
+ $page_table = $dbw->tableName( 'page' );
+ $revision_table = $dbw->tableName( 'revision' );
+ $archive_table = $dbw->tableName( 'archive' );
+
+ if ( $dbw->fieldExists( $page_table, 'page_content_model' ) ) {
+ $dbw->query( "alter table $page_table drop column page_content_model" );
+ $dbw->query( "alter table $revision_table drop column rev_content_model" );
+ $dbw->query( "alter table $revision_table drop column rev_content_format" );
+ $dbw->query( "alter table $archive_table drop column ar_content_model" );
+ $dbw->query( "alter table $archive_table drop column ar_content_format" );
+ }
+
+ parent::setUp();
+ }
+
+ function tearDown() {
+ global $wgContentHandlerUseDB;
+
+ parent::tearDown();
+
+ $wgContentHandlerUseDB = $this->saveContentHandlerNoDB;
+ }
+
+ /**
+ * @covers Revision::selectFields
+ */
+ public function testSelectFields() {
+ $fields = Revision::selectFields();
+
+ $this->assertTrue( in_array( 'rev_id', $fields ), 'missing rev_id in list of fields');
+ $this->assertTrue( in_array( 'rev_page', $fields ), 'missing rev_page in list of fields');
+ $this->assertTrue( in_array( 'rev_timestamp', $fields ), 'missing rev_timestamp in list of fields');
+ $this->assertTrue( in_array( 'rev_user', $fields ), 'missing rev_user in list of fields');
+
+ $this->assertFalse( in_array( 'rev_content_model', $fields ), 'missing rev_content_model in list of fields');
+ $this->assertFalse( in_array( 'rev_content_format', $fields ), 'missing rev_content_format in list of fields');
+ }
+
+ /**
+ * @covers Revision::getContentModel
+ */
+ public function testGetContentModel() {
+ try {
+ $this->makeRevision( array( 'text' => 'hello hello.',
+ 'content_model' => CONTENT_MODEL_JAVASCRIPT ) );
+
+ $this->fail( "Creating JavaScript content on a wikitext page should fail with "
+ . "\$wgContentHandlerUseDB disabled" );
+ } catch ( MWException $ex ) {
+ $this->assertTrue( true ); // ok
+ }
+ }
+
+
+ /**
+ * @covers Revision::getContentFormat
+ */
+ public function testGetContentFormat() {
+ try {
+ //@todo: change this to test failure on using a non-standard (but supported) format
+ // for a content model supported in the given location. As of 1.21, there are
+ // no alternative formats for any of the standard content models that could be
+ // used for this though.
+
+ $this->makeRevision( array( 'text' => 'hello hello.',
+ 'content_model' => CONTENT_MODEL_JAVASCRIPT,
+ 'content_format' => 'text/javascript' ) );
+
+ $this->fail( "Creating JavaScript content on a wikitext page should fail with "
+ . "\$wgContentHandlerUseDB disabled" );
+ } catch ( MWException $ex ) {
+ $this->assertTrue( true ); // ok
+ }
+ }
+
+}
+
+
<?php
+/**
+ * @group ContentHandler
+ */
class RevisionTest extends MediaWikiTestCase {
- var $saveGlobals = array();
-
- function setUp() {
+ protected function setUp() {
global $wgContLang;
- $wgContLang = Language::factory( 'en' );
- $globalSet = array(
+
+ parent::setUp();
+
+ $this->setMwGlobals( array(
+ 'wgContLang' => Language::factory( 'en' ),
'wgLegacyEncoding' => false,
'wgCompressRevisions' => false,
+
+ 'wgContentHandlerTextFallback' => 'ignore',
+ ) );
+
+ $this->mergeMwGlobalArrayValue(
+ 'wgExtraNamespaces',
+ array(
+ 12312 => 'Dummy',
+ 12313 => 'Dummy_talk',
+ )
);
- foreach ( $globalSet as $var => $data ) {
- $this->saveGlobals[$var] = $GLOBALS[$var];
- $GLOBALS[$var] = $data;
- }
+
+ $this->mergeMwGlobalArrayValue(
+ 'wgNamespaceContentModels',
+ array(
+ 12312 => 'testing',
+ )
+ );
+
+ $this->mergeMwGlobalArrayValue(
+ 'wgContentHandlers',
+ array(
+ 'testing' => 'DummyContentHandlerForTesting',
+ 'RevisionTestModifyableContent' => 'RevisionTestModifyableContentHandler',
+ )
+ );
+
+ MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache
+ $wgContLang->resetNamespaces(); # reset namespace cache
}
function tearDown() {
- foreach ( $this->saveGlobals as $var => $data ) {
- $GLOBALS[$var] = $data;
- }
+ global $wgContLang;
+
+ MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache
+ $wgContLang->resetNamespaces(); # reset namespace cache
+
+ parent::tearDown();
}
function testGetRevisionText() {
}
function testCompressRevisionTextUtf8Gzip() {
- $GLOBALS['wgCompressRevisions'] = true;
+ global $wgCompressRevisions;
+
+ $wgCompressRevisions = true;
+
$row = new stdClass;
$row->old_text = "Wiki est l'\xc3\xa9cole superieur !";
$row->old_flags = Revision::compressRevisionText( $row->old_text );
$this->assertEquals( "Wiki est l'\xc3\xa9cole superieur !",
Revision::getRevisionText( $row ), "getRevisionText" );
}
+
+ # =================================================================================================================
+
+ /**
+ * @param string $text
+ * @param string $title
+ * @param string $model
+ * @return Revision
+ */
+ function newTestRevision( $text, $title = "Test", $model = CONTENT_MODEL_WIKITEXT, $format = null ) {
+ if ( is_string( $title ) ) {
+ $title = Title::newFromText( $title );
+ }
+
+ $content = ContentHandler::makeContent( $text, $title, $model, $format );
+
+ $rev = new Revision(
+ array(
+ 'id' => 42,
+ 'page' => 23,
+ 'title' => $title,
+
+ 'content' => $content,
+ 'length' => $content->getSize(),
+ 'comment' => "testing",
+ 'minor_edit' => false,
+
+ 'content_format' => $format,
+ )
+ );
+
+ return $rev;
+ }
+
+ function dataGetContentModel() {
+ //NOTE: we expect the help namespace to always contain wikitext
+ return array(
+ array( 'hello world', 'Help:Hello', null, null, CONTENT_MODEL_WIKITEXT ),
+ array( 'hello world', 'User:hello/there.css', null, null, CONTENT_MODEL_CSS ),
+ array( serialize('hello world'), 'Dummy:Hello', null, null, "testing" ),
+ );
+ }
+
+ /**
+ * @group Database
+ * @dataProvider dataGetContentModel
+ */
+ function testGetContentModel( $text, $title, $model, $format, $expectedModel ) {
+ $rev = $this->newTestRevision( $text, $title, $model, $format );
+
+ $this->assertEquals( $expectedModel, $rev->getContentModel() );
+ }
+
+ function dataGetContentFormat() {
+ //NOTE: we expect the help namespace to always contain wikitext
+ return array(
+ array( 'hello world', 'Help:Hello', null, null, CONTENT_FORMAT_WIKITEXT ),
+ array( 'hello world', 'Help:Hello', CONTENT_MODEL_CSS, null, CONTENT_FORMAT_CSS ),
+ array( 'hello world', 'User:hello/there.css', null, null, CONTENT_FORMAT_CSS ),
+ array( serialize('hello world'), 'Dummy:Hello', null, null, "testing" ),
+ );
+ }
+
+ /**
+ * @group Database
+ * @dataProvider dataGetContentFormat
+ */
+ function testGetContentFormat( $text, $title, $model, $format, $expectedFormat ) {
+ $rev = $this->newTestRevision( $text, $title, $model, $format );
+
+ $this->assertEquals( $expectedFormat, $rev->getContentFormat() );
+ }
+
+ function dataGetContentHandler() {
+ //NOTE: we expect the help namespace to always contain wikitext
+ return array(
+ array( 'hello world', 'Help:Hello', null, null, 'WikitextContentHandler' ),
+ array( 'hello world', 'User:hello/there.css', null, null, 'CssContentHandler' ),
+ array( serialize('hello world'), 'Dummy:Hello', null, null, 'DummyContentHandlerForTesting' ),
+ );
+ }
+
+ /**
+ * @group Database
+ * @dataProvider dataGetContentHandler
+ */
+ function testGetContentHandler( $text, $title, $model, $format, $expectedClass ) {
+ $rev = $this->newTestRevision( $text, $title, $model, $format );
+
+ $this->assertEquals( $expectedClass, get_class( $rev->getContentHandler() ) );
+ }
+
+ function dataGetContent() {
+ //NOTE: we expect the help namespace to always contain wikitext
+ return array(
+ array( 'hello world', 'Help:Hello', null, null, Revision::FOR_PUBLIC, 'hello world' ),
+ array( serialize('hello world'), 'Hello', "testing", null, Revision::FOR_PUBLIC, serialize('hello world') ),
+ array( serialize('hello world'), 'Dummy:Hello', null, null, Revision::FOR_PUBLIC, serialize('hello world') ),
+ );
+ }
+
+ /**
+ * @group Database
+ * @dataProvider dataGetContent
+ */
+ function testGetContent( $text, $title, $model, $format, $audience, $expectedSerialization ) {
+ $rev = $this->newTestRevision( $text, $title, $model, $format );
+ $content = $rev->getContent( $audience );
+
+ $this->assertEquals( $expectedSerialization, is_null( $content ) ? null : $content->serialize( $format ) );
+ }
+
+ function dataGetText() {
+ //NOTE: we expect the help namespace to always contain wikitext
+ return array(
+ array( 'hello world', 'Help:Hello', null, null, Revision::FOR_PUBLIC, 'hello world' ),
+ array( serialize('hello world'), 'Hello', "testing", null, Revision::FOR_PUBLIC, null ),
+ array( serialize('hello world'), 'Dummy:Hello', null, null, Revision::FOR_PUBLIC, null ),
+ );
+ }
+
+ /**
+ * @group Database
+ * @dataProvider dataGetText
+ */
+ function testGetText( $text, $title, $model, $format, $audience, $expectedText ) {
+ $this->hideDeprecated( 'Revision::getText' );
+
+ $rev = $this->newTestRevision( $text, $title, $model, $format );
+
+ $this->assertEquals( $expectedText, $rev->getText( $audience ) );
+ }
+
+ /**
+ * @group Database
+ * @dataProvider dataGetText
+ */
+ function testGetRawText( $text, $title, $model, $format, $audience, $expectedText ) {
+ $this->hideDeprecated( 'Revision::getRawText' );
+
+ $rev = $this->newTestRevision( $text, $title, $model, $format );
+
+ $this->assertEquals( $expectedText, $rev->getRawText( $audience ) );
+ }
+
+
+ public function dataGetSize( ) {
+ return array(
+ array( "hello world.", CONTENT_MODEL_WIKITEXT, 12 ),
+ array( serialize( "hello world." ), "testing", 12 ),
+ );
+ }
+
+ /**
+ * @covers Revision::getSize
+ * @group Database
+ * @dataProvider dataGetSize
+ */
+ public function testGetSize( $text, $model, $expected_size )
+ {
+ $rev = $this->newTestRevision( $text, 'RevisionTest_testGetSize', $model );
+ $this->assertEquals( $expected_size, $rev->getSize() );
+ }
+
+ public function dataGetSha1( ) {
+ return array(
+ array( "hello world.", CONTENT_MODEL_WIKITEXT, Revision::base36Sha1( "hello world." ) ),
+ array( serialize( "hello world." ), "testing", Revision::base36Sha1( serialize( "hello world." ) ) ),
+ );
+ }
+
+ /**
+ * @covers Revision::getSha1
+ * @group Database
+ * @dataProvider dataGetSha1
+ */
+ public function testGetSha1( $text, $model, $expected_hash )
+ {
+ $rev = $this->newTestRevision( $text, 'RevisionTest_testGetSha1', $model );
+ $this->assertEquals( $expected_hash, $rev->getSha1() );
+ }
+
+ public function testConstructWithText() {
+ $this->hideDeprecated( "Revision::getText" );
+
+ $rev = new Revision( array(
+ 'text' => 'hello world.',
+ 'content_model' => CONTENT_MODEL_JAVASCRIPT
+ ));
+
+ $this->assertNotNull( $rev->getText(), 'no content text' );
+ $this->assertNotNull( $rev->getContent(), 'no content object available' );
+ $this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $rev->getContent()->getModel() );
+ $this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $rev->getContentModel() );
+ }
+
+ public function testConstructWithContent() {
+ $this->hideDeprecated( "Revision::getText" );
+
+ $title = Title::newFromText( 'RevisionTest_testConstructWithContent' );
+
+ $rev = new Revision( array(
+ 'content' => ContentHandler::makeContent( 'hello world.', $title, CONTENT_MODEL_JAVASCRIPT ),
+ ));
+
+ $this->assertNotNull( $rev->getText(), 'no content text' );
+ $this->assertNotNull( $rev->getContent(), 'no content object available' );
+ $this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $rev->getContent()->getModel() );
+ $this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $rev->getContentModel() );
+ }
+
+ /**
+ * Tests whether $rev->getContent() returns a clone when needed.
+ *
+ * @group Database
+ */
+ function testGetContentClone( ) {
+ $content = new RevisionTestModifyableContent( "foo" );
+
+ $rev = new Revision(
+ array(
+ 'id' => 42,
+ 'page' => 23,
+ 'title' => Title::newFromText( "testGetContentClone_dummy" ),
+
+ 'content' => $content,
+ 'length' => $content->getSize(),
+ 'comment' => "testing",
+ 'minor_edit' => false,
+ )
+ );
+
+ $content = $rev->getContent( Revision::RAW );
+ $content->setText( "bar" );
+
+ $content2 = $rev->getContent( Revision::RAW );
+ $this->assertNotSame( $content, $content2, "expected a clone" ); // content is mutable, expect clone
+ $this->assertEquals( "foo", $content2->getText() ); // clone should contain the original text
+
+ $content2->setText( "bla bla" );
+ $this->assertEquals( "bar", $content->getText() ); // clones should be independent
+ }
+
+
+ /**
+ * Tests whether $rev->getContent() returns the same object repeatedly if appropriate.
+ *
+ * @group Database
+ */
+ function testGetContentUncloned() {
+ $rev = $this->newTestRevision( "hello", "testGetContentUncloned_dummy", CONTENT_MODEL_WIKITEXT );
+ $content = $rev->getContent( Revision::RAW );
+ $content2 = $rev->getContent( Revision::RAW );
+
+ // for immutable content like wikitext, this should be the same object
+ $this->assertSame( $content, $content2 );
+ }
+
+}
+
+class RevisionTestModifyableContent extends TextContent {
+ public function __construct( $text ) {
+ parent::__construct( $text, "RevisionTestModifyableContent" );
+ }
+
+ public function copy( ) {
+ return new RevisionTestModifyableContent( $this->mText );
+ }
+
+ public function getText() {
+ return $this->mText;
+ }
+
+ public function setText( $text ) {
+ $this->mText = $text;
+ }
+
}
+class RevisionTestModifyableContentHandler extends TextContentHandler {
+ public function __construct( ) {
+ parent::__construct( "RevisionTestModifyableContent", array( CONTENT_FORMAT_TEXT ) );
+ }
+
+ public function unserializeContent( $text, $format = null ) {
+ $this->checkFormat( $format );
+
+ return new RevisionTestModifyableContent( $text );
+ }
+
+ public function makeEmptyContent() {
+ return new RevisionTestModifyableContent( '' );
+ }
+}
/**
* Anything that needs to happen before your tests should go here.
*/
- function setUp() {
- global $wgContLang;
+ protected function setUp() {
+ // Be sure to do call the parent setup and teardown functions.
+ // This makes sure that all the various cleanup and restorations
+ // happen as they should (including the restoration for setMwGlobals).
parent::setUp();
- /* For example, we need to set $wgContLang for creating a new Title */
- $wgContLang = Language::factory( 'en' );
+ // This sets the globals and will restore them automatically
+ // after each test.
+ $this->setMwGlobals( array(
+ 'wgContLang' => Language::factory( 'en' ),
+ ) );
}
/**
* Anything cleanup you need to do should go here.
*/
- function tearDown() {
+ protected function tearDown() {
parent::tearDown();
}
/**
* If you want to run a the same test with a variety of data. use a data provider.
* see: http://www.phpunit.de/manual/3.4/en/writing-tests-for-phpunit.html
+ *
+ * Note: Data providers are always called statically and outside setUp/tearDown!
*/
- public function provideTitles() {
+ public static function provideTitles() {
return array(
array( 'Text', NS_MEDIA, 'Media:Text' ),
array( 'Text', null, 'Text' ),
* example) as arguments to the next method (e.g. $title in
* testTitleDepends is whatever testInitialCreatiion returned.)
*/
+
/**
* @depends testSetUpMainPageTitleForNextTest
* See http://www.phpunit.de/manual/3.4/en/appendixes.annotations.html#appendixes.annotations.depends
class SanitizerTest extends MediaWikiTestCase {
- function setUp() {
+ protected function setUp() {
+ parent::setUp();
+
+ $this->setMwGlobals( 'wgCleanupPresentationalAttributes', true );
+
AutoLoader::loadClass( 'Sanitizer' );
}
$this->assertEquals( Sanitizer::decodeTagAttributes( 'foo=&foobar;' ), array( 'foo' => '&foobar;' ), 'Entity-like items are accepted' );
}
- function testDeprecatedAttributesDisabled() {
- $GLOBALS['wgCleanupPresentationalAttributes'] = false;
- $this->assertEquals( ' clear="left"', Sanitizer::fixTagAttributes( 'clear="left"', 'br' ), 'Deprecated attributes are not converted to styles when enabled.' );
- }
-
/**
* @dataProvider provideDeprecatedAttributes
*/
function testDeprecatedAttributes( $input, $tag, $expected, $message = null ) {
- $GLOBALS['wgCleanupPresentationalAttributes'] = true;
$this->assertEquals( $expected, Sanitizer::fixTagAttributes( $input, $tag ), $message );
}
- function provideDeprecatedAttributes() {
+ function testDeprecatedAttributesDisabled() {
+ global $wgCleanupPresentationalAttributes;
+
+ $wgCleanupPresentationalAttributes = false;
+
+ $this->assertEquals( ' clear="left"', Sanitizer::fixTagAttributes( 'clear="left"', 'br' ), 'Deprecated attributes are not converted to styles when enabled.' );
+ }
+
+ public static function provideDeprecatedAttributes() {
return array(
array( 'clear="left"', 'br', ' style="clear: left;"', 'Deprecated attributes are converted to styles when enabled.' ),
array( 'clear="all"', 'br', ' style="clear: both;"', 'clear=all is converted to clear: both; not clear: all;' ),
);
}
- function provideCssCommentsFixtures() {
+ public static function provideCssCommentsFixtures() {
/** array( <expected>, <css>, [message] ) */
return array(
array( ' ', '/**/' ),
private $testSuites1 = null;
- public function setUp() {
+ protected function setUp() {
+ parent::setUp();
if ( !defined( 'SELENIUMTEST' ) ) {
define( 'SELENIUMTEST', true );
}
/**
* Clean up the temporary file used to store the selenium settings.
*/
- public function tearDown() {
+ protected function tearDown() {
if ( strlen( $this->tempFileName ) > 0 ) {
unlink( $this->tempFileName );
unset( $this->tempFileName );
class SiteConfigurationTest extends MediaWikiTestCase {
var $mConf;
- function setUp() {
+ protected function setUp() {
$this->mConf = new SiteConfiguration;
$this->mConf->suffixes = array( 'wiki' );
$user = new User();
$user->mRights = array( 'createpage', 'edit', 'purge' );
- $status = $page->doEdit( '{{Categorising template}}', 'Create a page with a template', 0, false, $user );
+ $status = $page->doEditContent( new WikitextContent( '{{Categorising template}}' ), 'Create a page with a template', 0, false, $user );
$this->assertEquals(
array()
, $title->getParentCategories()
);
$template = WikiPage::factory( Title::newFromText( 'Template:Categorising template' ) );
- $status = $template->doEdit( '[[Category:Solved bugs]]', 'Add a category through a template', 0, false, $user );
+ $status = $template->doEditContent( new WikitextContent( '[[Category:Solved bugs]]' ), 'Add a category through a template', 0, false, $user );
// Run the job queue
$jobs = new RunJobs;
--- /dev/null
+<?php
+
+/**
+ * @group ContentHandler
+ *
+ * @group Database
+ * ^--- needed, because we do need the database to test link updates
+ */
+class TextContentTest extends MediaWikiTestCase {
+
+ public function setup() {
+ global $wgUser;
+
+ // anon user
+ $wgUser = new User();
+ $wgUser->setName( '127.0.0.1' );
+
+ $this->context = new RequestContext( new FauxRequest() );
+ $this->context->setTitle( Title::newFromText( "Test" ) );
+ $this->context->setUser( $wgUser );
+ }
+
+ public function newContent( $text ) {
+ return new TextContent( $text );
+ }
+
+
+ public function dataGetParserOutput() {
+ return array(
+ array("TextContentTest_testGetParserOutput", CONTENT_MODEL_TEXT, "hello ''world'' & stuff\n", "hello ''world'' & stuff"),
+ // @todo: more...?
+ );
+ }
+
+ /**
+ * @dataProvider dataGetParserOutput
+ */
+ public function testGetParserOutput( $title, $model, $text, $expectedHtml ) {
+ $title = Title::newFromText( $title );
+ $content = ContentHandler::makeContent( $text, $title, $model );
+
+ $po = $content->getParserOutput( $title );
+
+ $html = $po->getText();
+ $html = preg_replace( '#<!--.*?-->#sm', '', $html ); // strip comments
+
+ $this->assertEquals( $expectedHtml, trim( $html ) );
+ // @todo: assert more properties
+ }
+
+ public function dataPreSaveTransform() {
+ return array(
+ array( 'hello this is ~~~',
+ "hello this is ~~~",
+ ),
+ );
+ }
+
+ /**
+ * @dataProvider dataPreSaveTransform
+ */
+ public function testPreSaveTransform( $text, $expected ) {
+ global $wgContLang;
+
+ $options = ParserOptions::newFromUserAndLang( $this->context->getUser(), $wgContLang );
+
+ $content = $this->newContent( $text );
+ $content = $content->preSaveTransform( $this->context->getTitle(), $this->context->getUser(), $options );
+
+ $this->assertEquals( $expected, $content->getNativeData() );
+ }
+
+ public function dataPreloadTransform() {
+ return array(
+ array( 'hello this is ~~~',
+ "hello this is ~~~",
+ ),
+ );
+ }
+
+ /**
+ * @dataProvider dataPreloadTransform
+ */
+ public function testPreloadTransform( $text, $expected ) {
+ global $wgContLang;
+ $options = ParserOptions::newFromUserAndLang( $this->context->getUser(), $wgContLang );
+
+ $content = $this->newContent( $text );
+ $content = $content->preloadTransform( $this->context->getTitle(), $options );
+
+ $this->assertEquals( $expected, $content->getNativeData() );
+ }
+
+ public function dataGetRedirectTarget() {
+ return array(
+ array( '#REDIRECT [[Test]]',
+ null,
+ ),
+ );
+ }
+
+ /**
+ * @dataProvider dataGetRedirectTarget
+ */
+ public function testGetRedirectTarget( $text, $expected ) {
+ $content = $this->newContent( $text );
+ $t = $content->getRedirectTarget( );
+
+ if ( is_null( $expected ) ) {
+ $this->assertNull( $t, "text should not have generated a redirect target: $text" );
+ } else {
+ $this->assertEquals( $expected, $t->getPrefixedText() );
+ }
+ }
+
+ /**
+ * @dataProvider dataGetRedirectTarget
+ */
+ public function isRedirect( $text, $expected ) {
+ $content = $this->newContent( $text );
+
+ $this->assertEquals( !is_null($expected), $content->isRedirect() );
+ }
+
+
+ /**
+ * @todo: test needs database! Should be done by a test class in the Database group.
+ */
+ /*
+ public function getRedirectChain() {
+ $text = $this->getNativeData();
+ return Title::newFromRedirectArray( $text );
+ }
+ */
+
+ /**
+ * @todo: test needs database! Should be done by a test class in the Database group.
+ */
+ /*
+ public function getUltimateRedirectTarget() {
+ $text = $this->getNativeData();
+ return Title::newFromRedirectRecurse( $text );
+ }
+ */
+
+
+ public function dataIsCountable() {
+ return array(
+ array( '',
+ null,
+ 'any',
+ true
+ ),
+ array( 'Foo',
+ null,
+ 'any',
+ true
+ ),
+ array( 'Foo',
+ null,
+ 'comma',
+ false
+ ),
+ array( 'Foo, bar',
+ null,
+ 'comma',
+ false
+ ),
+ );
+ }
+
+
+ /**
+ * @dataProvider dataIsCountable
+ * @group Database
+ */
+ public function testIsCountable( $text, $hasLinks, $mode, $expected ) {
+ global $wgArticleCountMethod;
+
+ $old = $wgArticleCountMethod;
+ $wgArticleCountMethod = $mode;
+
+ $content = $this->newContent( $text );
+
+ $v = $content->isCountable( $hasLinks, $this->context->getTitle() );
+ $wgArticleCountMethod = $old;
+
+ $this->assertEquals( $expected, $v, "isCountable() returned unexpected value " . var_export( $v, true )
+ . " instead of " . var_export( $expected, true ) . " in mode `$mode` for text \"$text\"" );
+ }
+
+ public function dataGetTextForSummary() {
+ return array(
+ array( "hello\nworld.",
+ 16,
+ 'hello world.',
+ ),
+ array( 'hello world.',
+ 8,
+ 'hello...',
+ ),
+ array( '[[hello world]].',
+ 8,
+ '[[hel...',
+ ),
+ );
+ }
+
+ /**
+ * @dataProvider dataGetTextForSummary
+ */
+ public function testGetTextForSummary( $text, $maxlength, $expected ) {
+ $content = $this->newContent( $text );
+
+ $this->assertEquals( $expected, $content->getTextForSummary( $maxlength ) );
+ }
+
+
+ public function testGetTextForSearchIndex( ) {
+ $content = $this->newContent( "hello world." );
+
+ $this->assertEquals( "hello world.", $content->getTextForSearchIndex() );
+ }
+
+ public function testCopy() {
+ $content = $this->newContent( "hello world." );
+ $copy = $content->copy();
+
+ $this->assertTrue( $content->equals( $copy ), "copy must be equal to original" );
+ $this->assertEquals( "hello world.", $copy->getNativeData() );
+ }
+
+ public function testGetSize( ) {
+ $content = $this->newContent( "hello world." );
+
+ $this->assertEquals( 12, $content->getSize() );
+ }
+
+ public function testGetNativeData( ) {
+ $content = $this->newContent( "hello world." );
+
+ $this->assertEquals( "hello world.", $content->getNativeData() );
+ }
+
+ public function testGetWikitextForTransclusion( ) {
+ $content = $this->newContent( "hello world." );
+
+ $this->assertEquals( "hello world.", $content->getWikitextForTransclusion() );
+ }
+
+ # =================================================================================================================
+
+ public function testGetModel() {
+ $content = $this->newContent( "hello world." );
+
+ $this->assertEquals( CONTENT_MODEL_TEXT, $content->getModel() );
+ }
+
+ public function testGetContentHandler() {
+ $content = $this->newContent( "hello world." );
+
+ $this->assertEquals( CONTENT_MODEL_TEXT, $content->getContentHandler()->getModelID() );
+ }
+
+ public function dataIsEmpty( ) {
+ return array(
+ array( '', true ),
+ array( ' ', false ),
+ array( '0', false ),
+ array( 'hallo welt.', false ),
+ );
+ }
+
+ /**
+ * @dataProvider dataIsEmpty
+ */
+ public function testIsEmpty( $text, $empty ) {
+ $content = $this->newContent( $text );
+
+ $this->assertEquals( $empty, $content->isEmpty() );
+ }
+
+ public function dataEquals( ) {
+ return array(
+ array( new TextContent( "hallo" ), null, false ),
+ array( new TextContent( "hallo" ), new TextContent( "hallo" ), true ),
+ array( new TextContent( "hallo" ), new JavascriptContent( "hallo" ), false ),
+ array( new TextContent( "hallo" ), new WikitextContent( "hallo" ), false ),
+ array( new TextContent( "hallo" ), new TextContent( "HALLO" ), false ),
+ );
+ }
+
+ /**
+ * @dataProvider dataEquals
+ */
+ public function testEquals( Content $a, Content $b = null, $equal = false ) {
+ $this->assertEquals( $equal, $a->equals( $b ) );
+ }
+
+ public function dataGetDeletionUpdates() {
+ return array(
+ array("TextContentTest_testGetSecondaryDataUpdates_1",
+ CONTENT_MODEL_TEXT, "hello ''world''\n",
+ array( )
+ ),
+ array("TextContentTest_testGetSecondaryDataUpdates_2",
+ CONTENT_MODEL_TEXT, "hello [[world test 21344]]\n",
+ array( )
+ ),
+ // @todo: more...?
+ );
+ }
+
+ /**
+ * @dataProvider dataGetDeletionUpdates
+ */
+ public function testDeletionUpdates( $title, $model, $text, $expectedStuff ) {
+ $title = Title::newFromText( $title );
+ $title->resetArticleID( 2342 ); //dummy id. fine as long as we don't try to execute the updates!
+
+ $content = ContentHandler::makeContent( $text, $title, $model );
+
+ $updates = $content->getDeletionUpdates( WikiPage::factory( $title ) );
+
+ // make updates accessible by class name
+ foreach ( $updates as $update ) {
+ $class = get_class( $update );
+ $updates[ $class ] = $update;
+ }
+
+ if ( !$expectedStuff ) {
+ $this->assertTrue( true ); // make phpunit happy
+ return;
+ }
+
+ foreach ( $expectedStuff as $class => $fieldValues ) {
+ $this->assertArrayHasKey( $class, $updates, "missing an update of type $class" );
+
+ $update = $updates[ $class ];
+
+ foreach ( $fieldValues as $field => $value ) {
+ $v = $update->$field; #if the field doesn't exist, just crash and burn
+ $this->assertEquals( $value, $v, "unexpected value for field $field in instance of $class" );
+ }
+ }
+ }
+
+}
<?php
class TimeAdjustTest extends MediaWikiLangTestCase {
- static $offset;
-
- public function setUp() {
+ protected function setUp() {
parent::setUp();
- global $wgLocalTZoffset;
- self::$offset = $wgLocalTZoffset;
- $this->iniSet( 'precision', 15 );
- }
+ $this->setMwGlobals( array(
+ 'wgLocalTZoffset' => null,
+ 'wgContLang' => Language::factory( 'en' ),
+ ) );
- public function tearDown() {
- global $wgLocalTZoffset;
- $wgLocalTZoffset = self::$offset;
- parent::tearDown();
+ $this->iniSet( 'precision', 15 );
}
# Test offset usage for a given language::userAdjust
function testUserAdjust() {
global $wgLocalTZoffset, $wgContLang;
- $wgContLang = $en = Language::factory( 'en' );
-
# Collection of parameters for Language_t_Offset.
# Format: date to be formatted, localTZoffset value, expected date
$userAdjust_tests = array(
$this->assertEquals(
strval( $data[2] ),
- strval( $en->userAdjust( $data[0], '' ) ),
+ strval( $wgContLang->userAdjust( $data[0], '' ) ),
"User adjust {$data[0]} by {$data[1]} minutes should give {$data[2]}"
);
}
* Tests timestamp parsing and output.
*/
class TimestampTest extends MediaWikiTestCase {
+
+ protected function setUp() {
+ parent::setUp();
+
+ $this->setMwGlobals( array(
+ 'wgLanguageCode' => 'en',
+ 'wgContLang' => Language::factory( 'en' ),
+ 'wgLang' => Language::factory( 'en' ),
+ ) );
+ }
/**
* Test parsing of valid timestamps and outputing to MW format.
* @dataProvider provideValidTimestamps
*/
function testHumanOutput() {
$timestamp = new MWTimestamp( time() - 3600 );
- $this->assertEquals( "1 hour ago", $timestamp->getHumanTimestamp()->toString() );
+ $this->assertEquals( "1 hour ago", $timestamp->getHumanTimestamp()->inLanguage( 'en' )->text() );
}
/**
* Returns a list of valid timestamps in the format:
* array( type, timestamp_of_type, timestamp_in_MW )
*/
- function provideValidTimestamps() {
+ public static function provideValidTimestamps() {
return array(
// Various formats
array( TS_UNIX, '1343761268', '20120731190108' ),
<?php
+/**
+ * @group ContentHandler
+ *
+ * @note: We don't make assumptions about the main namespace.
+ * But we do expect the Help namespace to contain Wikitext.
+ *
+ */
class TitleMethodsTest extends MediaWikiTestCase {
- public function dataEquals() {
+ public function setup() {
+ global $wgExtraNamespaces, $wgNamespaceContentModels, $wgContLang;
+
+ $this->mergeMwGlobalArrayValue(
+ 'wgExtraNamespaces',
+ array(
+ 12302 => 'TEST-JS',
+ 12303 => 'TEST-JS_TALK',
+ )
+ );
+
+ $this->mergeMwGlobalArrayValue(
+ 'wgNamespaceContentModels',
+ array(
+ 12302 => CONTENT_MODEL_JAVASCRIPT,
+ )
+ );
+
+ MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache
+ $wgContLang->resetNamespaces(); # reset namespace cache
+ }
+
+ public function teardown() {
+ global $wgContLang;
+
+ MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache
+ $wgContLang->resetNamespaces(); # reset namespace cache
+ }
+
+ public static function provideEquals() {
return array(
array( 'Main Page', 'Main Page', true ),
array( 'Main Page', 'Not The Main Page', false ),
}
/**
- * @dataProvider dataEquals
+ * @dataProvider provideEquals
*/
public function testEquals( $titleA, $titleB, $expectedBool ) {
$titleA = Title::newFromText( $titleA );
$this->assertEquals( $expectedBool, $titleB->equals( $titleA ) );
}
- public function dataInNamespace() {
+ public static function provideInNamespace() {
return array(
array( 'Main Page', NS_MAIN, true ),
array( 'Main Page', NS_TALK, false ),
}
/**
- * @dataProvider dataInNamespace
+ * @dataProvider provideInNamespace
*/
public function testInNamespace( $title, $ns, $expectedBool ) {
$title = Title::newFromText( $title );
$this->assertFalse( $mainpage->inNamespaces( array( NS_PROJECT, NS_TEMPLATE ) ) );
}
- public function dataHasSubjectNamespace() {
+ public static function provideHasSubjectNamespace() {
return array(
array( 'Main Page', NS_MAIN, true ),
array( 'Main Page', NS_TALK, true ),
}
/**
- * @dataProvider dataHasSubjectNamespace
+ * @dataProvider provideHasSubjectNamespace
*/
public function testHasSubjectNamespace( $title, $ns, $expectedBool ) {
$title = Title::newFromText( $title );
$this->assertEquals( $expectedBool, $title->hasSubjectNamespace( $ns ) );
}
- public function dataIsCssOrJsPage() {
+ public function dataGetContentModel() {
+ return array(
+ array( 'Help:Foo', CONTENT_MODEL_WIKITEXT ),
+ array( 'Help:Foo.js', CONTENT_MODEL_WIKITEXT ),
+ array( 'Help:Foo/bar.js', CONTENT_MODEL_WIKITEXT ),
+ array( 'User:Foo', CONTENT_MODEL_WIKITEXT ),
+ array( 'User:Foo.js', CONTENT_MODEL_WIKITEXT ),
+ array( 'User:Foo/bar.js', CONTENT_MODEL_JAVASCRIPT ),
+ array( 'User:Foo/bar.css', CONTENT_MODEL_CSS ),
+ array( 'User talk:Foo/bar.css', CONTENT_MODEL_WIKITEXT ),
+ array( 'User:Foo/bar.js.xxx', CONTENT_MODEL_WIKITEXT ),
+ array( 'User:Foo/bar.xxx', CONTENT_MODEL_WIKITEXT ),
+ array( 'MediaWiki:Foo.js', CONTENT_MODEL_JAVASCRIPT ),
+ array( 'MediaWiki:Foo.css', CONTENT_MODEL_CSS ),
+ array( 'MediaWiki:Foo/bar.css', CONTENT_MODEL_CSS ),
+ array( 'MediaWiki:Foo.JS', CONTENT_MODEL_WIKITEXT ),
+ array( 'MediaWiki:Foo.CSS', CONTENT_MODEL_WIKITEXT ),
+ array( 'MediaWiki:Foo.css.xxx', CONTENT_MODEL_WIKITEXT ),
+ array( 'TEST-JS:Foo', CONTENT_MODEL_JAVASCRIPT ),
+ array( 'TEST-JS:Foo.js', CONTENT_MODEL_JAVASCRIPT ),
+ array( 'TEST-JS:Foo/bar.js', CONTENT_MODEL_JAVASCRIPT ),
+ array( 'TEST-JS_TALK:Foo.js', CONTENT_MODEL_WIKITEXT ),
+ );
+ }
+
+ /**
+ * @dataProvider dataGetContentModel
+ */
+ public function testGetContentModel( $title, $expectedModelId ) {
+ $title = Title::newFromText( $title );
+ $this->assertEquals( $expectedModelId, $title->getContentModel() );
+ }
+
+ /**
+ * @dataProvider dataGetContentModel
+ */
+ public function testHasContentModel( $title, $expectedModelId ) {
+ $title = Title::newFromText( $title );
+ $this->assertTrue( $title->hasContentModel( $expectedModelId ) );
+ }
+
+ public static function provideIsCssOrJsPage() {
return array(
- array( 'Foo', false ),
- array( 'Foo.js', false ),
- array( 'Foo/bar.js', false ),
+ array( 'Help:Foo', false ),
+ array( 'Help:Foo.js', false ),
+ array( 'Help:Foo/bar.js', false ),
array( 'User:Foo', false ),
array( 'User:Foo.js', false ),
array( 'User:Foo/bar.js', false ),
array( 'MediaWiki:Foo.JS', false ),
array( 'MediaWiki:Foo.CSS', false ),
array( 'MediaWiki:Foo.css.xxx', false ),
+ array( 'TEST-JS:Foo', false ),
+ array( 'TEST-JS:Foo.js', false ),
);
}
/**
- * @dataProvider dataIsCssOrJsPage
+ * @dataProvider provideIsCssOrJsPage
*/
public function testIsCssOrJsPage( $title, $expectedBool ) {
$title = Title::newFromText( $title );
}
- public function dataIsCssJsSubpage() {
+ public static function provideIsCssJsSubpage() {
return array(
- array( 'Foo', false ),
- array( 'Foo.js', false ),
- array( 'Foo/bar.js', false ),
+ array( 'Help:Foo', false ),
+ array( 'Help:Foo.js', false ),
+ array( 'Help:Foo/bar.js', false ),
array( 'User:Foo', false ),
array( 'User:Foo.js', false ),
array( 'User:Foo/bar.js', true ),
array( 'MediaWiki:Foo.js', false ),
array( 'User:Foo/bar.JS', false ),
array( 'User:Foo/bar.CSS', false ),
+ array( 'TEST-JS:Foo', false ),
+ array( 'TEST-JS:Foo.js', false ),
);
}
/**
- * @dataProvider dataIsCssJsSubpage
+ * @dataProvider provideIsCssJsSubpage
*/
public function testIsCssJsSubpage( $title, $expectedBool ) {
$title = Title::newFromText( $title );
$this->assertEquals( $expectedBool, $title->isCssJsSubpage() );
}
- public function dataIsCssSubpage() {
+ public static function provideIsCssSubpage() {
return array(
- array( 'Foo', false ),
- array( 'Foo.css', false ),
+ array( 'Help:Foo', false ),
+ array( 'Help:Foo.css', false ),
array( 'User:Foo', false ),
array( 'User:Foo.js', false ),
array( 'User:Foo.css', false ),
}
/**
- * @dataProvider dataIsCssSubpage
+ * @dataProvider provideIsCssSubpage
*/
public function testIsCssSubpage( $title, $expectedBool ) {
$title = Title::newFromText( $title );
$this->assertEquals( $expectedBool, $title->isCssSubpage() );
}
- public function dataIsJsSubpage() {
+ public static function provideIsJsSubpage() {
return array(
- array( 'Foo', false ),
- array( 'Foo.css', false ),
+ array( 'Help:Foo', false ),
+ array( 'Help:Foo.css', false ),
array( 'User:Foo', false ),
array( 'User:Foo.js', false ),
array( 'User:Foo.css', false ),
}
/**
- * @dataProvider dataIsJsSubpage
+ * @dataProvider provideIsJsSubpage
*/
public function testIsJsSubpage( $title, $expectedBool ) {
$title = Title::newFromText( $title );
$this->assertEquals( $expectedBool, $title->isJsSubpage() );
}
- public function dataIsWikitextPage() {
+ public static function provideIsWikitextPage() {
return array(
- array( 'Foo', true ),
- array( 'Foo.js', true ),
- array( 'Foo/bar.js', true ),
+ array( 'Help:Foo', true ),
+ array( 'Help:Foo.js', true ),
+ array( 'Help:Foo/bar.js', true ),
array( 'User:Foo', true ),
array( 'User:Foo.js', true ),
array( 'User:Foo/bar.js', false ),
array( 'MediaWiki:Foo/bar.css', false ),
array( 'User:Foo/bar.JS', true ),
array( 'User:Foo/bar.CSS', true ),
+ array( 'TEST-JS:Foo', false ),
+ array( 'TEST-JS:Foo.js', false ),
+ array( 'TEST-JS_TALK:Foo.js', true ),
);
}
/**
- * @dataProvider dataIsWikitextPage
+ * @dataProvider provideIsWikitextPage
*/
public function testIsWikitextPage( $title, $expectedBool ) {
$title = Title::newFromText( $title );
* @group Database
*/
class TitlePermissionTest extends MediaWikiLangTestCase {
- protected $title;
/**
- * @var User
+ * @var string
*/
- protected $user, $anonUser, $userUser, $altUser;
+ protected $userName, $altUserName;
/**
- * @var string
+ * @var Title
*/
- protected $userName, $altUserName;
+ protected $title;
- function setUp() {
- global $wgLocaltimezone, $wgLocalTZoffset, $wgMemc, $wgContLang, $wgLang;
- parent::setUp();
+ /**
+ * @var User
+ */
+ protected $user, $anonUser, $userUser, $altUser;
- if(!$wgMemc) {
- $wgMemc = new EmptyBagOStuff;
- }
- $wgContLang = $wgLang = Language::factory( 'en' );
+ protected function setUp() {
+ parent::setUp();
- $this->userName = "Useruser";
- $this->altUserName = "Altuseruser";
- date_default_timezone_set( $wgLocaltimezone );
- $wgLocalTZoffset = date( "Z" ) / 60;
+ $langObj = Language::factory( 'en' );
+ $localZone = 'UTC';
+ $localOffset = date( 'Z' ) / 60;
+
+ $this->setMwGlobals( array(
+ 'wgMemc' => new EmptyBagOStuff,
+ 'wgContLang' => $langObj,
+ 'wgLang' => $langObj,
+ 'wgLocaltimezone' => $localZone,
+ 'wgLocalTZoffset' => $localOffset,
+ 'wgNamespaceProtection' => array(
+ NS_MEDIAWIKI => 'editinterface',
+ ),
+ 'wgUser' => null,
+ ) );
+
+ $this->userName = 'Useruser';
+ $this->altUserName = 'Altuseruser';
+ date_default_timezone_set( $localZone );
$this->title = Title::makeTitle( NS_MAIN, "Main Page" );
if ( !isset( $this->userUser ) || !( $this->userUser instanceOf User ) ) {
$this->user = $this->userUser;
}
- }
- function tearDown() {
- parent::tearDown();
}
function setUserPerm( $perm ) {
}
function setUser( $userName = null ) {
+ global $wgUser;
+
if ( $userName === 'anon' ) {
$this->user = $this->anonUser;
} elseif ( $userName === null || $userName === $this->userName ) {
$this->user = $this->altUser;
}
- global $wgUser;
$wgUser = $this->user;
}
$this->runGroupPermissions( 'move', array( array( 'movenotallowedfile' ), array( 'movenotallowed' ) ),
array( array( 'movenotallowedfile' ), array( 'movenologintext' ) ) );
- $this->setTitle( NS_MAIN );
- $this->setUser( 'anon' );
- $this->setUserPerm( "move" );
- $this->runGroupPermissions( 'move', array( ) );
+ if ( $this->isWikitextNS( NS_MAIN ) ) {
+ //NOTE: some content models don't allow moving
+ //@todo: find a Wikitext namespace for testing
- $this->setUserPerm( "" );
- $this->runGroupPermissions( 'move', array( array( 'movenotallowed' ) ),
- array( array( 'movenologintext' ) ) );
+ $this->setTitle( NS_MAIN );
+ $this->setUser( 'anon' );
+ $this->setUserPerm( "move" );
+ $this->runGroupPermissions( 'move', array( ) );
- $this->setUser( $this->userName );
- $this->setUserPerm( "" );
- $this->runGroupPermissions( 'move', array( array( 'movenotallowed' ) ) );
+ $this->setUserPerm( "" );
+ $this->runGroupPermissions( 'move', array( array( 'movenotallowed' ) ),
+ array( array( 'movenologintext' ) ) );
- $this->setUserPerm( "move" );
- $this->runGroupPermissions( 'move', array( ) );
+ $this->setUser( $this->userName );
+ $this->setUserPerm( "" );
+ $this->runGroupPermissions( 'move', array( array( 'movenotallowed' ) ) );
- $this->setUser( 'anon' );
- $this->setUserPerm( 'move' );
- $res = $this->title->getUserPermissionsErrors( 'move-target', $this->user );
- $this->assertEquals( array( ), $res );
+ $this->setUserPerm( "move" );
+ $this->runGroupPermissions( 'move', array( ) );
- $this->setUserPerm( '' );
- $res = $this->title->getUserPermissionsErrors( 'move-target', $this->user );
- $this->assertEquals( array( array( 'movenotallowed' ) ), $res );
+ $this->setUser( 'anon' );
+ $this->setUserPerm( 'move' );
+ $res = $this->title->getUserPermissionsErrors( 'move-target', $this->user );
+ $this->assertEquals( array( ), $res );
+
+ $this->setUserPerm( '' );
+ $res = $this->title->getUserPermissionsErrors( 'move-target', $this->user );
+ $this->assertEquals( array( array( 'movenotallowed' ) ), $res );
+ }
$this->setTitle( NS_USER );
$this->setUser( $this->userName );
}
function testSpecialsAndNSPermissions() {
+ global $wgNamespaceProtection;
$this->setUser( $this->userName );
- global $wgUser;
- $wgUser = $this->user;
$this->setTitle( NS_SPECIAL );
$this->assertEquals( array( array( 'badaccess-group0' ) ),
$this->title->getUserPermissionsErrors( 'bogus', $this->user ) );
- global $wgNamespaceProtection;
- $wgNamespaceProtection[NS_USER] = array ( 'bogus' );
+ $wgNamespaceProtection[NS_USER] = array( 'bogus' );
+
$this->setTitle( NS_USER );
$this->setUserPerm( '' );
$this->assertEquals( array( array( 'badaccess-group0' ), array( 'namespaceprotected', 'User' ) ),
$this->title->getUserPermissionsErrors( 'bogus', $this->user ) );
$wgNamespaceProtection = null;
+
$this->setUserPerm( 'bogus' );
$this->assertEquals( array( ),
$this->title->getUserPermissionsErrors( 'bogus', $this->user ) );
function testCssAndJavascriptPermissions() {
$this->setUser( $this->userName );
- global $wgUser;
- $wgUser = $this->user;
$this->setTitle( NS_USER, $this->altUserName . '/test.js' );
$this->runCSSandJSPermissions(
$this->assertEquals( array( array( 'immobile-source-namespace', 'Media' ) ),
$this->title->getUserPermissionsErrors( 'move', $this->user ) );
- $this->setTitle( NS_MAIN, "test page" );
+ $this->setTitle( NS_HELP, "test page" );
$this->assertEquals( array( ),
$this->title->getUserPermissionsErrors( 'move', $this->user ) );
$this->assertEquals( true,
$this->assertEquals( array( array( 'immobile-target-namespace', 'Media' ) ),
$this->title->getUserPermissionsErrors( 'move-target', $this->user ) );
- $this->setTitle( NS_MAIN, "test page" );
+ $this->setTitle( NS_HELP, "test page" );
$this->assertEquals( array( ),
$this->title->getUserPermissionsErrors( 'move-target', $this->user ) );
$this->assertEquals( true,
$wgUser = $this->user;
$this->setUserPerm( array( "createpage", "move" ) );
- $this->setTitle( NS_MAIN, "test page" );
+ $this->setTitle( NS_HELP, "test page" );
# $short
$this->assertEquals( array( array( 'confirmedittext' ) ),
<?php
+/**
+ *
+ * @group Database
+ * ^--- needed for language cache stuff
+ */
class TitleTest extends MediaWikiTestCase {
+ protected function setUp() {
+ parent::setUp();
+
+ $this->setMwGlobals( array(
+ 'wgLanguageCode' => 'en',
+ 'wgContLang' => Language::factory( 'en' ),
+ // User language
+ 'wgLang' => Language::factory( 'en' ),
+ 'wgAllowUserJs' => false,
+ 'wgDefaultLanguageVariant' => false,
+ ) );
+ }
+
function testLegalChars() {
$titlechars = Title::legalChars();
}
/**
- * @dataProvider dataBug31100
+ * @dataProvider provideBug31100
*/
function testBug31100FixSpecialName( $text, $expectedParam ) {
$title = Title::newFromText( $text );
$this->assertEquals( $expectedParam, $par, "Bug 31100 regression check: Title->fixSpecialName() should preserve parameter" );
}
- function dataBug31100() {
+ public static function provideBug31100() {
return array(
array( 'Special:Version', null ),
array( 'Special:Version/', '' ),
* @param string $source
* @param string $target
* @param array|string|true $expected Required error
- * @dataProvider dataTestIsValidMoveOperation
+ * @dataProvider provideTestIsValidMoveOperation
*/
function testIsValidMoveOperation( $source, $target, $expected ) {
$title = Title::newFromText( $source );
return $result;
}
- function dataTestIsValidMoveOperation() {
+ public static function provideTestIsValidMoveOperation() {
return array(
array( 'Test', 'Test', 'selfmove' ),
array( 'File:Test.jpg', 'Page', 'imagenocrossnamespace' )
/**
* @dataProvider provideCasesForGetpageviewlanguage
*/
- function testGetpageviewlanguage( $expected, $titleText, $contLang, $lang, $variant, $msg='' ) {
- // Save globals
- global $wgContLang, $wgLang, $wgAllowUserJs, $wgLanguageCode, $wgDefaultLanguageVariant;
- $save['wgContLang'] = $wgContLang;
- $save['wgLang'] = $wgLang;
- $save['wgAllowUserJs'] = $wgAllowUserJs;
- $save['wgLanguageCode'] = $wgLanguageCode;
- $save['wgDefaultLanguageVariant'] = $wgDefaultLanguageVariant;
-
- // Setup test environnement:
- $wgContLang = Language::factory( $contLang );
- $wgLang = Language::factory( $lang );
- # To test out .js titles:
- $wgAllowUserJs = true;
+ function testGetpageviewlanguage( $expected, $titleText, $contLang, $lang, $variant, $msg = '' ) {
+ global $wgLanguageCode, $wgContLang, $wgLang, $wgDefaultLanguageVariant, $wgAllowUserJs;
+
+ // Setup environnement for this test
$wgLanguageCode = $contLang;
+ $wgContLang = Language::factory( $contLang );
+ $wgLang = Language::factory( $lang );
$wgDefaultLanguageVariant = $variant;
+ $wgAllowUserJs = true;
$title = Title::newFromText( $titleText );
$this->assertInstanceOf( 'Title', $title,
$title->getPageViewLanguage()->getCode(),
$msg
);
-
- // Restore globals
- $wgContLang = $save['wgContLang'];
- $wgLang = $save['wgLang'];
- $wgAllowUserJs = $save['wgAllowUserJs'];
- $wgLanguageCode = $save['wgLanguageCode'];
- $wgDefaultLanguageVariant = $save['wgDefaultLanguageVariant'];
}
function provideCasesForGetpageviewlanguage() {
);
}
- function provideRootTitleCases() {
+ public static function provideRootTitleCases() {
return array(
# Title, expected base, optional message
array('User:John_Doe/subOne/subTwo', 'John Doe' ),
* @group Database
*/
class UserTest extends MediaWikiTestCase {
- protected $savedGroupPermissions, $savedRevokedPermissions;
/**
* @var User
*/
protected $user;
- public function setUp() {
+ protected function setUp() {
parent::setUp();
- $this->savedGroupPermissions = $GLOBALS['wgGroupPermissions'];
- $this->savedRevokedPermissions = $GLOBALS['wgRevokePermissions'];
+ $this->setMwGlobals( array(
+ 'wgGroupPermissions' => array(),
+ 'wgRevokePermissions' => array(),
+ ) );
$this->setUpPermissionGlobals();
- $this->setUpUser();
+
+ $this->user = new User;
+ $this->user->addGroup( 'unittesters' );
}
+
private function setUpPermissionGlobals() {
global $wgGroupPermissions, $wgRevokePermissions;
'writetest' => true,
'modifytest' => true,
);
+
# Data for regular $wgRevokePermissions test
$wgRevokePermissions['formertesters'] = array(
'runtest' => true,
);
}
- private function setUpUser() {
- $this->user = new User;
- $this->user->addGroup( 'unittesters' );
- }
-
- public function tearDown() {
- parent::tearDown();
-
- $GLOBALS['wgGroupPermissions'] = $this->savedGroupPermissions;
- $GLOBALS['wgRevokePermissions'] = $this->savedRevokedPermissions;
- }
public function testGroupPermissions() {
$rights = User::getGroupPermissions( array( 'unittesters' ) );
$this->assertEquals( $expected, $result, "Groups with permission $right" );
}
- public function provideGetGroupsWithPermission() {
+ public static function provideGetGroupsWithPermission() {
return array(
array(
array( 'unittesters', 'testwriters' ),
$this->assertEquals( $this->user->isValidUserName( $username ), $result, $message );
}
- public function provideUserNames() {
+ public static function provideUserNames() {
return array(
array( '', false, 'Empty string' ),
array( ' ', false, 'Blank space' ),
<?php
class WebRequestTest extends MediaWikiTestCase {
- static $oldServer;
+ protected $oldServer;
- function setUp() {
- self::$oldServer = $_SERVER;
+ protected function setUp() {
+ parent::setUp();
+
+ $this->oldServer = $_SERVER;
}
- function tearDown() {
- $_SERVER = self::$oldServer;
+ protected function tearDown() {
+ $_SERVER = $this->oldServer;
+
+ parent::tearDown();
}
/**
$this->assertEquals( $expected, $result, $description );
}
- function provideDetectServer() {
+ public static function provideDetectServer() {
return array(
array(
'http://x',
$this->assertEquals( $expected, $result, $description );
}
- function provideGetIP() {
+ public static function provideGetIP() {
return array(
array(
'127.0.0.1',
$request->getIP();
}
- function languageProvider() {
+ public static function provideLanguageData() {
return array(
array( '', array(), 'Empty Accept-Language header' ),
array( 'en', array( 'en' => 1 ), 'One language' ),
}
/**
- * @dataProvider languageProvider
+ * @dataProvider provideLanguageData
*/
function testAcceptLang($acceptLanguageHeader, $expectedLanguages, $description) {
$_SERVER = array( 'HTTP_ACCEPT_LANGUAGE' => $acceptLanguageHeader );
<?php
/**
+* @group ContentHandler
* @group Database
* ^--- important, causes temporary tables to be used instead of the real database
**/
function __construct( $name = null, array $data = array(), $dataName = '' ) {
parent::__construct( $name, $data, $dataName );
- $this->tablesUsed = array_merge ( $this->tablesUsed,
- array( 'page',
- 'revision',
- 'text',
+ $this->tablesUsed = array_merge (
+ $this->tablesUsed,
+ array( 'page',
+ 'revision',
+ 'text',
- 'recentchanges',
- 'logging',
+ 'recentchanges',
+ 'logging',
- 'page_props',
- 'pagelinks',
- 'categorylinks',
- 'langlinks',
- 'externallinks',
- 'imagelinks',
- 'templatelinks',
- 'iwlinks' ) );
+ 'page_props',
+ 'pagelinks',
+ 'categorylinks',
+ 'langlinks',
+ 'externallinks',
+ 'imagelinks',
+ 'templatelinks',
+ 'iwlinks' ) );
}
- public function setUp() {
+ protected function setUp() {
parent::setUp();
$this->pages_to_delete = array();
+
+ LinkCache::singleton()->clear(); # avoid cached redirect status, etc
}
- public function tearDown() {
+ protected function tearDown() {
foreach ( $this->pages_to_delete as $p ) {
/* @var $p WikiPage */
parent::tearDown();
}
- protected function newPage( $title ) {
- if ( is_string( $title ) ) $title = Title::newFromText( $title );
+ /**
+ * @param Title $title
+ * @param String $model
+ * @return WikiPage
+ */
+ protected function newPage( $title, $model = null ) {
+ if ( is_string( $title ) ) {
+ $title = Title::newFromText( $title );
+ }
$p = new WikiPage( $title );
return $p;
}
+
+ /**
+ * @param String|Title|WikiPage $page
+ * @param String $text
+ * @param int $model
+ *
+ * @return WikiPage
+ */
protected function createPage( $page, $text, $model = null ) {
- if ( is_string( $page ) ) $page = Title::newFromText( $page );
- if ( $page instanceof Title ) $page = $this->newPage( $page );
+ if ( is_string( $page ) ) {
+ $page = Title::newFromText( $page );
+ }
+
+ if ( $page instanceof Title ) {
+ $page = $this->newPage( $page, $model );
+ }
- $page->doEdit( $text, "testing", EDIT_NEW );
+ $content = ContentHandler::makeContent( $text, $page->getTitle(), $model );
+ $page->doEditContent( $content, "testing", EDIT_NEW );
return $page;
}
+ public function testDoEditContent() {
+ $title = Title::newFromText( "WikiPageTest_testDoEditContent" );
+
+ $page = $this->newPage( $title );
+
+ $content = ContentHandler::makeContent( "[[Lorem ipsum]] dolor sit amet, consetetur sadipscing elitr, sed diam "
+ . " nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat.",
+ $title, CONTENT_MODEL_WIKITEXT );
+
+ $page->doEditContent( $content, "[[testing]] 1" );
+
+ $this->assertTrue( $title->getArticleID() > 0, "Title object should have new page id" );
+ $this->assertTrue( $page->getId() > 0, "WikiPage should have new page id" );
+ $this->assertTrue( $title->exists(), "Title object should indicate that the page now exists" );
+ $this->assertTrue( $page->exists(), "WikiPage object should indicate that the page now exists" );
+
+ $id = $page->getId();
+
+ # ------------------------
+ $dbr = wfGetDB( DB_SLAVE );
+ $res = $dbr->select( 'pagelinks', '*', array( 'pl_from' => $id ) );
+ $n = $res->numRows();
+ $res->free();
+
+ $this->assertEquals( 1, $n, 'pagelinks should contain one link from the page' );
+
+ # ------------------------
+ $page = new WikiPage( $title );
+
+ $retrieved = $page->getContent();
+ $this->assertTrue( $content->equals( $retrieved ), 'retrieved content doesn\'t equal original' );
+
+ # ------------------------
+ $content = ContentHandler::makeContent( "At vero eos et accusam et justo duo [[dolores]] et ea rebum. "
+ . "Stet clita kasd [[gubergren]], no sea takimata sanctus est.",
+ $title, CONTENT_MODEL_WIKITEXT );
+
+ $page->doEditContent( $content, "testing 2" );
+
+ # ------------------------
+ $page = new WikiPage( $title );
+
+ $retrieved = $page->getContent();
+ $this->assertTrue( $content->equals( $retrieved ), 'retrieved content doesn\'t equal original' );
+
+ # ------------------------
+ $dbr = wfGetDB( DB_SLAVE );
+ $res = $dbr->select( 'pagelinks', '*', array( 'pl_from' => $id ) );
+ $n = $res->numRows();
+ $res->free();
+
+ $this->assertEquals( 2, $n, 'pagelinks should contain two links from the page' );
+ }
+
public function testDoEdit() {
- $title = Title::newFromText( "WikiPageTest_testDoEdit" );
+ $this->hideDeprecated( "WikiPage::doEdit" );
+ $this->hideDeprecated( "WikiPage::getText" );
+ $this->hideDeprecated( "Revision::getText" );
+
+ //NOTE: assume help namespace will default to wikitext
+ $title = Title::newFromText( "Help:WikiPageTest_testDoEdit" );
$page = $this->newPage( $title );
$text = "[[Lorem ipsum]] dolor sit amet, consetetur sadipscing elitr, sed diam "
- . " nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat.";
+ . " nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat.";
- $page->doEdit( $text, "testing 1" );
+ $page->doEdit( $text, "[[testing]] 1" );
+ $this->assertTrue( $title->getArticleID() > 0, "Title object should have new page id" );
+ $this->assertTrue( $page->getId() > 0, "WikiPage should have new page id" );
$this->assertTrue( $title->exists(), "Title object should indicate that the page now exists" );
$this->assertTrue( $page->exists(), "WikiPage object should indicate that the page now exists" );
$id = $page->getId();
+ # ------------------------
+ $dbr = wfGetDB( DB_SLAVE );
+ $res = $dbr->select( 'pagelinks', '*', array( 'pl_from' => $id ) );
+ $n = $res->numRows();
+ $res->free();
+
+ $this->assertEquals( 1, $n, 'pagelinks should contain one link from the page' );
+
# ------------------------
$page = new WikiPage( $title );
# ------------------------
$text = "At vero eos et accusam et justo duo [[dolores]] et ea rebum. "
- . "Stet clita kasd [[gubergren]], no sea takimata sanctus est.";
+ . "Stet clita kasd [[gubergren]], no sea takimata sanctus est.";
$page->doEdit( $text, "testing 2" );
public function testDoQuickEdit() {
global $wgUser;
- $page = $this->createPage( "WikiPageTest_testDoQuickEdit", "original text" );
+ $this->hideDeprecated( "WikiPage::doQuickEdit" );
+
+ //NOTE: assume help namespace will default to wikitext
+ $page = $this->createPage( "Help:WikiPageTest_testDoQuickEdit", "original text" );
$text = "quick text";
$page->doQuickEdit( $text, $wgUser, "testing q" );
$this->assertEquals( $text, $page->getText() );
}
+ public function testDoQuickEditContent() {
+ global $wgUser;
+
+ $page = $this->createPage( "WikiPageTest_testDoQuickEditContent", "original text", CONTENT_MODEL_WIKITEXT );
+
+ $content = ContentHandler::makeContent( "quick text", $page->getTitle(), CONTENT_MODEL_WIKITEXT );
+ $page->doQuickEditContent( $content, $wgUser, "testing q" );
+
+ # ---------------------
+ $page = new WikiPage( $page->getTitle() );
+ $this->assertTrue( $content->equals( $page->getContent() ) );
+ }
+
public function testDoDeleteArticle() {
- $page = $this->createPage( "WikiPageTest_testDoDeleteArticle", "[[original text]] foo" );
+ $page = $this->createPage( "WikiPageTest_testDoDeleteArticle", "[[original text]] foo", CONTENT_MODEL_WIKITEXT );
$id = $page->getId();
$page->doDeleteArticle( "testing deletion" );
+ $this->assertFalse( $page->getTitle()->getArticleID() > 0, "Title object should now have page id 0" );
+ $this->assertFalse( $page->getId() > 0, "WikiPage should now have page id 0" );
$this->assertFalse( $page->exists(), "WikiPage::exists should return false after page was deleted" );
+ $this->assertNull( $page->getContent(), "WikiPage::getContent should return null after page was deleted" );
$this->assertFalse( $page->getText(), "WikiPage::getText should return false after page was deleted" );
$t = Title::newFromText( $page->getTitle()->getPrefixedText() );
}
public function testDoDeleteUpdates() {
- $page = $this->createPage( "WikiPageTest_testDoDeleteArticle", "[[original text]] foo" );
+ $page = $this->createPage( "WikiPageTest_testDoDeleteArticle", "[[original text]] foo", CONTENT_MODEL_WIKITEXT );
$id = $page->getId();
$page->doDeleteUpdates( $id );
$this->assertNull( $rev );
# -----------------
- $this->createPage( $page, "some text" );
+ $this->createPage( $page, "some text", CONTENT_MODEL_WIKITEXT );
$rev = $page->getRevision();
$this->assertEquals( $page->getLatest(), $rev->getId() );
- $this->assertEquals( "some text", $rev->getText() );
+ $this->assertEquals( "some text", $rev->getContent()->getNativeData() );
+ }
+
+ public function testGetContent() {
+ $page = $this->newPage( "WikiPageTest_testGetContent" );
+
+ $content = $page->getContent();
+ $this->assertNull( $content );
+
+ # -----------------
+ $this->createPage( $page, "some text", CONTENT_MODEL_WIKITEXT );
+
+ $content = $page->getContent();
+ $this->assertEquals( "some text", $content->getNativeData() );
}
public function testGetText() {
+ $this->hideDeprecated( "WikiPage::getText" );
+
$page = $this->newPage( "WikiPageTest_testGetText" );
$text = $page->getText();
$this->assertFalse( $text );
# -----------------
- $this->createPage( $page, "some text" );
+ $this->createPage( $page, "some text", CONTENT_MODEL_WIKITEXT );
$text = $page->getText();
$this->assertEquals( "some text", $text );
}
public function testGetRawText() {
+ $this->hideDeprecated( "WikiPage::getRawText" );
+
$page = $this->newPage( "WikiPageTest_testGetRawText" );
$text = $page->getRawText();
$this->assertFalse( $text );
# -----------------
- $this->createPage( $page, "some text" );
+ $this->createPage( $page, "some text", CONTENT_MODEL_WIKITEXT );
$text = $page->getRawText();
$this->assertEquals( "some text", $text );
}
-
+ public function testGetContentModel() {
+ global $wgContentHandlerUseDB;
+
+ if ( !$wgContentHandlerUseDB ) {
+ $this->markTestSkipped( '$wgContentHandlerUseDB is disabled' );
+ }
+
+ $page = $this->createPage( "WikiPageTest_testGetContentModel", "some text", CONTENT_MODEL_JAVASCRIPT );
+
+ $page = new WikiPage( $page->getTitle() );
+ $this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $page->getContentModel() );
+ }
+
+ public function testGetContentHandler() {
+ global $wgContentHandlerUseDB;
+
+ if ( !$wgContentHandlerUseDB ) {
+ $this->markTestSkipped( '$wgContentHandlerUseDB is disabled' );
+ }
+
+ $page = $this->createPage( "WikiPageTest_testGetContentHandler", "some text", CONTENT_MODEL_JAVASCRIPT );
+
+ $page = new WikiPage( $page->getTitle() );
+ $this->assertEquals( 'JavaScriptContentHandler', get_class( $page->getContentHandler() ) );
+ }
+
public function testExists() {
$page = $this->newPage( "WikiPageTest_testExists" );
$this->assertFalse( $page->exists() );
# -----------------
- $this->createPage( $page, "some text" );
+ $this->createPage( $page, "some text", CONTENT_MODEL_WIKITEXT );
$this->assertTrue( $page->exists() );
$page = new WikiPage( $page->getTitle() );
$this->assertFalse( $page->exists() );
}
- public function dataHasViewableContent() {
+ public static function provideHasViewableContent() {
return array(
array( 'WikiPageTest_testHasViewableContent', false, true ),
array( 'Special:WikiPageTest_testHasViewableContent', false ),
}
/**
- * @dataProvider dataHasViewableContent
+ * @dataProvider provideHasViewableContent
*/
public function testHasViewableContent( $title, $viewable, $create = false ) {
$page = $this->newPage( $title );
$this->assertEquals( $viewable, $page->hasViewableContent() );
if ( $create ) {
- $this->createPage( $page, "some text" );
+ $this->createPage( $page, "some text", CONTENT_MODEL_WIKITEXT );
$this->assertTrue( $page->hasViewableContent() );
$page = new WikiPage( $page->getTitle() );
}
}
- public function dataGetRedirectTarget() {
+ public static function provideGetRedirectTarget() {
return array(
- array( 'WikiPageTest_testGetRedirectTarget_1', "hello world", null ),
- array( 'WikiPageTest_testGetRedirectTarget_2', "#REDIRECT [[hello world]]", "Hello world" ),
+ array( 'WikiPageTest_testGetRedirectTarget_1', CONTENT_MODEL_WIKITEXT, "hello world", null ),
+ array( 'WikiPageTest_testGetRedirectTarget_2', CONTENT_MODEL_WIKITEXT, "#REDIRECT [[hello world]]", "Hello world" ),
);
}
/**
- * @dataProvider dataGetRedirectTarget
+ * @dataProvider provideGetRedirectTarget
*/
- public function testGetRedirectTarget( $title, $text, $target ) {
- $page = $this->createPage( $title, $text );
+ public function testGetRedirectTarget( $title, $model, $text, $target ) {
+ $page = $this->createPage( $title, $text, $model );
+
+ # sanity check, because this test seems to fail for no reason for some people.
+ $c = $page->getContent();
+ $this->assertEquals( 'WikitextContent', get_class( $c ) );
# now, test the actual redirect
$t = $page->getRedirectTarget();
}
/**
- * @dataProvider dataGetRedirectTarget
+ * @dataProvider provideGetRedirectTarget
*/
- public function testIsRedirect( $title, $text, $target ) {
- $page = $this->createPage( $title, $text );
+ public function testIsRedirect( $title, $model, $text, $target ) {
+ $page = $this->createPage( $title, $text, $model );
$this->assertEquals( !is_null( $target ), $page->isRedirect() );
}
- public function dataIsCountable() {
+ public static function provideIsCountable() {
return array(
// any
array( 'WikiPageTest_testIsCountable',
- '',
- 'any',
- true
+ CONTENT_MODEL_WIKITEXT,
+ '',
+ 'any',
+ true
),
array( 'WikiPageTest_testIsCountable',
- 'Foo',
- 'any',
- true
+ CONTENT_MODEL_WIKITEXT,
+ 'Foo',
+ 'any',
+ true
),
// comma
array( 'WikiPageTest_testIsCountable',
- 'Foo',
- 'comma',
- false
+ CONTENT_MODEL_WIKITEXT,
+ 'Foo',
+ 'comma',
+ false
),
array( 'WikiPageTest_testIsCountable',
- 'Foo, bar',
- 'comma',
- true
+ CONTENT_MODEL_WIKITEXT,
+ 'Foo, bar',
+ 'comma',
+ true
),
// link
array( 'WikiPageTest_testIsCountable',
- 'Foo',
- 'link',
- false
+ CONTENT_MODEL_WIKITEXT,
+ 'Foo',
+ 'link',
+ false
),
array( 'WikiPageTest_testIsCountable',
- 'Foo [[bar]]',
- 'link',
- true
+ CONTENT_MODEL_WIKITEXT,
+ 'Foo [[bar]]',
+ 'link',
+ true
),
// redirects
array( 'WikiPageTest_testIsCountable',
- '#REDIRECT [[bar]]',
- 'any',
- false
+ CONTENT_MODEL_WIKITEXT,
+ '#REDIRECT [[bar]]',
+ 'any',
+ false
),
array( 'WikiPageTest_testIsCountable',
- '#REDIRECT [[bar]]',
- 'comma',
- false
+ CONTENT_MODEL_WIKITEXT,
+ '#REDIRECT [[bar]]',
+ 'comma',
+ false
),
array( 'WikiPageTest_testIsCountable',
- '#REDIRECT [[bar]]',
- 'link',
- false
+ CONTENT_MODEL_WIKITEXT,
+ '#REDIRECT [[bar]]',
+ 'link',
+ false
),
// not a content namespace
array( 'Talk:WikiPageTest_testIsCountable',
- 'Foo',
- 'any',
- false
+ CONTENT_MODEL_WIKITEXT,
+ 'Foo',
+ 'any',
+ false
),
array( 'Talk:WikiPageTest_testIsCountable',
- 'Foo, bar',
- 'comma',
- false
+ CONTENT_MODEL_WIKITEXT,
+ 'Foo, bar',
+ 'comma',
+ false
),
array( 'Talk:WikiPageTest_testIsCountable',
- 'Foo [[bar]]',
- 'link',
- false
+ CONTENT_MODEL_WIKITEXT,
+ 'Foo [[bar]]',
+ 'link',
+ false
),
// not a content namespace, different model
array( 'MediaWiki:WikiPageTest_testIsCountable.js',
- 'Foo',
- 'any',
- false
+ null,
+ 'Foo',
+ 'any',
+ false
),
array( 'MediaWiki:WikiPageTest_testIsCountable.js',
- 'Foo, bar',
- 'comma',
- false
+ null,
+ 'Foo, bar',
+ 'comma',
+ false
),
array( 'MediaWiki:WikiPageTest_testIsCountable.js',
- 'Foo [[bar]]',
- 'link',
- false
+ null,
+ 'Foo [[bar]]',
+ 'link',
+ false
),
);
}
/**
- * @dataProvider dataIsCountable
+ * @dataProvider provideIsCountable
*/
- public function testIsCountable( $title, $text, $mode, $expected ) {
+ public function testIsCountable( $title, $model, $text, $mode, $expected ) {
global $wgArticleCountMethod;
- $old = $wgArticleCountMethod;
+ $oldArticleCountMethod = $wgArticleCountMethod;
$wgArticleCountMethod = $mode;
- $page = $this->createPage( $title, $text );
- $editInfo = $page->prepareTextForEdit( $page->getText() );
+ $page = $this->createPage( $title, $text, $model );
+ $hasLinks = wfGetDB( DB_SLAVE )->selectField( 'pagelinks', 1,
+ array( 'pl_from' => $page->getId() ), __METHOD__ );
+
+ $editInfo = $page->prepareContentForEdit( $page->getContent() );
$v = $page->isCountable();
$w = $page->isCountable( $editInfo );
- $wgArticleCountMethod = $old;
+
+ $wgArticleCountMethod = $oldArticleCountMethod;
$this->assertEquals( $expected, $v, "isCountable( null ) returned unexpected value " . var_export( $v, true )
- . " instead of " . var_export( $expected, true ) . " in mode `$mode` for text \"$text\"" );
+ . " instead of " . var_export( $expected, true ) . " in mode `$mode` for text \"$text\"" );
$this->assertEquals( $expected, $w, "isCountable( \$editInfo ) returned unexpected value " . var_export( $v, true )
- . " instead of " . var_export( $expected, true ) . " in mode `$mode` for text \"$text\"" );
+ . " instead of " . var_export( $expected, true ) . " in mode `$mode` for text \"$text\"" );
}
- public function dataGetParserOutput() {
+ public static function provideGetParserOutput() {
return array(
- array("hello ''world''\n", "<p>hello <i>world</i></p>"),
+ array( CONTENT_MODEL_WIKITEXT, "hello ''world''\n", "<p>hello <i>world</i></p>"),
// @todo: more...?
);
}
/**
- * @dataProvider dataGetParserOutput
+ * @dataProvider provideGetParserOutput
*/
- public function testGetParserOutput( $text, $expectedHtml ) {
- $page = $this->createPage( 'WikiPageTest_testGetParserOutput', $text );
+ public function testGetParserOutput( $model, $text, $expectedHtml ) {
+ $page = $this->createPage( 'WikiPageTest_testGetParserOutput', $text, $model );
$opt = new ParserOptions();
$po = $page->getParserOutput( $opt );
return $po;
}
+ /**
+ * @dataProvider provideGetParserOutput
+ */
+ public function testGetParserOutput_nonexisting( ) {
+ static $count = 0;
+ $count ++;
+
+ $page = new WikiPage( new Title( "testGetParserOutput_nonexisting_$count" ) );
+
+ $opt = new ParserOptions();
+ $po = $page->getParserOutput( $opt );
+
+ $this->assertFalse( $po, "getParserOutput() shall return false for non-existing pages." );
+ }
+
static $sections =
"Intro
public function dataReplaceSection() {
+ //NOTE: assume the Help namespace to contain wikitext
return array(
- array( 'WikiPageTest_testReplaceSection',
- WikiPageTest::$sections,
- "0",
- "No more",
- null,
- trim( preg_replace( '/^Intro/sm', 'No more', WikiPageTest::$sections ) )
- ),
- array( 'WikiPageTest_testReplaceSection',
- WikiPageTest::$sections,
- "",
- "No more",
- null,
- "No more"
- ),
- array( 'WikiPageTest_testReplaceSection',
- WikiPageTest::$sections,
- "2",
- "== TEST ==\nmore fun",
- null,
- trim( preg_replace( '/^== test ==.*== foo ==/sm', "== TEST ==\nmore fun\n\n== foo ==", WikiPageTest::$sections ) )
- ),
- array( 'WikiPageTest_testReplaceSection',
- WikiPageTest::$sections,
- "8",
- "No more",
- null,
- trim( WikiPageTest::$sections )
- ),
- array( 'WikiPageTest_testReplaceSection',
- WikiPageTest::$sections,
- "new",
- "No more",
- "New",
- trim( WikiPageTest::$sections ) . "\n\n== New ==\n\nNo more"
+ array( 'Help:WikiPageTest_testReplaceSection',
+ CONTENT_MODEL_WIKITEXT,
+ WikiPageTest::$sections,
+ "0",
+ "No more",
+ null,
+ trim( preg_replace( '/^Intro/sm', 'No more', WikiPageTest::$sections ) )
+ ),
+ array( 'Help:WikiPageTest_testReplaceSection',
+ CONTENT_MODEL_WIKITEXT,
+ WikiPageTest::$sections,
+ "",
+ "No more",
+ null,
+ "No more"
+ ),
+ array( 'Help:WikiPageTest_testReplaceSection',
+ CONTENT_MODEL_WIKITEXT,
+ WikiPageTest::$sections,
+ "2",
+ "== TEST ==\nmore fun",
+ null,
+ trim( preg_replace( '/^== test ==.*== foo ==/sm',
+ "== TEST ==\nmore fun\n\n== foo ==",
+ WikiPageTest::$sections ) )
+ ),
+ array( 'Help:WikiPageTest_testReplaceSection',
+ CONTENT_MODEL_WIKITEXT,
+ WikiPageTest::$sections,
+ "8",
+ "No more",
+ null,
+ trim( WikiPageTest::$sections )
+ ),
+ array( 'Help:WikiPageTest_testReplaceSection',
+ CONTENT_MODEL_WIKITEXT,
+ WikiPageTest::$sections,
+ "new",
+ "No more",
+ "New",
+ trim( WikiPageTest::$sections ) . "\n\n== New ==\n\nNo more"
),
);
}
/**
* @dataProvider dataReplaceSection
*/
- public function testReplaceSection( $title, $text, $section, $with, $sectionTitle, $expected ) {
- $page = $this->createPage( $title, $text );
+ public function testReplaceSection( $title, $model, $text, $section, $with, $sectionTitle, $expected ) {
+ $this->hideDeprecated( "WikiPage::replaceSection" );
+
+ $page = $this->createPage( $title, $text, $model );
$text = $page->replaceSection( $section, $with, $sectionTitle );
$text = trim( $text );
$this->assertEquals( $expected, $text );
}
+ /**
+ * @dataProvider dataReplaceSection
+ */
+ public function testReplaceSectionContent( $title, $model, $text, $section, $with, $sectionTitle, $expected ) {
+ $page = $this->createPage( $title, $text, $model );
+
+ $content = ContentHandler::makeContent( $with, $page->getTitle(), $page->getContentModel() );
+ $c = $page->replaceSectionContent( $section, $content, $sectionTitle );
+
+ $this->assertEquals( $expected, is_null( $c ) ? null : trim( $c->getNativeData() ) );
+ }
+
/* @todo FIXME: fix this!
public function testGetUndoText() {
global $wgDiff3;
$text = "one";
$page = $this->newPage( "WikiPageTest_testDoRollback" );
- $page->doEdit( $text, "section one", EDIT_NEW, false, $admin );
+ $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ),
+ "section one", EDIT_NEW, false, $admin );
$user1 = new User();
$user1->setName( "127.0.1.11" );
$text .= "\n\ntwo";
$page = new WikiPage( $page->getTitle() );
- $page->doEdit( $text, "adding section two", 0, false, $user1 );
+ $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ),
+ "adding section two", 0, false, $user1 );
$user2 = new User();
$user2->setName( "127.0.2.13" );
$text .= "\n\nthree";
$page = new WikiPage( $page->getTitle() );
- $page->doEdit( $text, "adding section three", 0, false, $user2 );
+ $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ),
+ "adding section three", 0, false, $user2 );
# we are having issues with doRollback spuriously failing. apparently the last revision somehow goes missing
# or not committed under some circumstances. so, make sure the last revision has the right user name.
}
$page = new WikiPage( $page->getTitle() );
- $this->assertEquals( $rev2->getSha1(), $page->getRevision()->getSha1(), "rollback did not revert to the correct revision" );
- $this->assertEquals( "one\n\ntwo", $page->getText() );
+ $this->assertEquals( $rev2->getSha1(), $page->getRevision()->getSha1(),
+ "rollback did not revert to the correct revision" );
+ $this->assertEquals( "one\n\ntwo", $page->getContent()->getNativeData() );
}
/**
$text = "one";
$page = $this->newPage( "WikiPageTest_testDoRollback" );
- $page->doEdit( $text, "section one", EDIT_NEW, false, $admin );
+ $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle(), CONTENT_MODEL_WIKITEXT ),
+ "section one", EDIT_NEW, false, $admin );
$rev1 = $page->getRevision();
$user1 = new User();
$user1->setName( "127.0.1.11" );
$text .= "\n\ntwo";
$page = new WikiPage( $page->getTitle() );
- $page->doEdit( $text, "adding section two", 0, false, $user1 );
+ $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle(), CONTENT_MODEL_WIKITEXT ),
+ "adding section two", 0, false, $user1 );
# now, try the rollback
$admin->addGroup( "sysop" ); #XXX: make the test user a sysop...
}
$page = new WikiPage( $page->getTitle() );
- $this->assertEquals( $rev1->getSha1(), $page->getRevision()->getSha1(), "rollback did not revert to the correct revision" );
- $this->assertEquals( "one", $page->getText() );
+ $this->assertEquals( $rev1->getSha1(), $page->getRevision()->getSha1(),
+ "rollback did not revert to the correct revision" );
+ $this->assertEquals( "one", $page->getContent()->getNativeData() );
}
- public function dataGetAutosummary( ) {
+ public static function provideGetAutosummary( ) {
return array(
array(
'Hello there, world!',
}
/**
- * @dataProvider dataGetAutoSummary
+ * @dataProvider provideGetAutoSummary
*/
public function testGetAutosummary( $old, $new, $flags, $expected ) {
+ $this->hideDeprecated( "WikiPage::getAutosummary" );
+
$page = $this->newPage( "WikiPageTest_testGetAutosummary" );
$summary = $page->getAutosummary( $old, $new, $flags );
- $this->assertTrue( (bool)preg_match( $expected, $summary ), "Autosummary didn't match expected pattern $expected: $summary" );
+ $this->assertTrue( (bool)preg_match( $expected, $summary ),
+ "Autosummary didn't match expected pattern $expected: $summary" );
}
- public function dataGetAutoDeleteReason( ) {
+ public static function provideGetAutoDeleteReason( ) {
return array(
array(
array(),
array(
array(
array( "first edit: "
- . "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam "
- . " nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. "
- . "At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea "
- . "takimata sanctus est Lorem ipsum dolor sit amet.'", null ),
+ . "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam "
+ . " nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. "
+ . "At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea "
+ . "takimata sanctus est Lorem ipsum dolor sit amet.'", null ),
),
'/first edit:.*\.\.\."/',
false
}
/**
- * @dataProvider dataGetAutoDeleteReason
+ * @dataProvider provideGetAutoDeleteReason
*/
public function testGetAutoDeleteReason( $edits, $expectedResult, $expectedHistory ) {
global $wgUser;
- $page = $this->newPage( "WikiPageTest_testGetAutoDeleteReason" );
+ //NOTE: assume Help namespace to contain wikitext
+ $page = $this->newPage( "Help:WikiPageTest_testGetAutoDeleteReason" );
$c = 1;
if ( !empty( $edit[1] ) ) $user->setName( $edit[1] );
else $user = $wgUser;
- $page->doEdit( $edit[0], "test edit $c", $c < 2 ? EDIT_NEW : 0, false, $user );
+ $content = ContentHandler::makeContent( $edit[0], $page->getTitle(), $page->getContentModel() );
+
+ $page->doEditContent( $content, "test edit $c", $c < 2 ? EDIT_NEW : 0, false, $user );
$c += 1;
}
$reason = $page->getAutoDeleteReason( $hasHistory );
if ( is_bool( $expectedResult ) || is_null( $expectedResult ) ) $this->assertEquals( $expectedResult, $reason );
- else $this->assertTrue( (bool)preg_match( $expectedResult, $reason ), "Autosummary didn't match expected pattern $expectedResult: $reason" );
+ else $this->assertTrue( (bool)preg_match( $expectedResult, $reason ),
+ "Autosummary didn't match expected pattern $expectedResult: $reason" );
- $this->assertEquals( $expectedHistory, $hasHistory, "expected \$hasHistory to be " . var_export( $expectedHistory, true ) );
+ $this->assertEquals( $expectedHistory, $hasHistory,
+ "expected \$hasHistory to be " . var_export( $expectedHistory, true ) );
$page->doDeleteArticle( "done" );
}
- public function dataPreSaveTransform() {
+ public static function providePreSaveTransform() {
return array(
array( 'hello this is ~~~',
- "hello this is [[Special:Contributions/127.0.0.1|127.0.0.1]]",
+ "hello this is [[Special:Contributions/127.0.0.1|127.0.0.1]]",
),
array( 'hello \'\'this\'\' is <nowiki>~~~</nowiki>',
- 'hello \'\'this\'\' is <nowiki>~~~</nowiki>',
+ 'hello \'\'this\'\' is <nowiki>~~~</nowiki>',
),
);
}
/**
- * @dataProvider dataPreSaveTransform
+ * @dataProvider providePreSaveTransform
*/
public function testPreSaveTransform( $text, $expected ) {
$this->hideDeprecated( 'WikiPage::preSaveTransform' );
$user = new User();
$user->setName("127.0.0.1");
- $page = $this->newPage( "WikiPageTest_testPreloadTransform" );
+ //NOTE: assume Help namespace to contain wikitext
+ $page = $this->newPage( "Help:WikiPageTest_testPreloadTransform" );
$text = $page->preSaveTransform( $text, $user );
$this->assertEquals( $expected, $text );
--- /dev/null
+<?php
+
+/**
+ * @group ContentHandler
+ * @group Database
+ * ^--- important, causes temporary tables to be used instead of the real database
+ */
+class WikiPageTest_ContentHandlerUseDB extends WikiPageTest {
+ var $saveContentHandlerNoDB = null;
+
+ function setUp() {
+ global $wgContentHandlerUseDB;
+
+ parent::setUp();
+
+ $this->saveContentHandlerNoDB = $wgContentHandlerUseDB;
+
+ $wgContentHandlerUseDB = false;
+
+ $dbw = wfGetDB( DB_MASTER );
+
+ $page_table = $dbw->tableName( 'page' );
+ $revision_table = $dbw->tableName( 'revision' );
+ $archive_table = $dbw->tableName( 'archive' );
+
+ if ( $dbw->fieldExists( $page_table, 'page_content_model' ) ) {
+ $dbw->query( "alter table $page_table drop column page_content_model" );
+ $dbw->query( "alter table $revision_table drop column rev_content_model" );
+ $dbw->query( "alter table $revision_table drop column rev_content_format" );
+ $dbw->query( "alter table $archive_table drop column ar_content_model" );
+ $dbw->query( "alter table $archive_table drop column ar_content_format" );
+ }
+ }
+
+ function tearDown() {
+ global $wgContentHandlerUseDB;
+
+ $wgContentHandlerUseDB = $this->saveContentHandlerNoDB;
+
+ parent::tearDown();
+ }
+
+ public function testGetContentModel() {
+ $page = $this->createPage( "WikiPageTest_testGetContentModel", "some text", CONTENT_MODEL_JAVASCRIPT );
+
+ $page = new WikiPage( $page->getTitle() );
+
+ // NOTE: since the content model is not recorded in the database,
+ // we expect to get the default, namely CONTENT_MODEL_WIKITEXT
+ $this->assertEquals( CONTENT_MODEL_WIKITEXT, $page->getContentModel() );
+ }
+
+ public function testGetContentHandler() {
+ $page = $this->createPage( "WikiPageTest_testGetContentHandler", "some text", CONTENT_MODEL_JAVASCRIPT );
+
+ // NOTE: since the content model is not recorded in the database,
+ // we expect to get the default, namely CONTENT_MODEL_WIKITEXT
+ $page = new WikiPage( $page->getTitle() );
+ $this->assertEquals( 'WikitextContentHandler', get_class( $page->getContentHandler() ) );
+ }
+
+}
+
+
--- /dev/null
+<?php
+
+/**
+ * @group ContentHandler
+ */
+class WikitextContentHandlerTest extends MediaWikiTestCase {
+
+ /**
+ * @var ContentHandler
+ */
+ var $handler;
+
+ public function setup() {
+ $this->handler = ContentHandler::getForModelID( CONTENT_MODEL_WIKITEXT );
+ }
+
+ public function teardown() {
+ }
+
+ public function testSerializeContent( ) {
+ $content = new WikitextContent( 'hello world' );
+
+ $this->assertEquals( 'hello world', $this->handler->serializeContent( $content ) );
+ $this->assertEquals( 'hello world', $this->handler->serializeContent( $content, CONTENT_FORMAT_WIKITEXT ) );
+
+ try {
+ $this->handler->serializeContent( $content, 'dummy/foo' );
+ $this->fail( "serializeContent() should have failed on unknown format" );
+ } catch ( MWException $e ) {
+ // ok, as expected
+ }
+ }
+
+ public function testUnserializeContent( ) {
+ $content = $this->handler->unserializeContent( 'hello world' );
+ $this->assertEquals( 'hello world', $content->getNativeData() );
+
+ $content = $this->handler->unserializeContent( 'hello world', CONTENT_FORMAT_WIKITEXT );
+ $this->assertEquals( 'hello world', $content->getNativeData() );
+
+ try {
+ $this->handler->unserializeContent( 'hello world', 'dummy/foo' );
+ $this->fail( "unserializeContent() should have failed on unknown format" );
+ } catch ( MWException $e ) {
+ // ok, as expected
+ }
+ }
+
+ public function testMakeEmptyContent() {
+ $content = $this->handler->makeEmptyContent();
+
+ $this->assertTrue( $content->isEmpty() );
+ $this->assertEquals( '', $content->getNativeData() );
+ }
+
+ public function dataIsSupportedFormat( ) {
+ return array(
+ array( null, true ),
+ array( CONTENT_FORMAT_WIKITEXT, true ),
+ array( 99887766, false ),
+ );
+ }
+
+ /**
+ * @dataProvider dataIsSupportedFormat
+ */
+ public function testIsSupportedFormat( $format, $supported ) {
+ $this->assertEquals( $supported, $this->handler->isSupportedFormat( $format ) );
+ }
+
+ public function dataMerge3( ) {
+ return array(
+ array( "first paragraph
+
+ second paragraph\n",
+
+ "FIRST paragraph
+
+ second paragraph\n",
+
+ "first paragraph
+
+ SECOND paragraph\n",
+
+ "FIRST paragraph
+
+ SECOND paragraph\n",
+ ),
+
+ array( "first paragraph
+ second paragraph\n",
+
+ "Bla bla\n",
+
+ "Blubberdibla\n",
+
+ false,
+ ),
+
+ );
+ }
+
+ /**
+ * @dataProvider dataMerge3
+ */
+ public function testMerge3( $old, $mine, $yours, $expected ) {
+ global $wgDiff3;
+
+ if ( !$wgDiff3 ) {
+ $this->markTestSkipped( "Can't test merge3(), since \$wgDiff3 is not configured" );
+ }
+
+ if ( !file_exists( $wgDiff3 ) ) {
+ #XXX: this sucks, since it uses arcane internal knowledge about TextContentHandler::merge3 and wfMerge.
+ $this->markTestSkipped( "Can't test merge3(), since \$wgDiff3 is misconfigured: can't find $wgDiff3" );
+ }
+
+ // test merge
+ $oldContent = new WikitextContent( $old );
+ $myContent = new WikitextContent( $mine );
+ $yourContent = new WikitextContent( $yours );
+
+ $merged = $this->handler->merge3( $oldContent, $myContent, $yourContent );
+
+ $this->assertEquals( $expected, $merged ? $merged->getNativeData() : $merged );
+ }
+
+ public function dataGetAutosummary( ) {
+ return array(
+ array(
+ 'Hello there, world!',
+ '#REDIRECT [[Foo]]',
+ 0,
+ '/^Redirected page .*Foo/'
+ ),
+
+ array(
+ null,
+ 'Hello world!',
+ EDIT_NEW,
+ '/^Created page .*Hello/'
+ ),
+
+ array(
+ 'Hello there, world!',
+ '',
+ 0,
+ '/^Blanked/'
+ ),
+
+ array(
+ 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut
+ labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et
+ ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.',
+ 'Hello world!',
+ 0,
+ '/^Replaced .*Hello/'
+ ),
+
+ array(
+ 'foo',
+ 'bar',
+ 0,
+ '/^$/'
+ ),
+ );
+ }
+
+ /**
+ * @dataProvider dataGetAutoSummary
+ */
+ public function testGetAutosummary( $old, $new, $flags, $expected ) {
+ global $wgLanguageCode, $wgContLang;
+
+ $oldContent = is_null( $old ) ? null : new WikitextContent( $old );
+ $newContent = is_null( $new ) ? null : new WikitextContent( $new );
+
+ $summary = $this->handler->getAutosummary( $oldContent, $newContent, $flags );
+
+ $this->assertTrue( (bool)preg_match( $expected, $summary ), "Autosummary didn't match expected pattern $expected: $summary" );
+ }
+
+ /**
+ * @todo Text case requires database, should be done by a test class in the Database group
+ */
+ /*
+ public function testGetAutoDeleteReason( Title $title, &$hasHistory ) {
+ }
+ */
+
+ /**
+ * @todo Text case requires database, should be done by a test class in the Database group
+ */
+ /*
+ public function testGetUndoContent( Revision $current, Revision $undo, Revision $undoafter = null ) {
+ }
+ */
+
+}
--- /dev/null
+<?php
+
+/**
+ * @group ContentHandler
+ *
+ * @group Database
+ * ^--- needed, because we do need the database to test link updates
+ */
+class WikitextContentTest extends TextContentTest {
+
+ public function newContent( $text ) {
+ return new WikitextContent( $text );
+ }
+
+ public function dataGetParserOutput() {
+ return array(
+ array("WikitextContentTest_testGetParserOutput", CONTENT_MODEL_WIKITEXT, "hello ''world''\n", "<p>hello <i>world</i>\n</p>"),
+ // @todo: more...?
+ );
+ }
+
+ public function dataGetSecondaryDataUpdates() {
+ return array(
+ array("WikitextContentTest_testGetSecondaryDataUpdates_1",
+ CONTENT_MODEL_WIKITEXT, "hello ''world''\n",
+ array( 'LinksUpdate' => array( 'mRecursive' => true,
+ 'mLinks' => array() ) )
+ ),
+ array("WikitextContentTest_testGetSecondaryDataUpdates_2",
+ CONTENT_MODEL_WIKITEXT, "hello [[world test 21344]]\n",
+ array( 'LinksUpdate' => array( 'mRecursive' => true,
+ 'mLinks' => array( array( 'World_test_21344' => 0 ) ) ) )
+ ),
+ // @todo: more...?
+ );
+ }
+
+ /**
+ * @dataProvider dataGetSecondaryDataUpdates
+ * @group Database
+ */
+ public function testGetSecondaryDataUpdates( $title, $model, $text, $expectedStuff ) {
+ $title = Title::newFromText( $title );
+ $title->resetArticleID( 2342 ); //dummy id. fine as long as we don't try to execute the updates!
+
+ $content = ContentHandler::makeContent( $text, $title, $model );
+
+ $updates = $content->getSecondaryDataUpdates( $title );
+
+ // make updates accessible by class name
+ foreach ( $updates as $update ) {
+ $class = get_class( $update );
+ $updates[$class] = $update;
+ }
+
+ foreach ( $expectedStuff as $class => $fieldValues ) {
+ $this->assertArrayHasKey( $class, $updates, "missing an update of type $class" );
+
+ $update = $updates[$class];
+
+ foreach ( $fieldValues as $field => $value ) {
+ $v = $update->$field; #if the field doesn't exist, just crash and burn
+ $this->assertEquals( $value, $v, "unexpected value for field $field in instance of $class" );
+ }
+ }
+ }
+
+
+ static $sections =
+
+"Intro
+
+== stuff ==
+hello world
+
+== test ==
+just a test
+
+== foo ==
+more stuff
+";
+
+ public function dataGetSection() {
+ return array(
+ array( WikitextContentTest::$sections,
+ "0",
+ "Intro"
+ ),
+ array( WikitextContentTest::$sections,
+ "2",
+"== test ==
+just a test"
+ ),
+ array( WikitextContentTest::$sections,
+ "8",
+ false
+ ),
+ );
+ }
+
+ /**
+ * @dataProvider dataGetSection
+ */
+ public function testGetSection( $text, $sectionId, $expectedText ) {
+ $content = $this->newContent( $text );
+
+ $sectionContent = $content->getSection( $sectionId );
+ if ( is_object( $sectionContent ) ) {
+ $sectionText = $sectionContent->getNativeData();
+ } else {
+ $sectionText = $sectionContent;
+ }
+
+ $this->assertEquals( $expectedText, $sectionText );
+ }
+
+ public function dataReplaceSection() {
+ return array(
+ array( WikitextContentTest::$sections,
+ "0",
+ "No more",
+ null,
+ trim( preg_replace( '/^Intro/sm', 'No more', WikitextContentTest::$sections ) )
+ ),
+ array( WikitextContentTest::$sections,
+ "",
+ "No more",
+ null,
+ "No more"
+ ),
+ array( WikitextContentTest::$sections,
+ "2",
+ "== TEST ==\nmore fun",
+ null,
+ trim( preg_replace( '/^== test ==.*== foo ==/sm', "== TEST ==\nmore fun\n\n== foo ==", WikitextContentTest::$sections ) )
+ ),
+ array( WikitextContentTest::$sections,
+ "8",
+ "No more",
+ null,
+ WikitextContentTest::$sections
+ ),
+ array( WikitextContentTest::$sections,
+ "new",
+ "No more",
+ "New",
+ trim( WikitextContentTest::$sections ) . "\n\n\n== New ==\n\nNo more"
+ ),
+ );
+ }
+
+ /**
+ * @dataProvider dataReplaceSection
+ */
+ public function testReplaceSection( $text, $section, $with, $sectionTitle, $expected ) {
+ $content = $this->newContent( $text );
+ $c = $content->replaceSection( $section, $this->newContent( $with ), $sectionTitle );
+
+ $this->assertEquals( $expected, is_null( $c ) ? null : $c->getNativeData() );
+ }
+
+ public function testAddSectionHeader( ) {
+ $content = $this->newContent( 'hello world' );
+ $content = $content->addSectionHeader( 'test' );
+
+ $this->assertEquals( "== test ==\n\nhello world", $content->getNativeData() );
+ }
+
+ public function dataPreSaveTransform() {
+ return array(
+ array( 'hello this is ~~~',
+ "hello this is [[Special:Contributions/127.0.0.1|127.0.0.1]]",
+ ),
+ array( 'hello \'\'this\'\' is <nowiki>~~~</nowiki>',
+ 'hello \'\'this\'\' is <nowiki>~~~</nowiki>',
+ ),
+ );
+ }
+
+ public function dataPreloadTransform() {
+ return array(
+ array( 'hello this is ~~~',
+ "hello this is ~~~",
+ ),
+ array( 'hello \'\'this\'\' is <noinclude>foo</noinclude><includeonly>bar</includeonly>',
+ 'hello \'\'this\'\' is bar',
+ ),
+ );
+ }
+
+ public function dataGetRedirectTarget() {
+ return array(
+ array( '#REDIRECT [[Test]]',
+ 'Test',
+ ),
+ array( '#REDIRECT Test',
+ null,
+ ),
+ array( '* #REDIRECT [[Test]]',
+ null,
+ ),
+ );
+ }
+
+ public function dataGetTextForSummary() {
+ return array(
+ array( "hello\nworld.",
+ 16,
+ 'hello world.',
+ ),
+ array( 'hello world.',
+ 8,
+ 'hello...',
+ ),
+ array( '[[hello world]].',
+ 8,
+ 'hel...',
+ ),
+ );
+ }
+
+ /**
+ * @todo: test needs database! Should be done by a test class in the Database group.
+ */
+ /*
+ public function getRedirectChain() {
+ $text = $this->getNativeData();
+ return Title::newFromRedirectArray( $text );
+ }
+ */
+
+ /**
+ * @todo: test needs database! Should be done by a test class in the Database group.
+ */
+ /*
+ public function getUltimateRedirectTarget() {
+ $text = $this->getNativeData();
+ return Title::newFromRedirectRecurse( $text );
+ }
+ */
+
+
+ public function dataIsCountable() {
+ return array(
+ array( '',
+ null,
+ 'any',
+ true
+ ),
+ array( 'Foo',
+ null,
+ 'any',
+ true
+ ),
+ array( 'Foo',
+ null,
+ 'comma',
+ false
+ ),
+ array( 'Foo, bar',
+ null,
+ 'comma',
+ true
+ ),
+ array( 'Foo',
+ null,
+ 'link',
+ false
+ ),
+ array( 'Foo [[bar]]',
+ null,
+ 'link',
+ true
+ ),
+ array( 'Foo',
+ true,
+ 'link',
+ true
+ ),
+ array( 'Foo [[bar]]',
+ false,
+ 'link',
+ false
+ ),
+ array( '#REDIRECT [[bar]]',
+ true,
+ 'any',
+ false
+ ),
+ array( '#REDIRECT [[bar]]',
+ true,
+ 'comma',
+ false
+ ),
+ array( '#REDIRECT [[bar]]',
+ true,
+ 'link',
+ false
+ ),
+ );
+ }
+
+ public function testMatchMagicWord( ) {
+ $mw = MagicWord::get( "staticredirect" );
+
+ $content = $this->newContent( "#REDIRECT [[FOO]]\n__STATICREDIRECT__" );
+ $this->assertTrue( $content->matchMagicWord( $mw ), "should have matched magic word" );
+
+ $content = $this->newContent( "#REDIRECT [[FOO]]" );
+ $this->assertFalse( $content->matchMagicWord( $mw ), "should not have matched magic word" );
+ }
+
+ public function testUpdateRedirect( ) {
+ $target = Title::newFromText( "testUpdateRedirect_target" );
+
+ // test with non-redirect page
+ $content = $this->newContent( "hello world." );
+ $newContent = $content->updateRedirect( $target );
+
+ $this->assertTrue( $content->equals( $newContent ), "content should be unchanged" );
+
+ // test with actual redirect
+ $content = $this->newContent( "#REDIRECT [[Someplace]]" );
+ $newContent = $content->updateRedirect( $target );
+
+ $this->assertFalse( $content->equals( $newContent ), "content should have changed" );
+ $this->assertTrue( $newContent->isRedirect(), "new content should be a redirect" );
+
+ $this->assertEquals( $target->getFullText(), $newContent->getRedirectTarget()->getFullText() );
+ }
+
+ # =================================================================================================================
+
+ public function testGetModel() {
+ $content = $this->newContent( "hello world." );
+
+ $this->assertEquals( CONTENT_MODEL_WIKITEXT, $content->getModel() );
+ }
+
+ public function testGetContentHandler() {
+ $content = $this->newContent( "hello world." );
+
+ $this->assertEquals( CONTENT_MODEL_WIKITEXT, $content->getContentHandler()->getModelID() );
+ }
+
+ public function dataEquals( ) {
+ return array(
+ array( new WikitextContent( "hallo" ), null, false ),
+ array( new WikitextContent( "hallo" ), new WikitextContent( "hallo" ), true ),
+ array( new WikitextContent( "hallo" ), new JavascriptContent( "hallo" ), false ),
+ array( new WikitextContent( "hallo" ), new TextContent( "hallo" ), false ),
+ array( new WikitextContent( "hallo" ), new WikitextContent( "HALLO" ), false ),
+ );
+ }
+
+ public function dataGetDeletionUpdates() {
+ return array(
+ array("WikitextContentTest_testGetSecondaryDataUpdates_1",
+ CONTENT_MODEL_WIKITEXT, "hello ''world''\n",
+ array( 'LinksDeletionUpdate' => array( ) )
+ ),
+ array("WikitextContentTest_testGetSecondaryDataUpdates_2",
+ CONTENT_MODEL_WIKITEXT, "hello [[world test 21344]]\n",
+ array( 'LinksDeletionUpdate' => array( ) )
+ ),
+ // @todo: more...?
+ );
+ }
+
+}
* Provides a fourth parameters representing the expected HTML output
*
*/
- public function provideConstructionParameters() {
+ public static function provideConstructionParameters() {
return array(
/**
* Values are set following a 3-bit Gray code where two successive
private static $oldLang;
private static $oldNamespaces;
- public function setUp() {
- global $wgLang, $wgContLang;
+ protected function setUp() {
+ parent::setUp();
- self::$oldLang = $wgLang;
- $wgLang = Language::factory( 'en' );
-
- // Hardcode namespaces during test runs,
- // so that html output based on existing namespaces
- // can be properly evaluated.
- self::$oldNamespaces = $wgContLang->getNamespaces();
- $wgContLang->setNamespaces( array(
+ $langObj = Language::factory( 'en' );
+ $langObj->setNamespaces( array(
-2 => 'Media',
-1 => 'Special',
0 => '',
100 => 'Custom',
101 => 'Custom_talk',
) );
- }
- public function tearDown() {
- global $wgLang, $wgContLang;
- $wgLang = self::$oldLang;
-
- $wgContLang->setNamespaces( self::$oldNamespaces );
+ $this->setMwGlobals( array(
+ 'wgLang' => $langObj,
+ ) );
}
public function testExpandAttributes() {
class ZipDirectoryReaderTest extends MediaWikiTestCase {
var $zipDir, $entries;
- function setUp() {
+ protected function setUp() {
$this->zipDir = __DIR__ . '/../data/zip';
}
*/
class ApiBlockTest extends ApiTestCase {
- function setUp() {
+ protected function setUp() {
parent::setUp();
$this->doLogin();
}
*/
class ApiEditPageTest extends ApiTestCase {
- function setUp() {
- parent::setUp();
+ public function setup() {
+ global $wgExtraNamespaces, $wgNamespaceContentModels, $wgContentHandlers, $wgContLang;
+
+ parent::setup();
+
+ $wgExtraNamespaces[12312] = 'Dummy';
+ $wgExtraNamespaces[12313] = 'Dummy_talk';
+
+ $wgNamespaceContentModels[12312] = "testing";
+ $wgContentHandlers["testing"] = 'DummyContentHandlerForTesting';
+
+ MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache
+ $wgContLang->resetNamespaces(); # reset namespace cache
+
$this->doLogin();
}
+ public function teardown() {
+ global $wgExtraNamespaces, $wgNamespaceContentModels, $wgContentHandlers, $wgContLang;
+
+ unset( $wgExtraNamespaces[12312] );
+ unset( $wgExtraNamespaces[12313] );
+
+ unset( $wgNamespaceContentModels[12312] );
+ unset( $wgContentHandlers["testing"] );
+
+ MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache
+ $wgContLang->resetNamespaces(); # reset namespace cache
+
+ parent::teardown();
+ }
+
function testEdit( ) {
- $name = 'ApiEditPageTest_testEdit';
+ $name = 'Help:ApiEditPageTest_testEdit'; // assume Help namespace to default to wikitext
// -- test new page --------------------------------------------
$apiResult = $this->doApiRequestWithToken( array(
'text' => 'some text', ) );
$apiResult = $apiResult[0];
- # Validate API result data
+ // Validate API result data
$this->assertArrayHasKey( 'edit', $apiResult );
$this->assertArrayHasKey( 'result', $apiResult['edit'] );
$this->assertEquals( 'Success', $apiResult['edit']['result'] );
);
}
- function testEditAppend() {
- $this->markTestIncomplete( "not yet implemented" );
+ function testNonTextEdit( ) {
+ $name = 'Dummy:ApiEditPageTest_testNonTextEdit';
+ $data = serialize( 'some bla bla text' );
+
+ // -- test new page --------------------------------------------
+ $apiResult = $this->doApiRequestWithToken( array(
+ 'action' => 'edit',
+ 'title' => $name,
+ 'text' => $data, ) );
+ $apiResult = $apiResult[0];
+
+ // Validate API result data
+ $this->assertArrayHasKey( 'edit', $apiResult );
+ $this->assertArrayHasKey( 'result', $apiResult['edit'] );
+ $this->assertEquals( 'Success', $apiResult['edit']['result'] );
+
+ $this->assertArrayHasKey( 'new', $apiResult['edit'] );
+ $this->assertArrayNotHasKey( 'nochange', $apiResult['edit'] );
+
+ $this->assertArrayHasKey( 'pageid', $apiResult['edit'] );
+
+ // validate resulting revision
+ $page = WikiPage::factory( Title::newFromText( $name ) );
+ $this->assertEquals( "testing", $page->getContentModel() );
+ $this->assertEquals( $data, $page->getContent()->serialize() );
}
- function testEditSection() {
- $this->markTestIncomplete( "not yet implemented" );
+ static function provideEditAppend() {
+ return array(
+ array( #0: append
+ 'foo', 'append', 'bar', "foobar"
+ ),
+ array( #1: prepend
+ 'foo', 'prepend', 'bar', "barfoo"
+ ),
+ array( #2: append to empty page
+ '', 'append', 'foo', "foo"
+ ),
+ array( #3: prepend to empty page
+ '', 'prepend', 'foo', "foo"
+ ),
+ array( #4: append to non-existing page
+ null, 'append', 'foo', "foo"
+ ),
+ array( #5: prepend to non-existing page
+ null, 'prepend', 'foo', "foo"
+ ),
+ );
}
- function testUndo() {
+ /**
+ * @dataProvider provideEditAppend
+ */
+ function testEditAppend( $text, $op, $append, $expected ) {
+ static $count = 0;
+ $count++;
+
+ // assume NS_HELP defaults to wikitext
+ $name = "Help:ApiEditPageTest_testEditAppend_$count";
+
+ // -- create page (or not) -----------------------------------------
+ if ( $text !== null ) {
+ if ( $text === '' ) {
+ // can't create an empty page, so create it with some content
+ list( $re,, ) = $this->doApiRequestWithToken( array(
+ 'action' => 'edit',
+ 'title' => $name,
+ 'text' => '(dummy)', ) );
+ }
+
+ list( $re,, ) = $this->doApiRequestWithToken( array(
+ 'action' => 'edit',
+ 'title' => $name,
+ 'text' => $text, ) );
+
+ $this->assertEquals( 'Success', $re['edit']['result'] ); // sanity
+ }
+
+ // -- try append/prepend --------------------------------------------
+ list( $re,, ) = $this->doApiRequestWithToken( array(
+ 'action' => 'edit',
+ 'title' => $name,
+ $op . 'text' => $append, ) );
+
+ $this->assertEquals( 'Success', $re['edit']['result'] );
+
+ // -- validate -----------------------------------------------------
+ $page = new WikiPage( Title::newFromText( $name ) );
+ $content = $page->getContent();
+ $this->assertNotNull( $content, 'Page should have been created' );
+
+ $text = $content->getNativeData();
+
+ $this->assertEquals( $expected, $text );
+ }
+
+ function testEditSection() {
$this->markTestIncomplete( "not yet implemented" );
}
- function testEditNonText() {
+ function testUndo() {
$this->markTestIncomplete( "not yet implemented" );
}
}
--- /dev/null
+<?php
+class ApiGeneratorTest extends MediaWikiTestCase {
+
+ /**
+ * Helper to easily get an ApiQuery object instance
+ */
+ function getApiQuery() {
+ // Initialize an ApiQuery object to play with
+ $main = new ApiMain( new FauxRequest() );
+ return new ApiQuery( $main, 'foo', 'bar' );
+ }
+
+ /**
+ * Test whether all registered query modules which are subclasses of
+ * ApiQueryGeneratorBase are listed as being a generator. Registration is
+ * done:
+ * - for core: add it to ApiQuery::$mQueryGenerators
+ * - for extension: by adding to $wgAPIGeneratorModules
+ *
+ * @dataProvider provideApiquerygeneratorbaseChilds
+ */
+ public function testApiquerygeneatorbaseModulesListedAsGenerators(
+ $moduleName, $moduleClass
+ ) {
+ $generators = $this->getApiQuery()->getGenerators();
+ $this->assertArrayHasKey( $moduleName, $generators,
+ "API module '$moduleName' of class '$moduleClass' (an ApiQueryGeneratorBase subclass) must be listed in ApiQuery::\$mQueryGenerators or added to \$wgAPIGeneratorModules."
+ );
+ }
+
+ /**
+ * Returns API modules which are subclassing ApiQueryGeneratorBase.
+ * Case format is:
+ * (moduleName, moduleClass)
+ */
+ public function provideApiquerygeneratorbaseChilds() {
+ $cases = array();
+ $modules = $this->getApiQuery()->getModules();
+ foreach( $modules as $moduleName => $moduleClass ) {
+ if( !is_subclass_of( $moduleClass, 'ApiQueryGeneratorBase' ) ) {
+ continue;
+ }
+ $cases[] = array( $moduleName, $moduleClass );
+ }
+ return $cases;
+ }
+
+ /**
+ * @dataProvider provideListedApiqueryGenerators
+ */
+ public function testGeneratorsAreApiquerygeneratorbaseSubclasses(
+ $generatorName, $generatorClass
+ ) {
+ $modules = $this->getApiQuery()->getModules();
+ $this->assertArrayHasKey( $generatorName, $modules,
+ "Class '$generatorClass' of generator '$generatorName' must be a subclass of 'ApiQueryGeneratorBase'. Listed either in ApiQuery::\$mQueryGenerators or in \$wgAPIGeneratorModules."
+ );
+
+ }
+
+ /**
+ * Return ApiQuery generators, either listed in ApiQuery or registered
+ * via wgAPIGeneratorModules.
+ * Case format is:
+ * (moduleName, $moduleClass).
+ */
+ public function provideListedApiqueryGenerators() {
+ $cases = array();
+ $generators = $this->getApiQuery()->getGenerators();
+ foreach( $generators as $generatorName => $generatorClass ) {
+ $cases[] = array( $generatorName, $generatorClass );
+ }
+ return $cases;
+ }
+
+}
private static $Success = array( 'options' => 'success' );
- function setUp() {
+ protected function setUp() {
parent::setUp();
$this->mUserMock = $this->getMockBuilder( 'User' )
*/
class ApiPurgeTest extends ApiTestCase {
- function setUp() {
+ protected function setUp() {
parent::setUp();
$this->doLogin();
}
*/
class ApiQueryTest extends ApiTestCase {
- function setUp() {
+ protected function setUp() {
parent::setUp();
$this->doLogin();
}
*/
protected $apiContext;
- function setUp() {
+ protected function setUp() {
global $wgContLang, $wgAuth, $wgMemc, $wgRequest, $wgUser, $wgServer;
parent::setUp();
/**
* Fixture -- run before every test
*/
- public function setUp() {
- global $wgEnableUploads, $wgEnableAPI;
+ protected function setUp() {
parent::setUp();
- $wgEnableUploads = true;
- $wgEnableAPI = true;
+ $this->setMwGlobals( array(
+ 'wgEnableUploads' => true,
+ 'wgEnableAPI' => true,
+ ) );
+
wfSetupSession();
$this->clearFakeUploads();
}
- public function tearDown() {
+ protected function tearDown() {
$this->clearTempUpload();
+
+ parent::tearDown();
}
/**
return $this->deleteFileByTitle( Title::newFromText( $fileName, NS_FILE ) );
}
-
/**
* Helper function -- given a file on the filesystem, find matching content in the db (and associated articles) and remove them.
* @param $filePath String: path to file on the filesystem
return true;
}
+
function fakeUploadChunk( $fieldName, $fileName, $type, & $chunkData ){
$tmpName = tempnam( wfTempDir(), "" );
// copy the chunk data to temp location:
$_FILES = array();
}
-
-
-
}
*/
class ApiWatchTest extends ApiTestCase {
- function setUp() {
+ protected function setUp() {
parent::setUp();
$this->doLogin();
}
$data = $this->doApiRequest( array(
'action' => 'edit',
- 'title' => 'UTPage',
+ 'title' => 'Help:UTPage', // Help namespace is hopefully wikitext
'text' => 'new text',
'token' => $pageinfo['edittoken'],
'watchlist' => 'watch' ) );
$data = $this->doApiRequest( array(
'action' => 'protect',
'token' => $pageinfo['protecttoken'],
- 'title' => 'UTPage',
+ 'title' => 'Help:UTPage',
'protections' => 'edit=sysop',
'watchlist' => 'unwatch' ) );
$pageinfo = $this->getTokens();
- if ( !Title::newFromText( 'UTPage' )->exists() ) {
- $this->markTestSkipped( "The article [[UTPage]] does not exist" ); //TODO: just create it?
+ if ( !Title::newFromText( 'Help:UTPage' )->exists() ) {
+ $this->markTestSkipped( "The article [[Help:UTPage]] does not exist" ); //TODO: just create it?
}
$data = $this->doApiRequest( array(
'action' => 'query',
'prop' => 'revisions',
- 'titles' => 'UTPage',
+ 'titles' => 'Help:UTPage',
'rvtoken' => 'rollback' ) );
$this->assertArrayHasKey( 'query', $data[0] );
$key = array_pop( $keys );
if ( isset( $data[0]['query']['pages'][$key]['missing'] ) ) {
- $this->markTestSkipped( "Target page (UTPage) doesn't exist" );
+ $this->markTestSkipped( "Target page (Help:UTPage) doesn't exist" );
}
$this->assertArrayHasKey( 'pageid', $data[0]['query']['pages'][$key] );
try {
$data = $this->doApiRequest( array(
'action' => 'rollback',
- 'title' => 'UTPage',
+ 'title' => 'Help:UTPage',
'user' => $revinfo['user'],
'token' => $pageinfo['rollbacktoken'],
'watchlist' => 'watch' ) );
$this->assertArrayHasKey( 'title', $data[0]['rollback'] );
} catch( UsageException $ue ) {
if( $ue->getCodeString() == 'onlyauthor' ) {
- $this->markTestIncomplete( "Only one author to 'UTPage', cannot test rollback" );
+ $this->markTestIncomplete( "Only one author to 'Help:UTPage', cannot test rollback" );
} else {
$this->fail( "Received error '" . $ue->getCodeString() . "'" );
}
$data = $this->doApiRequest( array(
'action' => 'delete',
'token' => $pageinfo['deletetoken'],
- 'title' => 'UTPage' ) );
+ 'title' => 'Help:UTPage' ) );
$this->assertArrayHasKey( 'delete', $data[0] );
$this->assertArrayHasKey( 'title', $data[0]['delete'] );
*/
class GenderCacheTest extends MediaWikiLangTestCase {
- function setUp() {
+ protected function setUp() {
global $wgDefaultUserOptions;
parent::setUp();
//ensure the correct default gender
/**
* test usernames
*
- * @dataProvider dataUserName
+ * @dataProvider provideUserGenders
*/
function testUserName( $username, $expectedGender ) {
$genderCache = GenderCache::singleton();
/**
* genderCache should work with user objects, too
*
- * @dataProvider dataUserName
+ * @dataProvider provideUserGenders
*/
function testUserObjects( $username, $expectedGender ) {
$genderCache = GenderCache::singleton();
$this->assertEquals( $gender, $expectedGender, "GenderCache normal" );
}
- function dataUserName() {
+ public static function provideUserGenders() {
return array(
array( 'UTMale', 'male' ),
array( 'UTFemale', 'female' ),
* test strip of subpages to avoid unnecessary queries
* against the never existing username
*
- * @dataProvider dataStripSubpages
+ * @dataProvider provideStripSubpages
*/
function testStripSubpages( $pageWithSubpage, $expectedGender ) {
$genderCache = GenderCache::singleton();
$this->assertEquals( $gender, $expectedGender, "GenderCache must strip of subpages" );
}
- function dataStripSubpages() {
+ public static function provideStripSubpages() {
return array(
array( 'UTMale/subpage', 'male' ),
array( 'UTFemale/subpage', 'female' ),
/**
* Value which are forbidden by the constructor
*/
- function provideInvalidConstructorArg() {
+ public static function provideInvalidConstructorArg() {
return array(
array( null ),
array( array() ),
/**
* Provider for testFillingCache
*/
- function provideCacheFilling() {
+ public static function provideCacheFilling() {
// ($cacheMaxEntries, $entryToFill, $msg='')
return array(
array( 1, 0 ),
*/
class DatabaseSQLTest extends MediaWikiTestCase {
- public function setUp() {
+ protected function setUp() {
// TODO support other DBMS or find another way to do it
- if( $this->db->getType() !== 'mysql' ) {
+ if ( $this->db->getType() !== 'mysql' ) {
$this->markTestSkipped( 'No mysql database' );
}
}
/**
- * @dataProvider dataSelectSQLText
+ * @dataProvider provideSelectSQLText
*/
function testSelectSQLText( $sql, $sqlText ) {
$this->assertEquals( trim( $this->db->selectSQLText(
) ), $sqlText );
}
- function dataSelectSQLText() {
+ public static function provideSelectSQLText() {
return array(
array(
array(
}
/**
- * @dataProvider dataConditional
+ * @dataProvider provideConditional
*/
function testConditional( $sql, $sqlText ) {
$this->assertEquals( trim( $this->db->conditional(
) ), $sqlText );
}
- function dataConditional() {
+ public static function provideConditional() {
return array(
array(
array(
class DatabaseSqliteTest extends MediaWikiTestCase {
var $db;
- public function setUp() {
+ protected function setUp() {
+ parent::setUp();
+
if ( !Sqlite::isPresent() ) {
$this->markTestSkipped( 'No SQLite support detected' );
}
class DatabaseTest extends MediaWikiTestCase {
var $db, $functionTest = false;
- function setUp() {
+ protected function setUp() {
$this->db = wfGetDB( DB_MASTER );
}
- function tearDown() {
+ protected function tearDown() {
if ( $this->functionTest ) {
$this->dropFunctions();
$this->functionTest = false;
return TestORMTable::singleton();
}
- public function setUp() {
+ protected function setUp() {
parent::setUp();
$dbw = wfGetDB( DB_MASTER );
class MWDebugTest extends MediaWikiTestCase {
- function setUp() {
+ protected function setUp() {
// Make sure MWDebug class is enabled
static $MWDebugEnabled = false;
if( !$MWDebugEnabled ) {
wfSuppressWarnings();
}
- function tearDown() {
+ protected function tearDown() {
wfRestoreWarnings();
}
private $filesToPrune = array();
private static $backendToUse;
- function setUp() {
+ protected function setUp() {
global $wgFileBackends;
parent::setUp();
$tmpPrefix = wfTempDir() . '/filebackend-unittest-' . time() . '-' . mt_rand();
$this->filesToPrune = array();
}
- private function baseStorePath() {
+ private static function baseStorePath() {
return 'mwstore://localtesting';
}
"FileBackend::extensionFromPath on path '$path'" );
}
- function provider_testExtensionFromPath() {
+ public static function provider_testExtensionFromPath() {
return array(
array( 'mwstore://backend/container/path.txt', 'txt' ),
array( 'mwstore://backend/container/path.svg.png', 'png' ),
$this->assertBackendPathsConsistent( array( $dest ) );
}
- public function provider_testStore() {
+ public static function provider_testStore() {
$cases = array();
$tmpName = TempFSFile::factory( "unittests_", 'txt' )->getPath();
- $toPath = $this->baseStorePath() . '/unittest-cont1/e/fun/obj1.txt';
+ $toPath = self::baseStorePath() . '/unittest-cont1/e/fun/obj1.txt';
$op = array( 'op' => 'store', 'src' => $tmpName, 'dst' => $toPath );
$cases[] = array(
$op, // operation
$this->assertBackendPathsConsistent( array( $source, $dest ) );
}
- public function provider_testCopy() {
+ public static function provider_testCopy() {
$cases = array();
- $source = $this->baseStorePath() . '/unittest-cont1/e/file.txt';
- $dest = $this->baseStorePath() . '/unittest-cont2/a/fileMoved.txt';
+ $source = self::baseStorePath() . '/unittest-cont1/e/file.txt';
+ $dest = self::baseStorePath() . '/unittest-cont2/a/fileMoved.txt';
$op = array( 'op' => 'copy', 'src' => $source, 'dst' => $dest );
$cases[] = array(
$this->assertBackendPathsConsistent( array( $source, $dest ) );
}
- public function provider_testMove() {
+ public static function provider_testMove() {
$cases = array();
- $source = $this->baseStorePath() . '/unittest-cont1/e/file.txt';
- $dest = $this->baseStorePath() . '/unittest-cont2/a/fileMoved.txt';
+ $source = self::baseStorePath() . '/unittest-cont1/e/file.txt';
+ $dest = self::baseStorePath() . '/unittest-cont2/a/fileMoved.txt';
$op = array( 'op' => 'move', 'src' => $source, 'dst' => $dest );
$cases[] = array(
$this->assertBackendPathsConsistent( array( $source ) );
}
- public function provider_testDelete() {
+ public static function provider_testDelete() {
$cases = array();
- $source = $this->baseStorePath() . '/unittest-cont1/e/myfacefile.txt';
+ $source = self::baseStorePath() . '/unittest-cont1/e/myfacefile.txt';
$op = array( 'op' => 'delete', 'src' => $source );
$cases[] = array(
/**
* @dataProvider provider_testCreate
*/
- public function provider_testCreate() {
+ public static function provider_testCreate() {
$cases = array();
- $dest = $this->baseStorePath() . '/unittest-cont2/a/myspacefile.txt';
+ $dest = self::baseStorePath() . '/unittest-cont2/a/myspacefile.txt';
$op = array( 'op' => 'create', 'content' => 'test test testing', 'dst' => $dest );
$cases[] = array(
private function doTestDoQuickOperations() {
$backendName = $this->backendClass();
- $base = $this->baseStorePath();
+ $base = self::baseStorePath();
$files = array(
"$base/unittest-cont1/e/fileA.a",
"$base/unittest-cont1/e/fileB.a",
$rand = mt_rand( 0, 2000000000 ) . time();
$dest = wfTempDir() . "/randomfile!$rand.txt";
$srcs = array(
- $this->baseStorePath() . '/unittest-cont1/e/file1.txt',
- $this->baseStorePath() . '/unittest-cont1/e/file2.txt',
- $this->baseStorePath() . '/unittest-cont1/e/file3.txt',
- $this->baseStorePath() . '/unittest-cont1/e/file4.txt',
- $this->baseStorePath() . '/unittest-cont1/e/file5.txt',
- $this->baseStorePath() . '/unittest-cont1/e/file6.txt',
- $this->baseStorePath() . '/unittest-cont1/e/file7.txt',
- $this->baseStorePath() . '/unittest-cont1/e/file8.txt',
- $this->baseStorePath() . '/unittest-cont1/e/file9.txt',
- $this->baseStorePath() . '/unittest-cont1/e/file10.txt'
+ self::baseStorePath() . '/unittest-cont1/e/file1.txt',
+ self::baseStorePath() . '/unittest-cont1/e/file2.txt',
+ self::baseStorePath() . '/unittest-cont1/e/file3.txt',
+ self::baseStorePath() . '/unittest-cont1/e/file4.txt',
+ self::baseStorePath() . '/unittest-cont1/e/file5.txt',
+ self::baseStorePath() . '/unittest-cont1/e/file6.txt',
+ self::baseStorePath() . '/unittest-cont1/e/file7.txt',
+ self::baseStorePath() . '/unittest-cont1/e/file8.txt',
+ self::baseStorePath() . '/unittest-cont1/e/file9.txt',
+ self::baseStorePath() . '/unittest-cont1/e/file10.txt'
);
$content = array(
'egfage',
function provider_testGetFileStat() {
$cases = array();
- $base = $this->baseStorePath();
+ $base = self::baseStorePath();
$cases[] = array( "$base/unittest-cont1/e/b/z/some_file.txt", "some file contents", true );
$cases[] = array( "$base/unittest-cont1/e/b/some-other_file.txt", "", true );
$cases[] = array( "$base/unittest-cont1/e/b/some-diff_file.txt", null, false );
function provider_testGetFileContents() {
$cases = array();
- $base = $this->baseStorePath();
+ $base = self::baseStorePath();
$cases[] = array( "$base/unittest-cont1/e/b/z/some_file.txt", "some file contents" );
$cases[] = array( "$base/unittest-cont1/e/b/some-other_file.txt", "more file contents" );
$cases[] = array(
$this->assertNotEquals( false, $contents, "Local copy of $source exists ($backendName)." );
$this->assertEquals( $content[0], $contents, "Local copy of $source is correct ($backendName)." );
}
+
+ $obj = new stdClass();
+ $tmpFile->bind( $obj );
}
function provider_testGetLocalCopy() {
$cases = array();
- $base = $this->baseStorePath();
+ $base = self::baseStorePath();
$cases[] = array( "$base/unittest-cont1/e/a/z/some_file.txt", "some file contents" );
$cases[] = array( "$base/unittest-cont1/e/a/some-other_file.txt", "more file contents" );
$cases[] = array( "$base/unittest-cont1/e/a/\$odd&.txt", "test file contents" );
function provider_testGetLocalReference() {
$cases = array();
- $base = $this->baseStorePath();
+ $base = self::baseStorePath();
$cases[] = array( "$base/unittest-cont1/e/a/z/some_file.txt", "some file contents" );
$cases[] = array( "$base/unittest-cont1/e/a/some-other_file.txt", "more file contents" );
$cases[] = array( "$base/unittest-cont1/e/a/\$odd&.txt", "test file contents" );
}
function provider_testPrepareAndClean() {
- $base = $this->baseStorePath();
+ $base = self::baseStorePath();
return array(
array( "$base/unittest-cont1/e/a/z/some_file1.txt", true ),
array( "$base/unittest-cont2/a/z/some_file2.txt", true ),
private function doTestRecursiveClean() {
$backendName = $this->backendClass();
- $base = $this->baseStorePath();
+ $base = self::baseStorePath();
$dirs = array(
"$base/unittest-cont1/e/a",
"$base/unittest-cont1/e/a/b",
}
private function doTestDoOperations() {
- $base = $this->baseStorePath();
+ $base = self::baseStorePath();
$fileA = "$base/unittest-cont1/e/a/b/fileA.txt";
$fileAContents = '3tqtmoeatmn4wg4qe-mg3qt3 tq';
// concurrency orientated
private function doTestDoOperationsPipeline() {
- $base = $this->baseStorePath();
+ $base = self::baseStorePath();
$fileAContents = '3tqtmoeatmn4wg4qe-mg3qt3 tq';
$fileBContents = 'g-jmq3gpqgt3qtg q3GT ';
}
private function doTestDoOperationsFailing() {
- $base = $this->baseStorePath();
+ $base = self::baseStorePath();
$fileA = "$base/unittest-cont2/a/b/fileA.txt";
$fileAContents = '3tqtmoeatmn4wg4qe-mg3qt3 tq';
private function doTestGetFileList() {
$backendName = $this->backendClass();
- $base = $this->baseStorePath();
+ $base = self::baseStorePath();
// Should have no errors
$iter = $this->backend->getFileList( array( 'dir' => "$base/unittest-cont-notexists" ) );
private function doTestGetDirectoryList() {
$backendName = $this->backendClass();
- $base = $this->baseStorePath();
+ $base = self::baseStorePath();
$files = array(
"$base/unittest-cont1/e/test1.txt",
"$base/unittest-cont1/e/test2.txt",
}
private function deleteFiles( $container ) {
- $base = $this->baseStorePath();
+ $base = self::baseStorePath();
$iter = $this->backend->getFileList( array( 'dir' => "$base/$container" ) );
if ( $iter ) {
foreach ( $iter as $file ) {
function assertGoodStatus( $status, $msg ) {
$this->assertEquals( print_r( array(), 1 ), print_r( $status->errors, 1 ), $msg );
}
-
- function tearDown() {
- parent::tearDown();
- }
}
*/
class StoreBatchTest extends MediaWikiTestCase {
- public function setUp() {
+ protected function setUp() {
global $wgFileBackends;
parent::setUp();
$this->createdFiles = array();
}
+ protected function tearDown() {
+ $this->repo->cleanupBatch( $this->createdFiles ); // delete files
+ foreach ( $this->createdFiles as $tmp ) { // delete dirs
+ $tmp = $this->repo->resolveVirtualUrl( $tmp );
+ while ( $tmp = FileBackend::parentStoragePath( $tmp ) ) {
+ $this->repo->getBackend()->clean( array( 'dir' => $tmp ) );
+ }
+ }
+ parent::tearDown();
+ }
+
/**
* Store a file or virtual URL source into a media file name.
*
$this->storecohort( "Test1.png", "$IP/skins/monobook/wiki.png", "$IP/skins/monobook/video.png", false );
$this->storecohort( "Test2.png", "$IP/skins/monobook/wiki.png", "$IP/skins/monobook/video.png", true );
}
-
- public function tearDown() {
- $this->repo->cleanupBatch( $this->createdFiles ); // delete files
- foreach ( $this->createdFiles as $tmp ) { // delete dirs
- $tmp = $this->repo->resolveVirtualUrl( $tmp );
- while ( $tmp = FileBackend::parentStoragePath( $tmp ) ) {
- $this->repo->getBackend()->clean( array( 'dir' => $tmp ) );
- }
- }
- parent::tearDown();
- }
}
*/
class CSSMinTest extends MediaWikiTestCase {
- protected $oldServer = null, $oldCanServer = null;
- function setUp() {
+ protected function setUp() {
parent::setUp();
- // Fake $wgServer and $wgCanonicalServer
- global $wgServer, $wgCanonicalServer;
- $this->oldServer = $wgServer;
- $this->oldCanServer = $wgCanonicalServer;
- $wgServer = $wgCanonicalServer = 'http://wiki.example.org';
- }
-
- function tearDown() {
- // Restore $wgServer and $wgCanonicalServer
- global $wgServer, $wgCanonicalServer;
- $wgServer = $this->oldServer;
- $wgCanonicalServer = $this->oldCanServer;
+ $server = 'http://doc.example.org';
- parent::tearDown();
+ $this->setMwGlobals( array(
+ 'wgServer' => $server,
+ 'wgCanonicalServer' => $server,
+ ) );
}
/**
array(
'Expand absolute paths',
array( 'foo { prop: url(/w/skin/images/bar.png); }', false, 'http://example.org/quux', false ),
- 'foo { prop: url(http://wiki.example.org/w/skin/images/bar.png); }',
+ 'foo { prop: url(http://doc.example.org/w/skin/images/bar.png); }',
),
);
}
$instances = array();
foreach ( $this->elementInstancesProvider() as $elementInstances ) {
- $instances[] = $this->getNew( $elementInstances );
+ $instances[] = $this->getNew( $elementInstances[0] );
}
return $this->arrayWrap( $instances );
<?php
class BitmapMetadataHandlerTest extends MediaWikiTestCase {
- public function setUp() {
+ protected function setUp() {
+ parent::setUp();
+
+ $this->setMwGlobals( 'wgShowEXIF', false );
+
$this->filePath = __DIR__ . '/../../data/media/';
}
* translation (to en) where XMP should win.
*/
public function testMultilingualCascade() {
+ global $wgShowEXIF;
+
if ( !wfDl( 'exif' ) ) {
$this->markTestSkipped( "This test needs the exif extension." );
}
if ( !wfDl( 'xml' ) ) {
$this->markTestSkipped( "This test needs the xml extension." );
}
- global $wgShowEXIF;
- $oldExif = $wgShowEXIF;
+
$wgShowEXIF = true;
$meta = BitmapMetadataHandler::Jpeg( $this->filePath .
'Did not extract any ImageDescription info?!' );
$this->assertEquals( $expected, $meta['ImageDescription'] );
-
- $wgShowEXIF = $oldExif;
}
/**
$this->assertEquals( '2020:07:14 01:36:05', $meta['DateTimeDigitized'] );
$this->assertEquals( '1997:03:02 00:01:02', $meta['DateTimeOriginal'] );
}
+
/**
* File has an invalid time (+ one valid but really weird time)
* that shouldn't be included
);
$this->assertEquals( $expected, $result );
}
+
public function testPNGNative() {
$handler = new BitmapMetadataHandler();
$result = $handler->png( $this->filePath . 'Png-native-test.png' );
$expected = 'http://example.com/url';
$this->assertEquals( $expected, $result['metadata']['Identifier']['x-default'] );
}
+
public function testTiffByteOrder() {
$handler = new BitmapMetadataHandler();
$res = $handler->getTiffByteOrder( $this->filePath . 'test.tiff' );
class BitmapScalingTest extends MediaWikiTestCase {
- function setUp() {
- global $wgMaxImageArea, $wgCustomConvertCommand;
- $this->oldMaxImageArea = $wgMaxImageArea;
- $this->oldCustomConvertCommand = $wgCustomConvertCommand;
- $wgMaxImageArea = 1.25e7; // 3500x3500
- $wgCustomConvertCommand = 'dummy'; // Set so that we don't get client side rendering
- }
- function tearDown() {
- global $wgMaxImageArea, $wgCustomConvertCommand;
- $wgMaxImageArea = $this->oldMaxImageArea;
- $wgCustomConvertCommand = $this->oldCustomConvertCommand;
+ protected function setUp() {
+ parent::setUp();
+
+ $this->setMwGlobals( array(
+ 'wgMaxImageArea' => 1.25e7, // 3500x3500
+ 'wgCustomConvertCommand' => 'dummy', // Set so that we don't get client side rendering
+ ) );
}
+
/**
* @dataProvider provideNormaliseParams
*/
'Bigger than max image size but doesn\'t need scaling',
),
);
- }
+ }
+
function testTooBigImage() {
$file = new FakeDimensionFile( array( 4000, 4000 ) );
$handler = new BitmapHandler;
$this->assertEquals( 'TransformParameterError',
get_class( $handler->doTransform( $file, 'dummy path', '', $params ) ) );
}
+
function testTooBigMustRenderImage() {
$file = new FakeDimensionFile( array( 4000, 4000 ) );
$file->mustRender = true;
class ExifBitmapTest extends MediaWikiTestCase {
- public function setUp() {
- global $wgShowEXIF;
- $this->showExif = $wgShowEXIF;
- $wgShowEXIF = true;
+ protected function setUp() {
+ parent::setUp();
+
+ $this->setMwGlobals( 'wgShowEXIF', true );
+
$this->handler = new ExifBitmapHandler;
if ( !wfDl( 'exif' ) ) {
$this->markTestSkipped( "This test needs the exif extension." );
}
}
- public function tearDown() {
- global $wgShowEXIF;
- $wgShowEXIF = $this->showExif;
- }
-
public function testIsOldBroken() {
$res = $this->handler->isMetadataValid( null, ExifBitmapHandler::OLD_BROKEN_FILE );
$this->assertEquals( ExifBitmapHandler::METADATA_COMPATIBLE, $res );
}
+
public function testIsBrokenFile() {
$res = $this->handler->isMetadataValid( null, ExifBitmapHandler::BROKEN_FILE );
$this->assertEquals( ExifBitmapHandler::METADATA_GOOD, $res );
}
+
public function testIsInvalid() {
$res = $this->handler->isMetadataValid( null, 'Something Invalid Here.' );
$this->assertEquals( ExifBitmapHandler::METADATA_BAD, $res );
}
+
public function testGoodMetadata() {
$meta = 'a:16:{s:10:"ImageWidth";i:20;s:11:"ImageLength";i:20;s:13:"BitsPerSample";a:3:{i:0;i:8;i:1;i:8;i:2;i:8;}s:11:"Compression";i:5;s:25:"PhotometricInterpretation";i:2;s:16:"ImageDescription";s:17:"Created with GIMP";s:12:"StripOffsets";i:8;s:11:"Orientation";i:1;s:15:"SamplesPerPixel";i:3;s:12:"RowsPerStrip";i:64;s:15:"StripByteCounts";i:238;s:11:"XResolution";s:19:"1207959552/16777216";s:11:"YResolution";s:19:"1207959552/16777216";s:19:"PlanarConfiguration";i:1;s:14:"ResolutionUnit";i:2;s:22:"MEDIAWIKI_EXIF_VERSION";i:2;}';
$res = $this->handler->isMetadataValid( null, $meta );
$this->assertEquals( ExifBitmapHandler::METADATA_GOOD, $res );
}
+
public function testIsOldGood() {
$meta = 'a:16:{s:10:"ImageWidth";i:20;s:11:"ImageLength";i:20;s:13:"BitsPerSample";a:3:{i:0;i:8;i:1;i:8;i:2;i:8;}s:11:"Compression";i:5;s:25:"PhotometricInterpretation";i:2;s:16:"ImageDescription";s:17:"Created with GIMP";s:12:"StripOffsets";i:8;s:11:"Orientation";i:1;s:15:"SamplesPerPixel";i:3;s:12:"RowsPerStrip";i:64;s:15:"StripByteCounts";i:238;s:11:"XResolution";s:19:"1207959552/16777216";s:11:"YResolution";s:19:"1207959552/16777216";s:19:"PlanarConfiguration";i:1;s:14:"ResolutionUnit";i:2;s:22:"MEDIAWIKI_EXIF_VERSION";i:1;}';
$res = $this->handler->isMetadataValid( null, $meta );
$this->assertEquals( ExifBitmapHandler::METADATA_COMPATIBLE, $res );
}
+
// Handle metadata from paged tiff handler (gotten via instant commons)
// gracefully.
public function testPagedTiffHandledGracefully() {
$res = $this->handler->convertMetadataVersion( $metadata, 2 );
$this->assertEquals( $metadata, $res );
}
+
function testConvertMetadataToOld() {
$metadata = array(
'foo' => array( 'First', 'Second', '_type' => 'ol' ),
$res = $this->handler->convertMetadataVersion( $metadata, 1 );
$this->assertEquals( $expected, $res );
}
+
function testConvertMetadataSoftware() {
$metadata = array(
'Software' => array( array('GIMP', '1.1' ) ),
$res = $this->handler->convertMetadataVersion( $metadata, 1 );
$this->assertEquals( $expected, $res );
}
+
function testConvertMetadataSoftwareNormal() {
$metadata = array(
'Software' => array( "GIMP 1.2", "vim" ),
*/
class ExifRotationTest extends MediaWikiTestCase {
- function setUp() {
+ protected function setUp() {
parent::setUp();
$this->handler = new BitmapHandler();
$filePath = __DIR__ . '/../../data/media';
$wgEnableAutoRotation = true;
}
- public function tearDown() {
+ protected function tearDown() {
global $wgShowEXIF, $wgEnableAutoRotation;
$wgShowEXIF = $this->show;
$wgEnableAutoRotation = $this->oldAuto;
/**
*
- * @dataProvider providerFiles
+ * @dataProvider provideFiles
*/
function testMetadata( $name, $type, $info ) {
if ( !BitmapHandler::canRotate() ) {
/**
*
- * @dataProvider providerFiles
+ * @dataProvider provideFiles
*/
function testRotationRendering( $name, $type, $info, $thumbs ) {
if ( !BitmapHandler::canRotate() ) {
}
}
+ /* Utility function */
private function dataFile( $name, $type ) {
return new UnregisteredLocalFile( false, $this->repo,
"mwstore://localtesting/data/$name", $type );
}
- function providerFiles() {
+ public static function provideFiles() {
return array(
array(
'landscape-plain.jpg',
/**
* Same as before, but with auto-rotation disabled.
- * @dataProvider providerFilesNoAutoRotate
+ * @dataProvider provideFilesNoAutoRotate
*/
function testMetadataNoAutoRotate( $name, $type, $info ) {
global $wgEnableAutoRotation;
/**
*
- * @dataProvider providerFilesNoAutoRotate
+ * @dataProvider provideFilesNoAutoRotate
*/
function testRotationRenderingNoAutoRotate( $name, $type, $info, $thumbs ) {
global $wgEnableAutoRotation;
$wgEnableAutoRotation = true;
}
- function providerFilesNoAutoRotate() {
+ public static function provideFilesNoAutoRotate() {
return array(
array(
'landscape-plain.jpg',
<?php
class ExifTest extends MediaWikiTestCase {
- public function setUp() {
+ protected function setUp() {
+ parent::setUp();
+
$this->mediaPath = __DIR__ . '/../../data/media/';
if ( !wfDl( 'exif' ) ) {
$this->markTestSkipped( "This test needs the exif extension." );
}
- global $wgShowEXIF;
- $this->showExif = $wgShowEXIF;
- $wgShowEXIF = true;
- }
- public function tearDown() {
- global $wgShowEXIF;
- $wgShowEXIF = $this->showExif;
+ $this->setMwGlobals( 'wgShowEXIF', true );
}
public function testGPSExtraction() {
<?php
class FormatMetadataTest extends MediaWikiTestCase {
- public function setUp() {
+
+ protected function setUp() {
+ parent::setUp();
+
if ( !wfDl( 'exif' ) ) {
$this->markTestSkipped( "This test needs the exif extension." );
}
'url' => 'http://localhost/thumbtest',
'backend' => $this->backend
) );
- global $wgShowEXIF;
- $this->show = $wgShowEXIF;
- $wgShowEXIF = true;
- }
- public function tearDown() {
- global $wgShowEXIF;
- $wgShowEXIF = $this->show;
+
+ $this->setMwGlobals( 'wgShowEXIF', true );
}
public function testInvalidDate() {
<?php
class GIFMetadataExtractorTest extends MediaWikiTestCase {
- public function setUp() {
+ protected function setUp() {
+ parent::setUp();
+
$this->mediaPath = __DIR__ . '/../../data/media/';
}
/**
* Put in a file, and see if the metadata coming out is as expected.
* @param $filename String
* @param $expected Array The extracted metadata.
- * @dataProvider dataGetMetadata
+ * @dataProvider provideGetMetadata
*/
public function testGetMetadata( $filename, $expected ) {
$actual = GIFMetadataExtractor::getMetadata( $this->mediaPath . $filename );
$this->assertEquals( $expected, $actual );
}
- public function dataGetMetadata() {
+ public static function provideGetMetadata() {
$xmpNugget = <<<EOF
<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?>
<?php
class GIFHandlerTest extends MediaWikiTestCase {
- public function setUp() {
+ protected function setUp() {
+ parent::setUp();
+
$this->filePath = __DIR__ . '/../../data/media';
$this->backend = new FSFileBackend( array(
'name' => 'localtesting',
$res = $this->handler->getMetadata( null, $this->filePath . '/README' );
$this->assertEquals( GIFHandler::BROKEN_FILE, $res );
}
+
/**
* @param $filename String basename of the file to check
* @param $expected boolean Expected result.
- * @dataProvider dataIsAnimated
+ * @dataProvider provideIsAnimated
*/
public function testIsAnimanted( $filename, $expected ) {
$file = $this->dataFile( $filename, 'image/gif' );
$actual = $this->handler->isAnimatedImage( $file );
$this->assertEquals( $expected, $actual );
}
- public function dataIsAnimated() {
+ public static function provideIsAnimated() {
return array(
array( 'animated.gif', true ),
array( 'nonanimated.gif', false ),
/**
* @param $filename String
* @param $expected Integer Total image area
- * @dataProvider dataGetImageArea
+ * @dataProvider provideGetImageArea
*/
public function testGetImageArea( $filename, $expected ) {
$file = $this->dataFile( $filename, 'image/gif' );
$actual = $this->handler->getImageArea( $file, $file->getWidth(), $file->getHeight() );
$this->assertEquals( $expected, $actual );
}
- public function dataGetImageArea() {
+ public static function provideGetImageArea() {
return array(
array( 'animated.gif', 5400 ),
array( 'nonanimated.gif', 1350 ),
/**
* @param $metadata String Serialized metadata
* @param $expected Integer One of the class constants of GIFHandler
- * @dataProvider dataIsMetadataValid
+ * @dataProvider provideIsMetadataValid
*/
public function testIsMetadataValid( $metadata, $expected ) {
$actual = $this->handler->isMetadataValid( null, $metadata );
$this->assertEquals( $expected, $actual );
}
- public function dataIsMetadataValid() {
+ public static function provideIsMetadataValid() {
return array(
array( GIFHandler::BROKEN_FILE, GIFHandler::METADATA_GOOD ),
array( '', GIFHandler::METADATA_BAD ),
/**
* @param $filename String
* @param $expected String Serialized array
- * @dataProvider dataGetMetadata
+ * @dataProvider provideGetMetadata
*/
public function testGetMetadata( $filename, $expected ) {
$file = $this->dataFile( $filename, 'image/gif' );
$this->assertEquals( unserialize( $expected ), unserialize( $actual ) );
}
- public function dataGetMetadata() {
+ public static function provideGetMetadata() {
return array(
array( 'nonanimated.gif', 'a:4:{s:10:"frameCount";i:1;s:6:"looped";b:0;s:8:"duration";d:0.1000000000000000055511151231257827021181583404541015625;s:8:"metadata";a:2:{s:14:"GIFFileComment";a:1:{i:0;s:35:"GIF test file ⁕ Created with GIMP";}s:15:"_MW_GIF_VERSION";i:1;}}' ),
array( 'animated-xmp.gif', 'a:4:{s:10:"frameCount";i:4;s:6:"looped";b:1;s:8:"duration";d:2.399999999999999911182158029987476766109466552734375;s:8:"metadata";a:5:{s:6:"Artist";s:7:"Bawolff";s:16:"ImageDescription";a:2:{s:9:"x-default";s:18:"A file to test GIF";s:5:"_type";s:4:"lang";}s:15:"SublocationDest";s:13:"The interwebs";s:14:"GIFFileComment";a:1:{i:0;s:16:"GIƒ·test·file";}s:15:"_MW_GIF_VERSION";i:1;}}' ),
*/
class JpegMetadataExtractorTest extends MediaWikiTestCase {
- public function setUp() {
+ protected function setUp() {
+ parent::setUp();
+
$this->filePath = __DIR__ . '/../../data/media/';
}
*
* @param $file filename
*
- * @dataProvider dataUtf8Comment
+ * @dataProvider provideUtf8Comment
*/
public function testUtf8Comment( $file ) {
$res = JpegMetadataExtractor::segmentSplitter( $this->filePath . $file );
$this->assertEquals( array( 'UTF-8 JPEG Comment — ¼' ), $res['COM'] );
}
- public function dataUtf8Comment() {
+ public static function provideUtf8Comment() {
return array(
array( 'jpeg-comment-utf.jpg' ),
array( 'jpeg-padding-even.jpg' ),
<?php
class JpegTest extends MediaWikiTestCase {
- public function setUp() {
+ protected function setUp() {
+ parent::setUp();
+
$this->filePath = __DIR__ . '/../../data/media/';
if ( !wfDl( 'exif' ) ) {
$this->markTestSkipped( "This test needs the exif extension." );
}
- global $wgShowEXIF;
- $this->show = $wgShowEXIF;
- $wgShowEXIF = true;
- }
- public function tearDown() {
- global $wgShowEXIF;
- $wgShowEXIF = $this->show;
+
+ $this->setMwGlobals( 'wgShowEXIF', true );
}
public function testInvalidFile() {
$res = $jpeg->getMetadata( null, $this->filePath . 'README' );
$this->assertEquals( ExifBitmapHandler::BROKEN_FILE, $res );
}
+
public function testJpegMetadataExtraction() {
$h = new JpegHandler;
$res = $h->getMetadata( null, $this->filePath . 'test.jpg' );
<?php
class PNGMetadataExtractorTest extends MediaWikiTestCase {
- function setUp() {
+ protected function setUp() {
$this->filePath = __DIR__ . '/../../data/media/';
}
/**
/**
* Test extraction of pHYs tags, which can tell what the
* actual resolution of the image is (aka in dots per meter).
+ */
+/*
function testPngPhysTag () {
$meta = PNGMetadataExtractor::getMetadata( $this->filePath .
'Png-native-test.png' );
$this->assertEquals( '2835/100', $meta['YResolution'] );
$this->assertEquals( 3, $meta['ResolutionUnit'] ); // 3 = cm
}
+*/
/**
* Given a normal static PNG, check the animation metadata returned.
<?php
class PNGHandlerTest extends MediaWikiTestCase {
- public function setUp() {
+ protected function setUp() {
+ parent::setUp();
+
$this->filePath = __DIR__ . '/../../data/media';
$this->backend = new FSFileBackend( array(
'name' => 'localtesting',
/**
* @param $filename String basename of the file to check
* @param $expected boolean Expected result.
- * @dataProvider dataIsAnimated
+ * @dataProvider provideIsAnimated
*/
public function testIsAnimanted( $filename, $expected ) {
$file = $this->dataFile( $filename, 'image/png' );
$actual = $this->handler->isAnimatedImage( $file );
$this->assertEquals( $expected, $actual );
}
- public function dataIsAnimated() {
+ public static function provideIsAnimated() {
return array(
array( 'Animated_PNG_example_bouncing_beach_ball.png', true ),
array( '1bit-png.png', false ),
/**
* @param $filename String
* @param $expected Integer Total image area
- * @dataProvider dataGetImageArea
+ * @dataProvider provideGetImageArea
*/
public function testGetImageArea( $filename, $expected ) {
$file = $this->dataFile($filename, 'image/png' );
$actual = $this->handler->getImageArea( $file, $file->getWidth(), $file->getHeight() );
$this->assertEquals( $expected, $actual );
}
- public function dataGetImageArea() {
+ public static function provideGetImageArea() {
return array(
array( '1bit-png.png', 2500 ),
array( 'greyscale-png.png', 2500 ),
/**
* @param $metadata String Serialized metadata
* @param $expected Integer One of the class constants of PNGHandler
- * @dataProvider dataIsMetadataValid
+ * @dataProvider provideIsMetadataValid
*/
public function testIsMetadataValid( $metadata, $expected ) {
$actual = $this->handler->isMetadataValid( null, $metadata );
$this->assertEquals( $expected, $actual );
}
- public function dataIsMetadataValid() {
+ public static function provideIsMetadataValid() {
return array(
array( PNGHandler::BROKEN_FILE, PNGHandler::METADATA_GOOD ),
array( '', PNGHandler::METADATA_BAD ),
/**
* @param $filename String
* @param $expected String Serialized array
- * @dataProvider dataGetMetadata
+ * @dataProvider provideGetMetadata
*/
public function testGetMetadata( $filename, $expected ) {
$file = $this->dataFile( $filename, 'image/png' );
// $this->assertEquals( unserialize( $expected ), unserialize( $actual ) );
$this->assertEquals( ( $expected ), ( $actual ) );
}
- public function dataGetMetadata() {
+ public static function provideGetMetadata() {
return array(
array( 'rgb-na-png.png', 'a:6:{s:10:"frameCount";i:0;s:9:"loopCount";i:1;s:8:"duration";d:0;s:8:"bitDepth";i:8;s:9:"colorType";s:10:"truecolour";s:8:"metadata";a:1:{s:15:"_MW_PNG_VERSION";i:1;}}' ),
array( 'xmp.png', 'a:6:{s:10:"frameCount";i:0;s:9:"loopCount";i:1;s:8:"duration";d:0;s:8:"bitDepth";i:1;s:9:"colorType";s:14:"index-coloured";s:8:"metadata";a:2:{s:12:"SerialNumber";s:9:"123456789";s:15:"_MW_PNG_VERSION";i:1;}}' ),
class SVGMetadataExtractorTest extends MediaWikiTestCase {
- function setUp() {
+ protected function setUp() {
AutoLoader::loadClass( 'SVGMetadataExtractorTest' );
}
/**
- * @dataProvider providerSvgFiles
+ * @dataProvider provideSvgFiles
*/
function testGetMetadata( $infile, $expected ) {
$this->assertMetadata( $infile, $expected );
}
/**
- * @dataProvider providerSvgFilesWithXMLMetadata
+ * @dataProvider provideSvgFilesWithXMLMetadata
*/
function testGetXMLMetadata( $infile, $expected ) {
$r = new XMLReader();
}
}
- function providerSvgFiles() {
+ public static function provideSvgFiles() {
$base = __DIR__ . '/../../data/media';
return array(
array(
);
}
- function providerSvgFilesWithXMLMetadata() {
+ public static function provideSvgFilesWithXMLMetadata() {
$base = __DIR__ . '/../../data/media';
$metadata =
'<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<?php
class TiffTest extends MediaWikiTestCase {
- public function setUp() {
- global $wgShowEXIF;
- $this->showExif = $wgShowEXIF;
- $wgShowEXIF = true;
+ protected function setUp() {
+ parent::setUp();
+
+ $this->setMwGlobals( 'wgShowEXIF', true );
+
$this->filePath = __DIR__ . '/../../data/media/';
$this->handler = new TiffHandler;
}
- public function tearDown() {
- global $wgShowEXIF;
- $wgShowEXIF = $this->showExif;
- }
-
public function testInvalidFile() {
if ( !wfDl( 'exif' ) ) {
$this->markTestIncomplete( "This test needs the exif extension." );
<?php
class XMPTest extends MediaWikiTestCase {
- function setUp() {
+ protected function setUp() {
if ( !wfDl( 'xml' ) ) {
$this->markTestSkipped( 'Requires libxml to do XMP parsing' );
}
* @param $expected Array expected result of parsing the xmp.
* @param $info String Short sentence on what's being tested.
*
- * @dataProvider dataXMPParse
+ * @dataProvider provideXMPParse
*/
public function testXMPParse( $xmp, $expected, $info ) {
if ( !is_string( $xmp ) || !is_array( $expected ) ) {
$this->assertEquals( $expected, $reader->getResults(), $info, 0.0000000001 );
}
- public function dataXMPParse() {
+ public static function provideXMPParse() {
$xmpPath = __DIR__ . '/../../data/xmp/' ;
$data = array();
class XMPValidateTest extends MediaWikiTestCase {
/**
- * @dataProvider providerDate
+ * @dataProvider provideDates
*/
function testValidateDate( $value, $expected ) {
// The method should modify $value.
$this->assertEquals( $expected, $value );
}
- function providerDate() {
+ public static function provideDates() {
/* For reference valid date formats are:
* YYYY
* YYYY-MM
$this->assertEquals( $format, $detector->detectFormatName( $userAgent ) );
}
- public function provideTestFormatName() {
+ public static function provideTestFormatName() {
return array(
array( 'android', 'Mozilla/5.0 (Linux; U; Android 2.1; en-us; Nexus One Build/ERD62) AppleWebKit/530.17 (KHTML, like Gecko) Version/4.0 Mobile Safari/530.17' ),
array( 'iphone2', 'Mozilla/5.0 (ipod: U;CPU iPhone OS 2_2 like Mac OS X: es_es) AppleWebKit/525.18.1 (KHTML, like Gecko) Version/3.0 Mobile/3B48b Safari/419.3' ),
);
/** setup a basic parser object */
- function setUp() {
- global $wgContLang;
- $wgContLang = Language::factory( 'en' );
+ protected function setUp() {
+ parent::setUp();
+
+ $this->setMwGlobals( 'wgContLang', Language::factory( 'en' ) );
$this->testParser = new Parser();
$this->testParser->Options( new ParserOptions() );
}
/** destroy parser (TODO: is it really neded?)*/
- function tearDown() {
+ protected function tearDown() {
unset( $this->testParser );
+
+ parent::tearDown();
}
############### TESTS #############################################
*/
class NewParserTest extends MediaWikiTestCase {
static protected $articles = array(); // Array of test articles defined by the tests
- /* The dataProvider is run on a different instance than the test, so it must be static
+ /* The data provider is run on a different instance than the test, so it must be static
* When running tests from several files, all tests will see all articles.
*/
static protected $backendToUse;
protected $file = false;
- function setUp() {
+ protected function setUp() {
global $wgContLang, $wgNamespaceProtection, $wgNamespaceAliases;
global $wgHooks, $IP;
$wgContLang = Language::factory( 'en' );
$wgNamespaceAliases['Image_talk'] = NS_FILE_TALK;
}
- public function tearDown() {
+ protected function tearDown() {
foreach ( $this->savedInitialGlobals as $var => $val ) {
$GLOBALS[$var] = $val;
}
'wgNoFollowLinks' => true,
'wgNoFollowDomainExceptions' => array(),
'wgThumbnailScriptPath' => false,
- 'wgUseImageResize' => false,
+ 'wgUseImageResize' => true,
'wgUseTeX' => isset( $opts['math'] ),
'wgMathDirectory' => $uploadDir . '/math',
'wgLocaltimezone' => 'UTC',
return;
}
+ if ( !$this->isWikitextNS( NS_MAIN ) ) {
+ // parser tests frequently assume that the main namespace contains wikitext.
+ // @todo: When setting up pages, force the content model. Only skip if
+ // $wgtContentModelUseDB is false.
+ $this->markTestSkipped( "Main namespace does not support wikitext,"
+ . "skipping parser test: $desc" );
+ }
+
wfDebug( "Running parser test: $desc\n" );
$opts = $this->parseOptions( $opts );
class ParserMethodsTest extends MediaWikiLangTestCase {
- public function dataPreSaveTransform() {
+ public static function providePreSaveTransform() {
return array(
array( 'hello this is ~~~',
"hello this is [[Special:Contributions/127.0.0.1|127.0.0.1]]",
}
/**
- * @dataProvider dataPreSaveTransform
+ * @dataProvider providePreSaveTransform
*/
public function testPreSaveTransform( $text, $expected ) {
global $wgParser;
private $testParserOptions;
private $title;
- function setUp() {
+ protected function setUp() {
$this->testParserOptions = new ParserOptions();
$this->testParser = new Parser();
$this->title = Title::newFromText( 'Preload Test' );
}
- function tearDown() {
+ protected function tearDown() {
unset( $this->testParser );
unset( $this->title );
}
var $mPPNodeCount = 0;
var $mOptions;
- function setUp() {
+ protected function setUp() {
global $wgParserConf;
$this->mOptions = new ParserOptions();
$name = isset( $wgParserConf['preprocessorClass'] ) ? $wgParserConf['preprocessorClass'] : 'Preprocessor_DOM';
class SearchEngineTest extends MediaWikiTestCase {
protected $search, $pageList;
- function tearDown() {
- unset( $this->search );
- }
-
/**
* Checks for database type & version.
* Will skip current test if DB does not support search.
*/
- function setUp() {
+ protected function setUp() {
parent::setUp();
// Search tests require MySQL or SQLite with FTS
# Get database type and version
$this->search = new $searchType( $this->db );
}
+ protected function tearDown() {
+ unset( $this->search );
+ }
+
function pageExists( $title ) {
return false;
}
if ( $this->pageExists( 'Not_Main_Page' ) ) {
return;
}
+
+ if ( !$this->isWikitextNS( NS_MAIN ) ) {
+ //@todo: cover the case of non-wikitext content in the main namespace
+ return;
+ }
+
$this->insertPage( "Not_Main_Page", "This is not a main page", 0 );
$this->insertPage( 'Talk:Not_Main_Page', 'This is not a talk page to the main page, see [[smithee]]', 1 );
$this->insertPage( 'Smithee', 'A smithee is one who smiths. See also [[Alan Smithee]]', 0 );
}
function fetchIds( $results ) {
+ if ( !$this->isWikitextNS( NS_MAIN ) ) {
+ $this->markTestIncomplete( __CLASS__ . " does no yet support non-wikitext content "
+ . "in the main namespace");
+ }
+
$this->assertTrue( is_object( $results ) );
$matches = array();
* @param $n Integer: unused
*/
function insertPage( $pageName, $text, $ns ) {
- $title = Title::newFromText( $pageName );
+ $title = Title::newFromText( $pageName, $ns );
$user = User::newFromName( 'WikiSysop' );
$comment = 'Search Test';
LinkCache::singleton()->clear();
$page = WikiPage::factory( $title );
- $page->doEdit( $text, $comment, 0, false, $user );
+ $page->doEditContent( ContentHandler::makeContent( $text, $title ), $comment, 0, false, $user );
$this->pageList[] = array( $title, $page->getId() );
* @group Search
*/
class SearchUpdateTest extends MediaWikiTestCase {
- static $searchType;
+
+ protected function setUp() {
+ parent::setUp();
+ $this->setMwGlobals( 'wgSearchType', 'MockSearch' );
+ }
function update( $text, $title = 'Test', $id = 1 ) {
$u = new SearchUpdate( $id, $title, $text );
return $resultText;
}
- function setUp() {
- global $wgSearchType;
-
- self::$searchType = $wgSearchType;
- $wgSearchType = 'MockSearch';
- }
-
- function tearDown() {
- global $wgSearchType;
-
- $wgSearchType = self::$searchType;
- }
-
function testUpdateText() {
$this->assertEquals(
'test',
--- /dev/null
+<?php
+
+/**
+ * Tests for the MediaWikiSite class.
+ *
+ * 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
+ * @since 1.21
+ *
+ * @ingroup Site
+ * @ingroup Test
+ *
+ * @group Site
+ * @group Database
+ *
+ * @licence GNU GPL v2+
+ * @author Jeroen De Dauw < jeroendedauw@gmail.com >
+ */
+class MediaWikiSiteTest extends SiteObjectTest {
+
+ public function setUp() {
+ parent::setUp();
+
+ static $hasSites = false;
+
+ if ( !$hasSites ) {
+ TestSites::insertIntoDb();
+ $hasSites = true;
+ }
+ }
+
+ public function testFactoryConstruction() {
+ $this->assertInstanceOf( 'MediaWikiSite', MediaWikiSite::newFromGlobalId( 'enwiki' ) );
+ $this->assertInstanceOf( 'Site', MediaWikiSite::newFromGlobalId( 'enwiki' ) );
+ $this->assertInstanceOf( 'MediaWikiSite', SitesTable::singleton()->newRow( array( 'type' => Site::TYPE_MEDIAWIKI ) ) );
+ }
+
+ public function testNormalizePageTitle() {
+ $site = MediaWikiSite::newFromGlobalId( 'enwiki' );
+
+ //NOTE: this does not actually call out to the enwiki site to perform the normalization,
+ // but uses a local Title object to do so. This is hardcoded on SiteLink::normalizePageTitle
+ // for the case that MW_PHPUNIT_TEST is set.
+ $this->assertEquals( 'Foo', $site->normalizePageName( ' foo ' ) );
+ }
+
+ public function fileUrlProvider() {
+ return array(
+ // url, filepath, path arg, expected
+ array( 'https://en.wikipedia.org', '/w/$1', 'api.php', 'https://en.wikipedia.org/w/api.php' ),
+ array( 'https://en.wikipedia.org', '/w/', 'api.php', 'https://en.wikipedia.org/w/' ),
+ array( 'https://en.wikipedia.org', '/foo/page.php?name=$1', 'api.php', 'https://en.wikipedia.org/foo/page.php?name=api.php' ),
+ array( 'https://en.wikipedia.org', '/w/$1', '', 'https://en.wikipedia.org/w/' ),
+ array( 'https://en.wikipedia.org', '/w/$1', 'foo/bar/api.php', 'https://en.wikipedia.org/w/foo/bar/api.php' ),
+ );
+ }
+
+ /**
+ * @dataProvider fileUrlProvider
+ */
+ public function testGetFileUrl( $url, $filePath, $pathArgument, $expected ) {
+ $site = MediaWikiSite::newFromGlobalId( 'enwiki' );
+
+ $site->setFilePath( $url . $filePath );
+
+ $this->assertEquals( $expected, $site->getFileUrl( $pathArgument ) );
+ }
+
+ public function provideGetPageUrl() {
+ return array(
+ // path, page, expected substring
+ array( 'http://acme.test/wiki/$1', 'Berlin', '/wiki/Berlin' ),
+ array( 'http://acme.test/wiki/', 'Berlin', '/wiki/' ),
+ array( 'http://acme.test/w/index.php?title=$1', 'Berlin', '/w/index.php?title=Berlin' ),
+ array( 'http://acme.test/wiki/$1', '', '/wiki/' ),
+ array( 'http://acme.test/wiki/$1', 'Berlin/sub page', '/wiki/Berlin/sub_page' ),
+ array( 'http://acme.test/wiki/$1', 'Cork (city) ', '/Cork_(city)' ),
+ array( 'http://acme.test/wiki/$1', 'M&M', '/wiki/M%26M' ),
+ );
+ }
+
+ /**
+ * @dataProvider provideGetPageUrl
+ */
+ public function testGetPageUrl( $path, $page, $expected ) {
+ /* @var MediaWikiSite $site */
+ $site = MediaWikiSite::newFromGlobalId( 'enwiki' );
+
+ $site->setLinkPath( $path );
+ $this->assertContains( $path, $site->getPageUrl() );
+ $this->assertContains( $expected, $site->getPageUrl( $page ) );
+ }
+
+}
--- /dev/null
+<?php
+
+/**
+ * Tests for the SiteArray class.
+ * The tests for methods defined in the SiteList interface are in SiteListTest.
+ *
+ * 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
+ *Both
+ * Bith
+ * @file
+ * @since 1.21
+ *
+ * @ingroup Site
+ * @ingroup Test
+ *
+ * @group Site
+ *
+ * @licence GNU GPL v2+
+ * @author Jeroen De Dauw < jeroendedauw@gmail.com >
+ */
+class SiteArrayTest extends GenericArrayObjectTest {
+
+ /**
+ * @see GenericArrayObjectTest::elementInstancesProvider
+ *
+ * @since 1.21
+ *
+ * @return array
+ */
+ public function elementInstancesProvider() {
+ $sites = TestSites::getSites();
+
+ $siteArrays = array();
+
+ $siteArrays[] = $sites;
+
+ $siteArrays[] = array( array_shift( $sites ) );
+
+ $siteArrays[] = array( array_shift( $sites ), array_shift( $sites ) );
+
+ return $this->arrayWrap( $siteArrays );
+ }
+
+ /**
+ * @see GenericArrayObjectTest::getInstanceClass
+ *
+ * @since 1.21
+ *
+ * @return array
+ */
+ public function getInstanceClass() {
+ return 'SiteArray';
+ }
+
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+/**
+ * Tests for the SiteList implementing 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
+ * @since 1.21
+ *
+ * @ingroup Site
+ * @ingroup Test
+ *
+ * @group Site
+ *
+ * @licence GNU GPL v2+
+ * @author Jeroen De Dauw < jeroendedauw@gmail.com >
+ */
+class SiteListTest extends MediaWikiTestCase {
+
+ /**
+ * Returns instances of SiteList implementing objects.
+ * @return array
+ */
+ public function siteListProvider() {
+ $sitesArrays = $this->siteArrayProvider();
+
+ $listInstances = array();
+
+ foreach ( $sitesArrays as $sitesArray ) {
+ $listInstances[] = new SiteArray( $sitesArray[0] );
+ }
+
+ return $this->arrayWrap( $listInstances );
+ }
+
+ /**
+ * Returns arrays with instances of Site implementing objects.
+ * @return array
+ */
+ public function siteArrayProvider() {
+ $sites = TestSites::getSites();
+
+ $siteArrays = array();
+
+ $siteArrays[] = $sites;
+
+ $siteArrays[] = array( array_shift( $sites ) );
+
+ $siteArrays[] = array( array_shift( $sites ), array_shift( $sites ) );
+
+ return $this->arrayWrap( $siteArrays );
+ }
+
+ /**
+ * @dataProvider siteListProvider
+ * @param SiteList $sites
+ */
+ public function testIsEmpty( SiteList $sites ) {
+ $this->assertEquals( count( $sites ) === 0, $sites->isEmpty() );
+ }
+
+ /**
+ * @dataProvider siteListProvider
+ * @param SiteList $sites
+ */
+ public function testGetSiteByGlobalId( SiteList $sites ) {
+ if ( $sites->isEmpty() ) {
+ $this->assertTrue( true );
+ }
+ else {
+ /**
+ * @var Site $site
+ */
+ foreach ( $sites as $site ) {
+ $this->assertEquals( $site, $sites->getSite( $site->getGlobalId() ) );
+ }
+ }
+ }
+
+ /**
+ * @dataProvider siteListProvider
+ * @param SiteList $sites
+ */
+ public function testGetSiteByInternalId( $sites ) {
+ /**
+ * @var Site $site
+ */
+ foreach ( $sites as $site ) {
+ if ( is_integer( $site->getInternalId() ) ) {
+ $this->assertEquals( $site, $sites->getSiteByInternalId( $site->getInternalId() ) );
+ }
+ }
+
+ $this->assertTrue( true );
+ }
+
+ /**
+ * @dataProvider siteListProvider
+ * @param SiteList $sites
+ */
+ public function testHasGlobalId( $sites ) {
+ $this->assertFalse( $sites->hasSite( 'non-existing-global-id' ) );
+ $this->assertFalse( $sites->hasInternalId( 720101010 ) );
+
+ if ( !$sites->isEmpty() ) {
+ /**
+ * @var Site $site
+ */
+ foreach ( $sites as $site ) {
+ $this->assertTrue( $sites->hasSite( $site->getGlobalId() ) );
+ }
+ }
+ }
+
+ /**
+ * @dataProvider siteListProvider
+ * @param SiteList $sites
+ */
+ public function testHasInternallId( $sites ) {
+ /**
+ * @var Site $site
+ */
+ foreach ( $sites as $site ) {
+ if ( is_integer( $site->getInternalId() ) ) {
+ $this->assertTrue( $site, $sites->hasInternalId( $site->getInternalId() ) );
+ }
+ }
+
+ $this->assertFalse( $sites->hasInternalId( -1 ) );
+ }
+
+ /**
+ * @dataProvider siteListProvider
+ * @param SiteList $sites
+ */
+ public function testGetGlobalIdentifiers( SiteList $sites ) {
+ $identifiers = $sites->getGlobalIdentifiers();
+
+ $this->assertTrue( is_array( $identifiers ) );
+
+ $expected = array();
+
+ /**
+ * @var Site $site
+ */
+ foreach ( $sites as $site ) {
+ $expected[] = $site->getGlobalId();
+ }
+
+ $this->assertArrayEquals( $expected, $identifiers );
+ }
+
+
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+/**
+ * Tests for the SiteObject class.
+ *
+ * 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
+ * @since 1.21
+ *
+ * @ingroup Site
+ * @ingroup Test
+ *
+ * @group Site
+ *
+ * @licence GNU GPL v2+
+ * @author Jeroen De Dauw < jeroendedauw@gmail.com >
+ */
+class SiteObjectTest extends ORMRowTest {
+
+ /**
+ * @see ORMRowTest::getRowClass
+ * @since 1.21
+ * @return string
+ */
+ protected function getRowClass() {
+ return 'SiteObject';
+ }
+
+ /**
+ * @see ORMRowTest::getTableInstance
+ * @since 1.21
+ * @return IORMTable
+ */
+ protected function getTableInstance() {
+ return SitesTable::singleton();
+ }
+
+ /**
+ * @see ORMRowTest::constructorTestProvider
+ * @since 1.21
+ * @return array
+ */
+ public function constructorTestProvider() {
+ $argLists = array();
+
+ $argLists[] = array( 'global_key' => '42' );
+
+ $argLists[] = array( 'global_key' => '42', 'type' => Site::TYPE_MEDIAWIKI );
+
+ $constructorArgs = array();
+
+ foreach ( $argLists as $argList ) {
+ $constructorArgs[] = array( $argList, true );
+ }
+
+ return $constructorArgs;
+ }
+
+ /**
+ * @dataProvider instanceProvider
+ * @param Site $site
+ */
+ public function testGetInterwikiIds( Site $site ) {
+ $this->assertInternalType( 'array', $site->getInterwikiIds() );
+ }
+
+ /**
+ * @dataProvider instanceProvider
+ * @param Site $site
+ */
+ public function testGetNavigationIds( Site $site ) {
+ $this->assertInternalType( 'array', $site->getNavigationIds() );
+ }
+
+ /**
+ * @dataProvider instanceProvider
+ * @param Site $site
+ */
+ public function testAddNavigationId( Site $site ) {
+ $site->addNavigationId( 'foobar' );
+ $this->assertTrue( in_array( 'foobar', $site->getNavigationIds(), true ) );
+ }
+
+ /**
+ * @dataProvider instanceProvider
+ * @param Site $site
+ */
+ public function testAddInterwikiId( Site $site ) {
+ $site->addInterwikiId( 'foobar' );
+ $this->assertTrue( in_array( 'foobar', $site->getInterwikiIds(), true ) );
+ }
+
+ /**
+ * @dataProvider instanceProvider
+ * @param Site $site
+ */
+ public function testGetLanguageCode( Site $site ) {
+ $this->assertTypeOrFalse( 'string', $site->getLanguageCode() );
+ }
+
+ /**
+ * @dataProvider instanceProvider
+ * @param Site $site
+ */
+ public function testSetLanguageCode( Site $site ) {
+ $site->setLanguageCode( 'en' );
+ $this->assertEquals( 'en', $site->getLanguageCode() );
+ }
+
+ /**
+ * @dataProvider instanceProvider
+ * @param Site $site
+ */
+ public function testNormalizePageName( Site $site ) {
+ $this->assertInternalType( 'string', $site->normalizePageName( 'Foobar' ) );
+ }
+
+ /**
+ * @dataProvider instanceProvider
+ * @param Site $site
+ */
+ public function testGetGlobalId( Site $site ) {
+ $this->assertInternalType( 'string', $site->getGlobalId() );
+ }
+
+ /**
+ * @dataProvider instanceProvider
+ * @param Site $site
+ */
+ public function testSetGlobalId( Site $site ) {
+ $site->setGlobalId( 'foobar' );
+ $this->assertEquals( 'foobar', $site->getGlobalId() );
+ }
+
+ /**
+ * @dataProvider instanceProvider
+ * @param Site $site
+ */
+ public function testGetType( Site $site ) {
+ $this->assertInternalType( 'string', $site->getType() );
+ }
+
+ /**
+ * @dataProvider instanceProvider
+ * @param Site $site
+ */
+ public function testGetPath( Site $site ) {
+ $this->assertTypeOrFalse( 'string', $site->getPath( 'page_path' ) );
+ $this->assertTypeOrFalse( 'string', $site->getPath( 'file_path' ) );
+ $this->assertTypeOrFalse( 'string', $site->getPath( 'foobar' ) );
+ }
+
+ /**
+ * @dataProvider instanceProvider
+ * @param Site $site
+ */
+ public function testGetAllPaths( Site $site ) {
+ $this->assertInternalType( 'array', $site->getAllPaths() );
+ }
+
+ /**
+ * @dataProvider instanceProvider
+ * @param Site $site
+ */
+ public function testSetAndRemovePath( Site $site ) {
+ $count = count( $site->getAllPaths() );
+
+ $site->setPath( 'spam', 'http://www.wikidata.org/$1' );
+ $site->setPath( 'spam', 'http://www.wikidata.org/foo/$1' );
+ $site->setPath( 'foobar', 'http://www.wikidata.org/bar/$1' );
+
+ $this->assertEquals( $count + 2, count( $site->getAllPaths() ) );
+
+ $this->assertInternalType( 'string', $site->getPath( 'foobar' ) );
+ $this->assertEquals( 'http://www.wikidata.org/foo/$1', $site->getPath( 'spam' ) );
+
+ $site->removePath( 'spam' );
+ $site->removePath( 'foobar' );
+
+ $this->assertEquals( $count, count( $site->getAllPaths() ) );
+
+ $this->assertFalse( $site->getPath( 'foobar' ) );
+ $this->assertFalse( $site->getPath( 'spam' ) );
+ }
+
+ public function testSetLinkPath() {
+ /* @var SiteObject $site */
+ $site = $this->getRowInstance( $this->getMockFields(), false );
+ $path = "TestPath/$1";
+
+ $site->setLinkPath( $path );
+ $this->assertEquals( $path, $site->getLinkPath() );
+ }
+
+ public function testGetLinkPathType() {
+ /* @var SiteObject $site */
+ $site = $this->getRowInstance( $this->getMockFields(), false );
+
+ $path = 'TestPath/$1';
+ $site->setLinkPath( $path );
+ $this->assertEquals( $path, $site->getPath( $site->getLinkPathType() ) );
+
+ $path = 'AnotherPath/$1';
+ $site->setPath( $site->getLinkPathType(), $path );
+ $this->assertEquals( $path, $site->getLinkPath() );
+ }
+
+ public function testSetPath() {
+ /* @var SiteObject $site */
+ $site = $this->getRowInstance( $this->getMockFields(), false );
+
+ $path = 'TestPath/$1';
+ $site->setPath( 'foo', $path );
+
+ $this->assertEquals( $path, $site->getPath( 'foo' ) );
+ }
+
+ public function provideGetPageUrl() {
+ //NOTE: the assumption that the URL is built by replacing $1
+ // with the urlencoded version of $page
+ // is true for SiteObject but not guaranteed for subclasses.
+ // Subclasses need to override this provider appropriately.
+
+ return array(
+ array( #0
+ 'http://acme.test/TestPath/$1',
+ 'Foo',
+ '/TestPath/Foo',
+ ),
+ array( #1
+ 'http://acme.test/TestScript?x=$1&y=bla',
+ 'Foo',
+ 'TestScript?x=Foo&y=bla',
+ ),
+ array( #2
+ 'http://acme.test/TestPath/$1',
+ 'foo & bar/xyzzy (quux-shmoox?)',
+ '/TestPath/foo%20%26%20bar%2Fxyzzy%20%28quux-shmoox%3F%29',
+ ),
+ );
+ }
+
+ /**
+ * @dataProvider provideGetPageUrl
+ */
+ public function testGetPageUrl( $path, $page, $expected ) {
+ /* @var SiteObject $site */
+ $site = $this->getRowInstance( $this->getMockFields(), false );
+
+ //NOTE: the assumption that getPageUrl is based on getLinkPath
+ // is true for SiteObject but not guaranteed for subclasses.
+ // Subclasses need to override this test case appropriately.
+ $site->setLinkPath( $path );
+ $this->assertContains( $path, $site->getPageUrl() );
+
+ $this->assertContains( $expected, $site->getPageUrl( $page ) );
+ }
+
+ protected function assertTypeOrFalse( $type, $value ) {
+ if ( $value === false ) {
+ $this->assertTrue( true );
+ }
+ else {
+ $this->assertInternalType( $type, $value );
+ }
+ }
+
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+/**
+ * Tests for the Sites class.
+ *
+ * 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
+ * @since 1.21
+ *
+ * @ingroup Site
+ * @ingroup Test
+ *
+ * @group Site
+ * @group Database
+ *
+ * @licence GNU GPL v2+
+ * @author Jeroen De Dauw < jeroendedauw@gmail.com >
+ */
+class SitesTest extends MediaWikiTestCase {
+
+ public function setUp() {
+ parent::setUp();
+ TestSites::insertIntoDb();
+ }
+
+ public function testSingleton() {
+ $this->assertInstanceOf( 'Sites', Sites::singleton() );
+ $this->assertTrue( Sites::singleton() === Sites::singleton() );
+ }
+
+ public function testGetSites() {
+ $this->assertInstanceOf( 'SiteList', Sites::singleton()->getSites() );
+ }
+
+
+ public function testGetSite() {
+ $count = 0;
+ $sites = Sites::singleton()->getSites();
+
+ /**
+ * @var Site $site
+ */
+ foreach ( $sites as $site ) {
+ $this->assertInstanceOf( 'Site', $site );
+
+ $this->assertEquals(
+ $site,
+ Sites::singleton()->getSite( $site->getGlobalId() )
+ );
+
+ if ( ++$count > 100 ) {
+ break;
+ }
+ }
+ }
+
+ public function testNewSite() {
+ $this->assertInstanceOf( 'Site', Sites::newSite() );
+ $this->assertInstanceOf( 'Site', Sites::newSite( 'enwiki' ) );
+ }
+
+ public function testGetGroup() {
+ $wikipedias = Sites::singleton()->getSiteGroup( "wikipedia" );
+
+ $this->assertFalse( $wikipedias->isEmpty() );
+
+ /* @var Site $site */
+ foreach ( $wikipedias as $site ) {
+ $this->assertEquals( 'wikipedia', $site->getGroup() );
+ }
+ }
+
+}
--- /dev/null
+<?php
+
+/**
+ * Holds sites for testing purposes.
+ *
+ * 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
+ * @since 1.21
+ *
+ * @ingroup Site
+ * @ingroup Test
+ *
+ * @group Site
+ *
+ * @licence GNU GPL v2+
+ * @author Jeroen De Dauw < jeroendedauw@gmail.com >
+ */
+class TestSites {
+
+ /**
+ * @since 1.21
+ *
+ * @return array
+ */
+ public static function getSites() {
+ $sites = array();
+
+ $site = Sites::newSite( 'foobar' );
+ $sites[] = $site;
+
+ $site = Sites::newSite( 'enwiktionary' );
+ $site->setGroup( 'wiktionary' );
+ $site->setType( Site::TYPE_MEDIAWIKI );
+ $site->setLanguageCode( 'en' );
+ $site->addNavigationId( 'enwiktionary' );
+ $site->setPath( MediaWikiSite::PATH_PAGE, "https://en.wiktionary.org/wiki/$1" );
+ $site->setPath( MediaWikiSite::PATH_FILE, "https://en.wiktionary.org/w/$1" );
+ $sites[] = $site;
+
+ $site = Sites::newSite( 'dewiktionary' );
+ $site->setGroup( 'wiktionary' );
+ $site->setType( Site::TYPE_MEDIAWIKI );
+ $site->setLanguageCode( 'de' );
+ $site->addInterwikiId( 'dewiktionary' );
+ $site->addInterwikiId( 'wiktionaryde' );
+ $site->setPath( MediaWikiSite::PATH_PAGE, "https://de.wiktionary.org/wiki/$1" );
+ $site->setPath( MediaWikiSite::PATH_FILE, "https://de.wiktionary.org/w/$1" );
+ $sites[] = $site;
+
+ $site = Sites::newSite( 'spam' );
+ $site->setGroup( 'spam' );
+ $site->setType( Site::TYPE_UNKNOWN );
+ $site->setLanguageCode( 'en' );
+ $site->addNavigationId( 'spam' );
+ $site->addNavigationId( 'spamz' );
+ $site->addInterwikiId( 'spamzz' );
+ $site->setLinkPath( "http://spamzz.test/testing/" );
+ $sites[] = $site;
+
+ foreach ( array( 'en', 'de', 'nl', 'sv', 'sr', 'no', 'nn' ) as $langCode ) {
+ $site = Sites::newSite( $langCode . 'wiki' );
+ $site->setGroup( 'wikipedia' );
+ $site->setType( Site::TYPE_MEDIAWIKI );
+ $site->setLanguageCode( $langCode );
+ $site->addInterwikiId( $langCode );
+ $site->addNavigationId( $langCode );
+ $site->setPath( MediaWikiSite::PATH_PAGE, "https://$langCode.wikipedia.org/wiki/$1" );
+ $site->setPath( MediaWikiSite::PATH_FILE, "https://$langCode.wikipedia.org/w/$1" );
+ $sites[] = $site;
+ }
+
+ return $sites;
+ }
+
+ /**
+ * Inserts sites into the database for the unit tests that need them.
+ *
+ * @since 0.1
+ */
+ public static function insertIntoDb() {
+ $dbw = wfGetDB( DB_MASTER );
+
+ $dbw->begin( __METHOD__ );
+
+ $dbw->delete( 'sites', '*', __METHOD__ );
+ $dbw->delete( 'site_identifiers', '*', __METHOD__ );
+
+ /**
+ * @var Site $site
+ */
+ foreach ( TestSites::getSites() as $site ) {
+ $site->save();
+ }
+
+ $dbw->commit( __METHOD__ );
+
+ Sites::singleton()->getSites( false ); // re-cache
+ }
+
+}
\ No newline at end of file
*/
protected $rc;
- function setUp() {
+ protected function setUp() {
}
/** helper to test SpecialRecentchanges::buildMainQueryConds() */
* Provides associated namespaces to test recent changes
* namespaces association filtering.
*/
- public function provideNamespacesAssociations() {
+ public static function provideNamespacesAssociations() {
return array( # (NS => Associated_NS)
array( NS_MAIN, NS_TALK),
array( NS_TALK, NS_MAIN),
class SpecialSearchTest extends MediaWikiTestCase {
private $search;
- function setUp() { }
- function tearDown() { }
-
/**
* @covers SpecialSearch::load
* @dataProvider provideSearchOptionsTests
'Web request with specific NS should override user preference'
),
array(
- $EMPTY_REQUEST, array( 'searchNs2' => 1, 'searchNs14' => 1 ),
- 'advanced', array( 2, 14 ),
- 'Bug 33583: search with no option should honor User search preferences'
- ),
- array(
- $EMPTY_REQUEST, array_fill_keys( array_map( function( $ns ) {
+ $EMPTY_REQUEST, array(
+ 'searchNs2' => 1,
+ 'searchNs14' => 1,
+ ) + array_fill_keys( array_map( function( $ns ) {
return "searchNs$ns";
- }, $defaultNS ), 0 ) + array( 'searchNs2' => 1, 'searchNs14' => 1 ),
+ }, $defaultNS ), 0 ),
'advanced', array( 2, 14 ),
'Bug 33583: search with no option should honor User search preferences'
- . 'and have all other namespace disabled'
+ . ' and have all other namespace disabled'
),
);
}
}
return $u;
}
+
+ /**
+ * Verify we do not expand search term in <title> on search result page
+ * https://gerrit.wikimedia.org/r/4841
+ */
+ function testSearchTermIsNotExpanded() {
+
+ # Initialize [[Special::Search]]
+ $search = new SpecialSearch();
+ $search->getContext()->setTitle( Title::newFromText('Special:Search' ) );
+ $search->load();
+
+ # Simulate a user searching for a given term
+ $term = '{{SITENAME}}';
+ $search->showResults( $term );
+
+ # Lookup the HTML page title set for that page
+ $pageTitle = $search
+ ->getContext()
+ ->getOutput()
+ ->getHTMLTitle();
+
+ # Compare :-]
+ $this->assertRegExp(
+ '/' . preg_quote( $term ) . '/',
+ $pageTitle,
+ "Search term '{$term}' should not be expanded in Special:Search <title>"
+ );
+
+ }
}
*/
class UploadFromUrlTest extends ApiTestCase {
- public function setUp() {
+ protected function setUp() {
global $wgEnableUploads, $wgAllowCopyUploads, $wgAllowAsyncCopyUploads;
parent::setUp();
*/
public static $users;
- public function setUp() {
+ protected function setUp() {
parent::setUp();
// Setup a file for bug 29408
);
}
+ protected function tearDown() {
+ if ( file_exists( $this->bug29408File . "." ) ) {
+ unlink( $this->bug29408File . "." );
+ }
+
+ if ( file_exists( $this->bug29408File ) ) {
+ unlink( $this->bug29408File );
+ }
+
+ parent::tearDown();
+ }
+
public function testBug29408() {
global $wgUser;
$wgUser = self::$users['uploader']->user;
$request = new FauxRequest( array( 'wpFileKey' => 'testkey-test.test', 'wpSessionKey' => 'foo') );
$this->assertTrue( UploadFromStash::isValidRequest($request), 'Check key precedence' );
}
-
- public function tearDown() {
- parent::tearDown();
-
- if( file_exists( $this->bug29408File . "." ) ) {
- unlink( $this->bug29408File . "." );
- }
-
- if( file_exists( $this->bug29408File ) ) {
- unlink( $this->bug29408File );
- }
- }
}
protected $upload;
- function setUp() {
+ protected function setUp() {
global $wgHooks;
parent::setUp();
};
}
- function tearDown() {
+ protected function tearDown() {
global $wgHooks;
$wgHooks = $this->hooks;
}
* First checks the return code
* of UploadBase::getTitle() and then the actual returned title
*
- * @dataProvider dataTestTitleValidation
+ * @dataProvider provideTestTitleValidation
*/
public function testTitleValidation( $srcFilename, $dstFilename, $code, $msg ) {
/* Check the result code */
/**
* Test various forms of valid and invalid titles that can be supplied.
*/
- public function dataTestTitleValidation() {
+ public static function provideTestTitleValidation() {
return array(
/* Test a valid title */
array( 'ValidTitle.jpg', 'ValidTitle.jpg', UploadBase::OK,
class LanguageAmTest extends MediaWikiTestCase {
private $lang;
- function setUp() {
+ protected function setUp() {
$this->lang = Language::factory( 'Am' );
}
- function tearDown() {
+ protected function tearDown() {
unset( $this->lang );
}
class LanguageArTest extends MediaWikiTestCase {
private $lang;
- function setUp() {
+ protected function setUp() {
$this->lang = Language::factory( 'Ar' );
}
- function tearDown() {
+ protected function tearDown() {
unset( $this->lang );
}
class LanguageBeTest extends MediaWikiTestCase {
private $lang;
- function setUp() {
+ protected function setUp() {
$this->lang = Language::factory( 'Be' );
}
- function tearDown() {
+ protected function tearDown() {
unset( $this->lang );
}
class LanguageBeTaraskTest extends MediaWikiTestCase {
private $lang;
- function setUp() {
+ protected function setUp() {
$this->lang = Language::factory( 'Be-tarask' );
}
- function tearDown() {
+ protected function tearDown() {
unset( $this->lang );
}
class LanguageBhTest extends MediaWikiTestCase {
private $lang;
- function setUp() {
+ protected function setUp() {
$this->lang = Language::factory( 'Bh' );
}
- function tearDown() {
+ protected function tearDown() {
unset( $this->lang );
}
class LanguageBsTest extends MediaWikiTestCase {
private $lang;
- function setUp() {
+ protected function setUp() {
$this->lang = Language::factory( 'Bs' );
}
- function tearDown() {
+ protected function tearDown() {
unset( $this->lang );
}
class LanguageCsTest extends MediaWikiTestCase {
private $lang;
- function setUp() {
+ protected function setUp() {
$this->lang = Language::factory( 'cs' );
}
- function tearDown() {
+ protected function tearDown() {
unset( $this->lang );
}
class LanguageCuTest extends MediaWikiTestCase {
private $lang;
- function setUp() {
+ protected function setUp() {
$this->lang = Language::factory( 'cu' );
}
- function tearDown() {
+ protected function tearDown() {
unset( $this->lang );
}
class LanguageCyTest extends MediaWikiTestCase {
private $lang;
- function setUp() {
+ protected function setUp() {
$this->lang = Language::factory( 'cy' );
}
- function tearDown() {
+ protected function tearDown() {
unset( $this->lang );
}
class LanguageDsbTest extends MediaWikiTestCase {
private $lang;
- function setUp() {
+ protected function setUp() {
$this->lang = Language::factory( 'dsb' );
}
- function tearDown() {
+ protected function tearDown() {
unset( $this->lang );
}
class LanguageFrTest extends MediaWikiTestCase {
private $lang;
- function setUp() {
+ protected function setUp() {
$this->lang = Language::factory( 'fr' );
}
- function tearDown() {
+ protected function tearDown() {
unset( $this->lang );
}
class LanguageGaTest extends MediaWikiTestCase {
private $lang;
- function setUp() {
+ protected function setUp() {
$this->lang = Language::factory( 'ga' );
}
- function tearDown() {
+ protected function tearDown() {
unset( $this->lang );
}
class LanguageGdTest extends MediaWikiTestCase {
private $lang;
- function setUp() {
+ protected function setUp() {
$this->lang = Language::factory( 'gd' );
}
- function tearDown() {
+ protected function tearDown() {
unset( $this->lang );
}
class LanguageGvTest extends MediaWikiTestCase {
private $lang;
- function setUp() {
+ protected function setUp() {
$this->lang = Language::factory( 'gv' );
}
- function tearDown() {
+ protected function tearDown() {
unset( $this->lang );
}
class LanguageHeTest extends MediaWikiTestCase {
private $lang;
- function setUp() {
+ protected function setUp() {
$this->lang = Language::factory( 'he' );
}
- function tearDown() {
+ protected function tearDown() {
unset( $this->lang );
}
class LanguageHiTest extends MediaWikiTestCase {
private $lang;
- function setUp() {
+ protected function setUp() {
$this->lang = Language::factory( 'Hi' );
}
- function tearDown() {
+ protected function tearDown() {
unset( $this->lang );
}
class LanguageHrTest extends MediaWikiTestCase {
private $lang;
- function setUp() {
+ protected function setUp() {
$this->lang = Language::factory( 'hr' );
}
- function tearDown() {
+ protected function tearDown() {
unset( $this->lang );
}
class LanguageHsbTest extends MediaWikiTestCase {
private $lang;
- function setUp() {
+ protected function setUp() {
$this->lang = Language::factory( 'hsb' );
}
- function tearDown() {
+ protected function tearDown() {
unset( $this->lang );
}
class LanguageHuTest extends MediaWikiTestCase {
private $lang;
- function setUp() {
+ protected function setUp() {
$this->lang = Language::factory( 'Hu' );
}
- function tearDown() {
+ protected function tearDown() {
unset( $this->lang );
}
class LanguageHyTest extends MediaWikiTestCase {
private $lang;
- function setUp() {
+ protected function setUp() {
$this->lang = Language::factory( 'hy' );
}
- function tearDown() {
+ protected function tearDown() {
unset( $this->lang );
}
class LanguageKshTest extends MediaWikiTestCase {
private $lang;
- function setUp() {
+ protected function setUp() {
$this->lang = Language::factory( 'ksh' );
}
- function tearDown() {
+ protected function tearDown() {
unset( $this->lang );
}
class LanguageLnTest extends MediaWikiTestCase {
private $lang;
- function setUp() {
+ protected function setUp() {
$this->lang = Language::factory( 'ln' );
}
- function tearDown() {
+ protected function tearDown() {
unset( $this->lang );
}
class LanguageLtTest extends MediaWikiTestCase {
private $lang;
- function setUp() {
+ protected function setUp() {
$this->lang = Language::factory( 'Lt' );
}
- function tearDown() {
+ protected function tearDown() {
unset( $this->lang );
}
class LanguageLvTest extends MediaWikiTestCase {
private $lang;
- function setUp() {
+ protected function setUp() {
$this->lang = Language::factory( 'lv' );
}
- function tearDown() {
+ protected function tearDown() {
unset( $this->lang );
}
class LanguageMgTest extends MediaWikiTestCase {
private $lang;
- function setUp() {
+ protected function setUp() {
$this->lang = Language::factory( 'mg' );
}
- function tearDown() {
+ protected function tearDown() {
unset( $this->lang );
}
class LanguageMkTest extends MediaWikiTestCase {
private $lang;
- function setUp() {
+ protected function setUp() {
$this->lang = Language::factory( 'mk' );
}
- function tearDown() {
+ protected function tearDown() {
unset( $this->lang );
}
class LanguageMlTest extends MediaWikiTestCase {
private $lang;
- function setUp() {
+ protected function setUp() {
$this->lang = Language::factory( 'Ml' );
}
- function tearDown() {
+ protected function tearDown() {
unset( $this->lang );
}
class LanguageMoTest extends MediaWikiTestCase {
private $lang;
- function setUp() {
+ protected function setUp() {
$this->lang = Language::factory( 'mo' );
}
- function tearDown() {
+ protected function tearDown() {
unset( $this->lang );
}
class LanguageMtTest extends MediaWikiTestCase {
private $lang;
- function setUp() {
+ protected function setUp() {
$this->lang = Language::factory( 'mt' );
}
- function tearDown() {
+ protected function tearDown() {
unset( $this->lang );
}
class LanguageNlTest extends MediaWikiTestCase {
private $lang;
- function setUp() {
+ protected function setUp() {
$this->lang = Language::factory( 'Nl' );
}
- function tearDown() {
+ protected function tearDown() {
unset( $this->lang );
}
class LanguageNsoTest extends MediaWikiTestCase {
private $lang;
- function setUp() {
+ protected function setUp() {
$this->lang = Language::factory( 'nso' );
}
- function tearDown() {
+ protected function tearDown() {
unset( $this->lang );
}
class LanguagePlTest extends MediaWikiTestCase {
private $lang;
- function setUp() {
+ protected function setUp() {
$this->lang = Language::factory( 'pl' );
}
- function tearDown() {
+ protected function tearDown() {
unset( $this->lang );
}
class LanguageRoTest extends MediaWikiTestCase {
private $lang;
- function setUp() {
+ protected function setUp() {
$this->lang = Language::factory( 'ro' );
}
- function tearDown() {
+ protected function tearDown() {
unset( $this->lang );
}
class LanguageRuTest extends MediaWikiTestCase {
private $lang;
- function setUp() {
+ protected function setUp() {
$this->lang = Language::factory( 'ru' );
}
- function tearDown() {
+ protected function tearDown() {
unset( $this->lang );
}
class LanguageSeTest extends MediaWikiTestCase {
private $lang;
- function setUp() {
+ protected function setUp() {
$this->lang = Language::factory( 'se' );
}
- function tearDown() {
+ protected function tearDown() {
unset( $this->lang );
}
class LanguageSgsTest extends MediaWikiTestCase {
private $lang;
- function setUp() {
+ protected function setUp() {
$this->lang = Language::factory( 'Sgs' );
}
- function tearDown() {
+ protected function tearDown() {
unset( $this->lang );
}
class LanguageShTest extends MediaWikiTestCase {
private $lang;
- function setUp() {
+ protected function setUp() {
$this->lang = Language::factory( 'sh' );
}
- function tearDown() {
+ protected function tearDown() {
unset( $this->lang );
}
class LanguageSkTest extends MediaWikiTestCase {
private $lang;
- function setUp() {
+ protected function setUp() {
$this->lang = Language::factory( 'sk' );
}
- function tearDown() {
+ protected function tearDown() {
unset( $this->lang );
}
class LanguageSlTest extends MediaWikiTestCase {
private $lang;
- function setUp() {
+ protected function setUp() {
$this->lang = Language::factory( 'sl' );
}
- function tearDown() {
+ protected function tearDown() {
unset( $this->lang );
}
class LanguageSmaTest extends MediaWikiTestCase {
private $lang;
- function setUp() {
+ protected function setUp() {
$this->lang = Language::factory( 'sma' );
}
- function tearDown() {
+ protected function tearDown() {
unset( $this->lang );
}
/* Language object. Initialized before each test */
private $lang;
- function setUp() {
+ protected function setUp() {
$this->lang = Language::factory( 'sr' );
}
- function tearDown() {
+ protected function tearDown() {
unset( $this->lang );
}
*/
private $lang;
- function setUp() {
+ protected function setUp() {
$this->lang = Language::factory( 'en' );
}
- function tearDown() {
+ protected function tearDown() {
unset( $this->lang );
}
class LanguageTiTest extends MediaWikiTestCase {
private $lang;
- function setUp() {
+ protected function setUp() {
$this->lang = Language::factory( 'Ti' );
}
- function tearDown() {
+ protected function tearDown() {
unset( $this->lang );
}
class LanguageTlTest extends MediaWikiTestCase {
private $lang;
- function setUp() {
+ protected function setUp() {
$this->lang = Language::factory( 'Tl' );
}
- function tearDown() {
+ protected function tearDown() {
unset( $this->lang );
}
class LanguageTrTest extends MediaWikiTestCase {
private $lang;
- function setUp() {
+ protected function setUp() {
$this->lang = Language::factory( 'Tr' );
}
- function tearDown() {
+ protected function tearDown() {
unset( $this->lang );
}
class LanguageUkTest extends MediaWikiTestCase {
private $lang;
- function setUp() {
+ protected function setUp() {
$this->lang = Language::factory( 'Uk' );
}
- function tearDown() {
+ protected function tearDown() {
unset( $this->lang );
}
/* Language object. Initialized before each test */
private $lang;
- function setUp() {
+ protected function setUp() {
$this->lang = Language::factory( 'uz' );
}
- function tearDown() {
+ protected function tearDown() {
unset( $this->lang );
}
class LanguageWaTest extends MediaWikiTestCase {
private $lang;
- function setUp() {
+ protected function setUp() {
$this->lang = Language::factory( 'Wa' );
}
- function tearDown() {
+ protected function tearDown() {
unset( $this->lang );
}
* @throws MWExcepion
*/
protected function addRevision( Page $page, $text, $summary ) {
- $status = $page->doEdit( $text, $summary );
+ $status = $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ), $summary );
if ( $status->isGood() ) {
$value = $status->getValue();
$revision = $value['revision'];
*
* Clears $wgUser, and reports errors from addDBData to PHPUnit
*/
- public function setUp() {
+ protected function setUp() {
global $wgUser;
parent::setUp();
* @param $text_sha1 string: the base36 SHA-1 of the revision's text
* @param $text string|false: (optional) The revision's string, or false to check for a
* revision stub
+ * @param $model String: the expected content model id (default: CONTENT_MODEL_WIKITEXT)
+ * @param $format String: the expected format model id (default: CONTENT_FORMAT_WIKITEXT)
* @param $parentid int|false: (optional) id of the parent revision
*/
- protected function assertRevision( $id, $summary, $text_id, $text_bytes, $text_sha1, $text = false, $parentid = false ) {
+ protected function assertRevision( $id, $summary, $text_id, $text_bytes, $text_sha1, $text = false, $parentid = false,
+ $model = CONTENT_MODEL_WIKITEXT, $format = CONTENT_FORMAT_WIKITEXT ) {
$this->assertNodeStart( "revision" );
$this->skipWhitespace();
$this->skipWhitespace();
$this->assertTextNode( "comment", $summary );
+ $this->skipWhitespace();
+
+ if ( $this->xml->name == "text" ) {
+ // note: <text> tag may occur here or at the very end.
+ $text_found = true;
+ $this->assertText( $id, $text_id, $text_bytes, $text );
+ } else {
+ $text_found = false;
+ }
$this->assertTextNode( "sha1", $text_sha1 );
+ $this->assertTextNode( "model", $model );
+ $this->skipWhitespace();
+
+ $this->assertTextNode( "format", $format );
+ $this->skipWhitespace();
+
+ if ( !$text_found ) {
+ $this->assertText( $id, $text_id, $text_bytes, $text );
+ }
+
+ $this->assertNodeEnd( "revision" );
+ $this->skipWhitespace();
+ }
+
+ protected function assertText( $id, $text_id, $text_bytes, $text ) {
$this->assertNodeStart( "text", false );
if ( $text_bytes !== false ) {
$this->assertEquals( $this->xml->getAttribute( "bytes" ), $text_bytes,
$this->assertNodeEnd( "text" );
$this->skipWhitespace();
}
-
- $this->assertNodeEnd( "revision" );
- $this->skipWhitespace();
}
-
}
<siteinfo>
<sitename>wikisvn</sitename>
<base>http://localhost/wiki-svn/index.php/Main_Page</base>
- <generator>MediaWiki 1.20alpha</generator>
+ <generator>MediaWiki 1.21alpha</generator>
<case>first-letter</case>
<namespaces>
<namespace key="-2" case="first-letter">Media</namespace>
<comment>BackupDumperTestP1Summary1</comment>
<sha1>0bolhl6ol7i6x0e7yq91gxgaan39j87</sha1>
<text xml:space="preserve">BackupDumperTestP1Text1</text>
+ <model name="wikitext">1</model>
+ <format mime="text/x-wiki">1</format>
</revision>
</page>
';
<comment>BackupDumperTestP2Summary1</comment>
<sha1>jprywrymfhysqllua29tj3sc7z39dl2</sha1>
<text xml:space="preserve">BackupDumperTestP2Text1</text>
+ <model name="wikitext">1</model>
+ <format mime="text/x-wiki">1</format>
</revision>
<revision>
<id>5</id>
<comment>BackupDumperTestP2Summary4 extra</comment>
<sha1>6o1ciaxa6pybnqprmungwofc4lv00wv</sha1>
<text xml:space="preserve">BackupDumperTestP2Text4 some additional Text</text>
+ <model name="wikitext">1</model>
+ <format mime="text/x-wiki">1</format>
</revision>
</page>
';
</contributor>
<comment>Talk BackupDumperTestP1 Summary1</comment>
<sha1>nktofwzd0tl192k3zfepmlzxoax1lpe</sha1>
+ <model name="wikitext">1</model>
+ <format mime="text/x-wiki">1</format>
<text xml:space="preserve">Talk about BackupDumperTestP1 Text1</text>
</revision>
</page>
$this->tablesUsed[] = 'revision';
$this->tablesUsed[] = 'text';
+ $ns = $this->getDefaultWikitextNS();
+
try {
// Simple page
- $title = Title::newFromText( 'BackupDumperTestP1' );
+ $title = Title::newFromText( 'BackupDumperTestP1', $ns );
$page = WikiPage::factory( $title );
list( $this->revId1_1, $this->textId1_1 ) = $this->addRevision( $page,
"BackupDumperTestP1Text1", "BackupDumperTestP1Summary1" );
$this->pageId1 = $page->getId();
// Page with more than one revision
- $title = Title::newFromText( 'BackupDumperTestP2' );
+ $title = Title::newFromText( 'BackupDumperTestP2', $ns );
$page = WikiPage::factory( $title );
list( $this->revId2_1, $this->textId2_1 ) = $this->addRevision( $page,
"BackupDumperTestP2Text1", "BackupDumperTestP2Summary1" );
$this->pageId2 = $page->getId();
// Deleted page.
- $title = Title::newFromText( 'BackupDumperTestP3' );
+ $title = Title::newFromText( 'BackupDumperTestP3', $ns );
$page = WikiPage::factory( $title );
list( $this->revId3_1, $this->textId3_1 ) = $this->addRevision( $page,
"BackupDumperTestP3Text1", "BackupDumperTestP2Summary1" );
$page->doDeleteArticle( "Testing ;)" );
// Page from non-default namespace
+
+ if ( $ns === NS_TALK ) {
+ //@todo: work around this.
+ throw new MWException( "The default wikitext namespace is the talk namespace. "
+ . " We can't currently deal with that.");
+ }
+
$title = Title::newFromText( 'BackupDumperTestP1', NS_TALK );
$page = WikiPage::factory( $title );
list( $this->revId4_1, $this->textId4_1 ) = $this->addRevision( $page,
}
- public function setUp() {
+ protected function setUp() {
parent::setUp();
// Since we will restrict dumping by page ranges (to allow
$this->assertEmpty( $files, "Remaining unchecked files" );
// ... and have dealt with more than one checkpoint file
- $this->assertGreaterThan( 1, $checkpointFiles, "# of checkpoint files" );
+ $this->assertGreaterThan( 1, $checkpointFiles, "expected more than 1 checkpoint to have been created. Checkpoint interval is $checkpointAfter seconds, maybe your computer is too fast?" );
$this->expectETAOutput();
}
<siteinfo>
<sitename>wikisvn</sitename>
<base>http://localhost/wiki-svn/index.php/Main_Page</base>
- <generator>MediaWiki 1.20alpha</generator>
+ <generator>MediaWiki 1.21alpha</generator>
<case>first-letter</case>
<namespaces>
<namespace key="-2" case="first-letter">Media</namespace>
</contributor>
<comment>BackupDumperTestP1Summary1</comment>
<sha1>0bolhl6ol7i6x0e7yq91gxgaan39j87</sha1>
+ <model>wikitext</model>
+ <format>text/x-wiki</format>
<text id="' . $this->textId1_1 . '" bytes="23" />
</revision>
</page>
</contributor>
<comment>BackupDumperTestP2Summary1</comment>
<sha1>jprywrymfhysqllua29tj3sc7z39dl2</sha1>
+ <model>wikitext</model>
+ <format>text/x-wiki</format>
<text id="' . $this->textId2_1 . '" bytes="23" />
</revision>
<revision>
</contributor>
<comment>BackupDumperTestP2Summary2</comment>
<sha1>b7vj5ks32po5m1z1t1br4o7scdwwy95</sha1>
+ <model>wikitext</model>
+ <format>text/x-wiki</format>
<text id="' . $this->textId2_2 . '" bytes="23" />
</revision>
<revision>
</contributor>
<comment>BackupDumperTestP2Summary3</comment>
<sha1>jfunqmh1ssfb8rs43r19w98k28gg56r</sha1>
+ <model>wikitext</model>
+ <format>text/x-wiki</format>
<text id="' . $this->textId2_3 . '" bytes="23" />
</revision>
<revision>
</contributor>
<comment>BackupDumperTestP2Summary4 extra</comment>
<sha1>6o1ciaxa6pybnqprmungwofc4lv00wv</sha1>
+ <model>wikitext</model>
+ <format>text/x-wiki</format>
<text id="' . $this->textId2_4 . '" bytes="44" />
</revision>
</page>
</contributor>
<comment>Talk BackupDumperTestP1 Summary1</comment>
<sha1>nktofwzd0tl192k3zfepmlzxoax1lpe</sha1>
+ <model>wikitext</model>
+ <format>text/x-wiki</format>
<text id="' . $this->textId4_1 . '" bytes="35" />
</revision>
</page>
// We'll add several pages, revision and texts. The following variables hold the
// corresponding ids.
private $pageId1, $pageId2, $pageId3, $pageId4, $pageId5;
+ private $pageTitle1, $pageTitle2, $pageTitle3, $pageTitle4, $pageTitle5;
private $revId1_1, $textId1_1;
private $revId2_1, $textId2_1, $revId2_2, $textId2_2;
private $revId2_3, $textId2_3, $revId2_4, $textId2_4;
private $revId3_1, $textId3_1, $revId3_2, $textId3_2;
private $revId4_1, $textId4_1;
+ private $namespace, $talk_namespace;
function addDBData() {
$this->tablesUsed[] = 'page';
$this->tablesUsed[] = 'text';
try {
- $title = Title::newFromText( 'BackupDumperTestP1' );
- $page = WikiPage::factory( $title );
+ $this->namespace = $this->getDefaultWikitextNS();
+ $this->talk_namespace = NS_TALK;
+
+ if ( $this->namespace === $this->talk_namespace ) {
+ //@todo: work around this.
+ throw new MWException( "The default wikitext namespace is the talk namespace. "
+ . " We can't currently deal with that.");
+ }
+
+ $this->pageTitle1 = Title::newFromText( 'BackupDumperTestP1', $this->namespace );
+ $page = WikiPage::factory( $this->pageTitle1 );
list( $this->revId1_1, $this->textId1_1 ) = $this->addRevision( $page,
"BackupDumperTestP1Text1", "BackupDumperTestP1Summary1" );
$this->pageId1 = $page->getId();
- $title = Title::newFromText( 'BackupDumperTestP2' );
- $page = WikiPage::factory( $title );
+ $this->pageTitle2 = Title::newFromText( 'BackupDumperTestP2', $this->namespace );
+ $page = WikiPage::factory( $this->pageTitle2 );
list( $this->revId2_1, $this->textId2_1 ) = $this->addRevision( $page,
"BackupDumperTestP2Text1", "BackupDumperTestP2Summary1" );
list( $this->revId2_2, $this->textId2_2 ) = $this->addRevision( $page,
"BackupDumperTestP2Summary4 extra " );
$this->pageId2 = $page->getId();
- $title = Title::newFromText( 'BackupDumperTestP3' );
- $page = WikiPage::factory( $title );
+ $this->pageTitle3 = Title::newFromText( 'BackupDumperTestP3', $this->namespace );
+ $page = WikiPage::factory( $this->pageTitle3 );
list( $this->revId3_1, $this->textId3_1 ) = $this->addRevision( $page,
"BackupDumperTestP3Text1", "BackupDumperTestP2Summary1" );
list( $this->revId3_2, $this->textId3_2 ) = $this->addRevision( $page,
$this->pageId3 = $page->getId();
$page->doDeleteArticle( "Testing ;)" );
- $title = Title::newFromText( 'BackupDumperTestP1', NS_TALK );
- $page = WikiPage::factory( $title );
+ $this->pageTitle4 = Title::newFromText( 'BackupDumperTestP1', $this->talk_namespace );
+ $page = WikiPage::factory( $this->pageTitle4 );
list( $this->revId4_1, $this->textId4_1 ) = $this->addRevision( $page,
"Talk about BackupDumperTestP1 Text1",
"Talk BackupDumperTestP1 Summary1" );
}
- public function setUp() {
+ protected function setUp() {
parent::setUp();
// Since we will restrict dumping by page ranges (to allow
$this->assertDumpStart( $fname );
// Page 1
- $this->assertPageStart( $this->pageId1, NS_MAIN, "BackupDumperTestP1" );
+ $this->assertPageStart( $this->pageId1, $this->namespace, $this->pageTitle1->getPrefixedText() );
$this->assertRevision( $this->revId1_1, "BackupDumperTestP1Summary1",
$this->textId1_1, 23, "0bolhl6ol7i6x0e7yq91gxgaan39j87",
"BackupDumperTestP1Text1" );
$this->assertPageEnd();
// Page 2
- $this->assertPageStart( $this->pageId2, NS_MAIN, "BackupDumperTestP2" );
+ $this->assertPageStart( $this->pageId2, $this->namespace, $this->pageTitle2->getPrefixedText() );
$this->assertRevision( $this->revId2_1, "BackupDumperTestP2Summary1",
$this->textId2_1, 23, "jprywrymfhysqllua29tj3sc7z39dl2",
"BackupDumperTestP2Text1" );
// -> Page is marked deleted. Hence not visible
// Page 4
- $this->assertPageStart( $this->pageId4, NS_TALK, "Talk:BackupDumperTestP1" );
+ $this->assertPageStart( $this->pageId4, $this->talk_namespace, $this->pageTitle4->getPrefixedText() );
$this->assertRevision( $this->revId4_1, "Talk BackupDumperTestP1 Summary1",
$this->textId4_1, 35, "nktofwzd0tl192k3zfepmlzxoax1lpe",
"Talk about BackupDumperTestP1 Text1" );
$this->assertDumpStart( $fname );
// Page 1
- $this->assertPageStart( $this->pageId1, NS_MAIN, "BackupDumperTestP1" );
+ $this->assertPageStart( $this->pageId1, $this->namespace, $this->pageTitle1->getPrefixedText() );
$this->assertRevision( $this->revId1_1, "BackupDumperTestP1Summary1",
$this->textId1_1, 23, "0bolhl6ol7i6x0e7yq91gxgaan39j87" );
$this->assertPageEnd();
// Page 2
- $this->assertPageStart( $this->pageId2, NS_MAIN, "BackupDumperTestP2" );
+ $this->assertPageStart( $this->pageId2, $this->namespace, $this->pageTitle2->getPrefixedText() );
$this->assertRevision( $this->revId2_1, "BackupDumperTestP2Summary1",
$this->textId2_1, 23, "jprywrymfhysqllua29tj3sc7z39dl2" );
$this->assertRevision( $this->revId2_2, "BackupDumperTestP2Summary2",
// -> Page is marked deleted. Hence not visible
// Page 4
- $this->assertPageStart( $this->pageId4, NS_TALK, "Talk:BackupDumperTestP1" );
+ $this->assertPageStart( $this->pageId4, $this->talk_namespace, $this->pageTitle4->getPrefixedText() );
$this->assertRevision( $this->revId4_1, "Talk BackupDumperTestP1 Summary1",
$this->textId4_1, 35, "nktofwzd0tl192k3zfepmlzxoax1lpe" );
$this->assertPageEnd();
$this->assertDumpStart( $fname );
// Page 1
- $this->assertPageStart( $this->pageId1, NS_MAIN, "BackupDumperTestP1" );
+ $this->assertPageStart( $this->pageId1, $this->namespace, $this->pageTitle1->getPrefixedText() );
$this->assertRevision( $this->revId1_1, "BackupDumperTestP1Summary1",
$this->textId1_1, 23, "0bolhl6ol7i6x0e7yq91gxgaan39j87" );
$this->assertPageEnd();
// Page 2
- $this->assertPageStart( $this->pageId2, NS_MAIN, "BackupDumperTestP2" );
+ $this->assertPageStart( $this->pageId2, $this->namespace, $this->pageTitle2->getPrefixedText() );
$this->assertRevision( $this->revId2_4, "BackupDumperTestP2Summary4 extra",
$this->textId2_4, 44, "6o1ciaxa6pybnqprmungwofc4lv00wv", false, $this->revId2_3 );
$this->assertPageEnd();
// -> Page is marked deleted. Hence not visible
// Page 4
- $this->assertPageStart( $this->pageId4, NS_TALK, "Talk:BackupDumperTestP1" );
+ $this->assertPageStart( $this->pageId4, $this->talk_namespace, $this->pageTitle4->getPrefixedText() );
$this->assertRevision( $this->revId4_1, "Talk BackupDumperTestP1 Summary1",
$this->textId4_1, 35, "nktofwzd0tl192k3zfepmlzxoax1lpe" );
$this->assertPageEnd();
$this->assertDumpStart( $fname );
// Page 1
- $this->assertPageStart( $this->pageId1, NS_MAIN, "BackupDumperTestP1" );
+ $this->assertPageStart( $this->pageId1, $this->namespace, $this->pageTitle1->getPrefixedText() );
$this->assertRevision( $this->revId1_1, "BackupDumperTestP1Summary1",
$this->textId1_1, 23, "0bolhl6ol7i6x0e7yq91gxgaan39j87" );
$this->assertPageEnd();
// Page 2
- $this->assertPageStart( $this->pageId2, NS_MAIN, "BackupDumperTestP2" );
+ $this->assertPageStart( $this->pageId2, $this->namespace, $this->pageTitle2->getPrefixedText() );
$this->assertRevision( $this->revId2_4, "BackupDumperTestP2Summary4 extra",
$this->textId2_4, 44, "6o1ciaxa6pybnqprmungwofc4lv00wv", false, $this->revId2_3 );
$this->assertPageEnd();
// -> Page is marked deleted. Hence not visible
// Page 4
- $this->assertPageStart( $this->pageId4, NS_TALK, "Talk:BackupDumperTestP1" );
+ $this->assertPageStart( $this->pageId4, $this->talk_namespace, $this->pageTitle4->getPrefixedText() );
$this->assertRevision( $this->revId4_1, "Talk BackupDumperTestP1 Summary1",
$this->textId4_1, 35, "nktofwzd0tl192k3zfepmlzxoax1lpe" );
$this->assertPageEnd();
$this->assertDumpStart( $fnameMetaHistory );
// Page 1
- $this->assertPageStart( $this->pageId1, NS_MAIN, "BackupDumperTestP1" );
+ $this->assertPageStart( $this->pageId1, $this->namespace, $this->pageTitle1->getPrefixedText() );
$this->assertRevision( $this->revId1_1, "BackupDumperTestP1Summary1",
$this->textId1_1, 23, "0bolhl6ol7i6x0e7yq91gxgaan39j87" );
$this->assertPageEnd();
// Page 2
- $this->assertPageStart( $this->pageId2, NS_MAIN, "BackupDumperTestP2" );
+ $this->assertPageStart( $this->pageId2, $this->namespace, $this->pageTitle2->getPrefixedText() );
$this->assertRevision( $this->revId2_1, "BackupDumperTestP2Summary1",
$this->textId2_1, 23, "jprywrymfhysqllua29tj3sc7z39dl2" );
$this->assertRevision( $this->revId2_2, "BackupDumperTestP2Summary2",
// -> Page is marked deleted. Hence not visible
// Page 4
- $this->assertPageStart( $this->pageId4, NS_TALK, "Talk:BackupDumperTestP1" );
+ $this->assertPageStart( $this->pageId4, $this->talk_namespace, $this->pageTitle4->getPrefixedText() );
$this->assertRevision( $this->revId4_1, "Talk BackupDumperTestP1 Summary1",
$this->textId4_1, 35, "nktofwzd0tl192k3zfepmlzxoax1lpe" );
$this->assertPageEnd();
$this->assertDumpStart( $fnameMetaCurrent );
// Page 1
- $this->assertPageStart( $this->pageId1, NS_MAIN, "BackupDumperTestP1" );
+ $this->assertPageStart( $this->pageId1, $this->namespace, $this->pageTitle1->getPrefixedText() );
$this->assertRevision( $this->revId1_1, "BackupDumperTestP1Summary1",
$this->textId1_1, 23, "0bolhl6ol7i6x0e7yq91gxgaan39j87" );
$this->assertPageEnd();
// Page 2
- $this->assertPageStart( $this->pageId2, NS_MAIN, "BackupDumperTestP2" );
+ $this->assertPageStart( $this->pageId2, $this->namespace, $this->pageTitle2->getPrefixedText() );
$this->assertRevision( $this->revId2_4, "BackupDumperTestP2Summary4 extra",
$this->textId2_4, 44, "6o1ciaxa6pybnqprmungwofc4lv00wv", false, $this->revId2_3 );
$this->assertPageEnd();
// -> Page is marked deleted. Hence not visible
// Page 4
- $this->assertPageStart( $this->pageId4, NS_TALK, "Talk:BackupDumperTestP1" );
+ $this->assertPageStart( $this->pageId4, $this->talk_namespace, $this->pageTitle4->getPrefixedText() );
$this->assertRevision( $this->revId4_1, "Talk BackupDumperTestP1 Summary1",
$this->textId4_1, 35, "nktofwzd0tl192k3zfepmlzxoax1lpe" );
$this->assertPageEnd();
$this->assertDumpStart( $fnameArticles );
// Page 1
- $this->assertPageStart( $this->pageId1, NS_MAIN, "BackupDumperTestP1" );
+ $this->assertPageStart( $this->pageId1, $this->namespace, $this->pageTitle1->getPrefixedText() );
$this->assertRevision( $this->revId1_1, "BackupDumperTestP1Summary1",
$this->textId1_1, 23, "0bolhl6ol7i6x0e7yq91gxgaan39j87" );
$this->assertPageEnd();
// Page 2
- $this->assertPageStart( $this->pageId2, NS_MAIN, "BackupDumperTestP2" );
+ $this->assertPageStart( $this->pageId2, $this->namespace, $this->pageTitle2->getPrefixedText() );
$this->assertRevision( $this->revId2_4, "BackupDumperTestP2Summary4 extra",
$this->textId2_4, 44, "6o1ciaxa6pybnqprmungwofc4lv00wv", false, $this->revId2_3 );
$this->assertPageEnd();
// -> Page is marked deleted. Hence not visible
// Page 4
- // -> Page is not in NS_MAIN. Hence not visible
+ // -> Page is not in $this->namespace. Hence not visible
$this->assertDumpEnd();
* @throws MWExcepion
*/
private function addRevision( $page, $text, $summary ) {
- $status = $page->doEdit( $text, $summary );
+ $status = $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ), $summary );
if ( $status->isGood() ) {
$value = $status->getValue();
$revision = $value['revision'];
$this->tablesUsed[] = 'revision';
$this->tablesUsed[] = 'text';
+ $wikitextNamespace = $this->getDefaultWikitextNS();
+
try {
- $title = Title::newFromText( 'FetchTextTestPage1' );
+ $title = Title::newFromText( 'FetchTextTestPage1', $wikitextNamespace );
$page = WikiPage::factory( $title );
$this->textId1 = $this->addRevision( $page, "FetchTextTestPage1Text1", "FetchTextTestPage1Summary1" );
- $title = Title::newFromText( 'FetchTextTestPage2' );
+ $title = Title::newFromText( 'FetchTextTestPage2', $wikitextNamespace );
$page = WikiPage::factory( $title );
$this->textId2 = $this->addRevision( $page, "FetchTextTestPage2Text1", "FetchTextTestPage2Summary1" );
$this->textId3 = $this->addRevision( $page, "FetchTextTestPage2Text2", "FetchTextTestPage2Summary2" );
--- /dev/null
+<?php
+/**
+ * Sanity checks for making sure registered resources are sane.
+ *
+ * @file
+ * @author Niklas Laxström, 2012
+ * @author Antoine Musso, 2012
+ * @author Santhosh Thottingal, 2012
+ * @author Timo Tijhof, 2012
+ * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later
+ */
+class ResourcesTest extends MediaWikiTestCase {
+
+ /**
+ * @dataProvider provideResourceFiles
+ */
+ public function testFileExistence( $filename, $module, $resource ) {
+ $this->assertFileExists( $filename,
+ "File '$resource' referenced by '$module' must exist."
+ );
+ }
+
+ /**
+ * This ask the ResouceLoader for all registered files from modules
+ * created by ResourceLoaderFileModule (or one of its descendants).
+ *
+ *
+ * Since the raw data is stored in protected properties, we have to
+ * overrride this through ReflectionObject methods.
+ */
+ public static function provideResourceFiles() {
+ global $wgEnableJavaScriptTest;
+
+ // Test existance of test suite files as well
+ // (can't use setUp or setMwGlobals because providers are static)
+ $live_wgEnableJavaScriptTest = $wgEnableJavaScriptTest;
+ $wgEnableJavaScriptTest = true;
+
+ // Array with arguments for the test function
+ $cases = array();
+
+ // Initialize ResourceLoader
+ $rl = new ResourceLoader();
+
+ // See also ResourceLoaderFileModule::__construct
+ $filePathProps = array(
+ // Lists of file paths
+ 'lists' => array(
+ 'scripts',
+ 'debugScripts',
+ 'loaderScripts',
+ 'styles',
+ ),
+
+ // Collated lists of file paths
+ 'nested-lists' => array(
+ 'languageScripts',
+ 'skinScripts',
+ 'skinStyles',
+ ),
+ );
+
+ foreach ( $rl->getModuleNames() as $moduleName ) {
+ $module = $rl->getModule( $moduleName );
+ if ( ! $module instanceof ResourceLoaderFileModule ) {
+ continue;
+ }
+
+ $reflectedModule = new ReflectionObject( $module );
+
+ $files = array();
+
+ foreach ( $filePathProps['lists'] as $propName ) {
+ $property = $reflectedModule->getProperty( $propName );
+ $property->setAccessible( true );
+ $list = $property->getValue( $module );
+ foreach ( $list as $key => $value ) {
+ // 'scripts' are numeral arrays.
+ // 'styles' can be numeral or associative.
+ // In case of associative the key is the file path
+ // and the value is the 'media' attribute.
+ if ( is_int( $key ) ) {
+ $files[] = $value;
+ } else {
+ $files[] = $key;
+ }
+ }
+ }
+
+ foreach ( $filePathProps['nested-lists'] as $propName ) {
+ $property = $reflectedModule->getProperty( $propName );
+ $property->setAccessible( true );
+ $lists = $property->getValue( $module );
+ foreach ( $lists as $group => $list ) {
+ foreach ( $list as $key => $value ) {
+ // We need the same filter as for 'lists',
+ // due to 'skinStyles'.
+ if ( is_int( $key ) ) {
+ $files[] = $value;
+ } else {
+ $files[] = $key;
+ }
+ }
+ }
+ }
+
+ // Get method for resolving the paths to full paths
+ $method = $reflectedModule->getMethod( 'getLocalPath' );
+ $method->setAccessible( true );
+
+ // Populate cases
+ foreach ( $files as $file ) {
+ $cases[] = array(
+ $method->invoke( $module, $file ),
+ $module->getName(),
+ $file,
+ );
+ }
+
+ }
+
+ // Restore settings
+ $wgEnableJavaScriptTest = $live_wgEnableJavaScriptTest;
+
+ return $cases;
+ }
+
+}
}
}
- function setUp() {
+ protected function setUp() {
parent::setUp();
$this->initMessagesHref();
$this->skin = new SkinTemplate();
$this->skin->getContext()->setLanguage( Language::factory( 'en' ) );
}
- function tearDown() {
+ protected function tearDown() {
parent::tearDown();
$this->skin = null;
}
return true;
}
- function setUp() {
+ protected function setUp() {
global $wgParser, $wgParserConf, $IP, $messageMemc, $wgMemc,
$wgUser, $wgLang, $wgOut, $wgRequest, $wgStyleDirectory, $wgEnableParserCache,
$wgNamespaceAliases, $wgNamespaceProtection, $parserMemc;
FileBackendGroup::destroySingleton();
}
- public function tearDown() {
+ protected function tearDown() {
foreach ( $this->savedGlobals as $var => $val ) {
$GLOBALS[$var] = $val;
}
'tests/qunit/suites/resources/jquery/jquery.colorUtil.test.js',
'tests/qunit/suites/resources/jquery/jquery.delayedBind.test.js',
'tests/qunit/suites/resources/jquery/jquery.getAttrs.test.js',
+ 'tests/qunit/suites/resources/jquery/jquery.hidpi.test.js',
'tests/qunit/suites/resources/jquery/jquery.highlightText.test.js',
'tests/qunit/suites/resources/jquery/jquery.localize.test.js',
'tests/qunit/suites/resources/jquery/jquery.mwExtension.test.js',
'tests/qunit/suites/resources/jquery/jquery.tabIndex.test.js',
'tests/qunit/suites/resources/jquery/jquery.tablesorter.test.js',
'tests/qunit/suites/resources/jquery/jquery.textSelection.test.js',
+ 'tests/qunit/data/mediawiki.jqueryMsg.data.js',
'tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js',
'tests/qunit/suites/resources/mediawiki/mediawiki.jscompat.test.js',
'tests/qunit/suites/resources/mediawiki/mediawiki.test.js',
'jquery.colorUtil',
'jquery.delayedBind',
'jquery.getAttrs',
+ 'jquery.hidpi',
'jquery.highlightText',
'jquery.localize',
'jquery.mwExtension',
--- /dev/null
+<?php
+/**
+ * This PHP script defines the spec that the mediawiki.jqueryMsg module should conform to.
+ *
+ * It does this by looking up the results of various kinds of string parsing, with various
+ * languages, in the current installation of MediaWiki. It then outputs a static specification,
+ * mapping expected inputs to outputs, which can be used fed into a unit test framework.
+ * (QUnit, Jasmine, anything, it just outputs an object with key/value pairs).
+ *
+ * This is similar to Michael Dale (mdale@mediawiki.org)'s parser tests, except that it doesn't
+ * look up the API results while doing the test, so the test run is much faster (at the cost
+ * of being out of date in rare circumstances. But mostly the parsing that we are doing in
+ * Javascript doesn't change much).
+ */
+
+/*
+ * @example QUnit
+ * <code>
+ QUnit.test( 'Output matches PHP parser', mw.libs.phpParserData.tests.length, function ( assert ) {
+ mw.messages.set( mw.libs.phpParserData.messages );
+ $.each( mw.libs.phpParserData.tests, function ( i, test ) {
+ QUnit.stop();
+ getMwLanguage( test.lang, function ( langClass ) {
+ var parser = new mw.jqueryMsg.parser( { language: langClass } );
+ assert.equal(
+ parser.parse( test.key, test.args ).html(),
+ test.result,
+ test.name
+ );
+ QUnit.start();
+ } );
+ } );
+ });
+ * </code>
+ *
+ * @example Jasmine
+ * <code>
+ describe( 'match output to output from PHP parser', function () {
+ mw.messages.set( mw.libs.phpParserData.messages );
+ $.each( mw.libs.phpParserData.tests, function ( i, test ) {
+ it( 'should parse ' + test.name, function () {
+ var langClass;
+ runs( function () {
+ getMwLanguage( test.lang, function ( gotIt ) {
+ langClass = gotIt;
+ });
+ });
+ waitsFor( function () {
+ return langClass !== undefined;
+ }, 'Language class should be loaded', 1000 );
+ runs( function () {
+ console.log( test.lang, 'running tests' );
+ var parser = new mw.jqueryMsg.parser( { language: langClass } );
+ expect(
+ parser.parse( test.key, test.args ).html()
+ ).toEqual( test.result );
+ } );
+ } );
+ } );
+ } );
+ * </code>
+ */
+
+$maintenanceDir = dirname( dirname( dirname( __DIR__ ) ) ) . '/maintenance';
+
+require( "$maintenanceDir/Maintenance.php" );
+
+class GenerateJqueryMsgData extends Maintenance {
+
+ static $keyToTestArgs = array(
+ 'undelete_short' => array(
+ array( 0 ),
+ array( 1 ),
+ array( 2 ),
+ array( 5 ),
+ array( 21 ),
+ array( 101 )
+ ),
+ 'category-subcat-count' => array(
+ array( 0, 10 ),
+ array( 1, 1 ),
+ array( 1, 2 ),
+ array( 3, 30 )
+ )
+ );
+
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = 'Create a specification for message parsing ini JSON format';
+ // add any other options here
+ }
+
+ public function execute() {
+ list( $messages, $tests ) = $this->getMessagesAndTests();
+ $this->writeJavascriptFile( $messages, $tests, __DIR__ . '/mediawiki.jqueryMsg.data.js' );
+ }
+
+ private function getMessagesAndTests() {
+ $messages = array();
+ $tests = array();
+ foreach ( array( 'en', 'fr', 'ar', 'jp', 'zh' ) as $languageCode ) {
+ foreach ( self::$keyToTestArgs as $key => $testArgs ) {
+ foreach ($testArgs as $args) {
+ // Get the raw message, without any transformations.
+ $template = wfMessage( $key )->inLanguage( $languageCode )->plain();
+
+ // Get the magic-parsed version with args.
+ $result = wfMessage( $key, $args )->inLanguage( $languageCode )->text();
+
+ // Record the template, args, language, and expected result
+ // fake multiple languages by flattening them together.
+ $langKey = $languageCode . '_' . $key;
+ $messages[$langKey] = $template;
+ $tests[] = array(
+ 'name' => $languageCode . ' ' . $key . ' ' . join( ',', $args ),
+ 'key' => $langKey,
+ 'args' => $args,
+ 'result' => $result,
+ 'lang' => $languageCode
+ );
+ }
+ }
+ }
+ return array( $messages, $tests );
+ }
+
+ private function writeJavascriptFile( $messages, $tests, $dataSpecFile ) {
+ $phpParserData = array(
+ 'messages' => $messages,
+ 'tests' => $tests,
+ );
+
+ $output =
+ "// This file stores the output from the PHP parser for various messages, arguments,\n"
+ . "// languages, and parser modes. Intended for use by a unit test framework by looping\n"
+ . "// through the object and comparing its parser return value with the 'result' property.\n"
+ . '// Last generated with ' . basename( __FILE__ ) . ' at ' . gmdate( 'r' ) . "\n"
+ . "\n"
+ . 'mediaWiki.libs.phpParserData = ' . FormatJson::encode( $phpParserData, true ) . ";\n";
+
+ $fp = file_put_contents( $dataSpecFile, $output );
+ if ( $fp === false ) {
+ die( "Couldn't write to $dataSpecFile." );
+ }
+ }
+}
+
+$maintClass = "GenerateJqueryMsgData";
+require_once( "$maintenanceDir/doMaintenance.php" );
--- /dev/null
+// This file stores the output from the PHP parser for various messages, arguments,
+// languages, and parser modes. Intended for use by a unit test framework by looping
+// through the object and comparing its parser return value with the 'result' property.
+// Last generated with generateJqueryMsgData.php at Sun, 07 Oct 2012 07:30:16 +0000
+
+mediaWiki.libs.phpParserData = {
+ "messages": {
+ "en_undelete_short": "Undelete {{PLURAL:$1|one edit|$1 edits}}",
+ "en_category-subcat-count": "{{PLURAL:$2|This category has only the following subcategory.|This category has the following {{PLURAL:$1|subcategory|$1 subcategories}}, out of $2 total.}}",
+ "fr_undelete_short": "Restaurer $1 modification{{PLURAL:$1||s}}",
+ "fr_category-subcat-count": "Cette cat\u00e9gorie comprend {{PLURAL:$2|la sous-cat\u00e9gorie|$2 sous-cat\u00e9gories, dont {{PLURAL:$1|celle|les $1}}}} ci-dessous.",
+ "ar_undelete_short": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 {{PLURAL:$1|\u062a\u0639\u062f\u064a\u0644 \u0648\u0627\u062d\u062f|\u062a\u0639\u062f\u064a\u0644\u064a\u0646|$1 \u062a\u0639\u062f\u064a\u0644\u0627\u062a|$1 \u062a\u0639\u062f\u064a\u0644|$1 \u062a\u0639\u062f\u064a\u0644\u0627}}",
+ "ar_category-subcat-count": "{{PLURAL:$2|\u0644\u0627 \u062a\u0635\u0627\u0646\u064a\u0641 \u0641\u0631\u0639\u064a\u0629 \u0641\u064a \u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641|\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0641\u064a\u0647 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0627\u0644\u0641\u0631\u0639\u064a \u0627\u0644\u062a\u0627\u0644\u064a \u0641\u0642\u0637.|\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0641\u064a\u0647 {{PLURAL:$1||\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0627\u0644\u0641\u0631\u0639\u064a|\u0647\u0630\u064a\u0646 \u0627\u0644\u062a\u0635\u0646\u064a\u0641\u064a\u0646 \u0627\u0644\u0641\u0631\u0639\u064a\u064a\u0646|\u0647\u0630\u0647 \u0627\u0644$1 \u062a\u0635\u0627\u0646\u064a\u0641 \u0627\u0644\u0641\u0631\u0639\u064a\u0629|\u0647\u0630\u0647 \u0627\u0644$1 \u062a\u0635\u0646\u064a\u0641\u0627 \u0641\u0631\u0639\u064a\u0627|\u0647\u0630\u0647 \u0627\u0644$1 \u062a\u0635\u0646\u064a\u0641 \u0641\u0631\u0639\u064a}}\u060c \u0645\u0646 \u0625\u062c\u0645\u0627\u0644\u064a $2.}}",
+ "jp_undelete_short": "Undelete {{PLURAL:$1|one edit|$1 edits}}",
+ "jp_category-subcat-count": "{{PLURAL:$2|This category has only the following subcategory.|This category has the following {{PLURAL:$1|subcategory|$1 subcategories}}, out of $2 total.}}",
+ "zh_undelete_short": "\u6062\u590d$1\u4e2a\u88ab\u5220\u9664\u7684\u7f16\u8f91",
+ "zh_category-subcat-count": "{{PLURAL:$2|\u672c\u5206\u7c7b\u53ea\u6709\u4e0b\u5217\u4e00\u4e2a\u5b50\u5206\u7c7b\u3002|\u672c\u5206\u7c7b\u5305\u542b\u4e0b\u5217$1\u4e2a\u5b50\u5206\u7c7b\uff0c\u5171$2\u4e2a\u5b50\u5206\u7c7b\u3002}}"
+ },
+ "tests": [
+ {
+ "name": "en undelete_short 0",
+ "key": "en_undelete_short",
+ "args": [
+ 0
+ ],
+ "result": "Undelete 0 edits",
+ "lang": "en"
+ },
+ {
+ "name": "en undelete_short 1",
+ "key": "en_undelete_short",
+ "args": [
+ 1
+ ],
+ "result": "Undelete one edit",
+ "lang": "en"
+ },
+ {
+ "name": "en undelete_short 2",
+ "key": "en_undelete_short",
+ "args": [
+ 2
+ ],
+ "result": "Undelete 2 edits",
+ "lang": "en"
+ },
+ {
+ "name": "en undelete_short 5",
+ "key": "en_undelete_short",
+ "args": [
+ 5
+ ],
+ "result": "Undelete 5 edits",
+ "lang": "en"
+ },
+ {
+ "name": "en undelete_short 21",
+ "key": "en_undelete_short",
+ "args": [
+ 21
+ ],
+ "result": "Undelete 21 edits",
+ "lang": "en"
+ },
+ {
+ "name": "en undelete_short 101",
+ "key": "en_undelete_short",
+ "args": [
+ 101
+ ],
+ "result": "Undelete 101 edits",
+ "lang": "en"
+ },
+ {
+ "name": "en category-subcat-count 0,10",
+ "key": "en_category-subcat-count",
+ "args": [
+ 0,
+ 10
+ ],
+ "result": "This category has the following 0 subcategories, out of 10 total.",
+ "lang": "en"
+ },
+ {
+ "name": "en category-subcat-count 1,1",
+ "key": "en_category-subcat-count",
+ "args": [
+ 1,
+ 1
+ ],
+ "result": "This category has only the following subcategory.",
+ "lang": "en"
+ },
+ {
+ "name": "en category-subcat-count 1,2",
+ "key": "en_category-subcat-count",
+ "args": [
+ 1,
+ 2
+ ],
+ "result": "This category has the following subcategory, out of 2 total.",
+ "lang": "en"
+ },
+ {
+ "name": "en category-subcat-count 3,30",
+ "key": "en_category-subcat-count",
+ "args": [
+ 3,
+ 30
+ ],
+ "result": "This category has the following 3 subcategories, out of 30 total.",
+ "lang": "en"
+ },
+ {
+ "name": "fr undelete_short 0",
+ "key": "fr_undelete_short",
+ "args": [
+ 0
+ ],
+ "result": "Restaurer 0 modification",
+ "lang": "fr"
+ },
+ {
+ "name": "fr undelete_short 1",
+ "key": "fr_undelete_short",
+ "args": [
+ 1
+ ],
+ "result": "Restaurer 1 modification",
+ "lang": "fr"
+ },
+ {
+ "name": "fr undelete_short 2",
+ "key": "fr_undelete_short",
+ "args": [
+ 2
+ ],
+ "result": "Restaurer 2 modifications",
+ "lang": "fr"
+ },
+ {
+ "name": "fr undelete_short 5",
+ "key": "fr_undelete_short",
+ "args": [
+ 5
+ ],
+ "result": "Restaurer 5 modifications",
+ "lang": "fr"
+ },
+ {
+ "name": "fr undelete_short 21",
+ "key": "fr_undelete_short",
+ "args": [
+ 21
+ ],
+ "result": "Restaurer 21 modifications",
+ "lang": "fr"
+ },
+ {
+ "name": "fr undelete_short 101",
+ "key": "fr_undelete_short",
+ "args": [
+ 101
+ ],
+ "result": "Restaurer 101 modifications",
+ "lang": "fr"
+ },
+ {
+ "name": "fr category-subcat-count 0,10",
+ "key": "fr_category-subcat-count",
+ "args": [
+ 0,
+ 10
+ ],
+ "result": "Cette cat\u00e9gorie comprend 10 sous-cat\u00e9gories, dont celle ci-dessous.",
+ "lang": "fr"
+ },
+ {
+ "name": "fr category-subcat-count 1,1",
+ "key": "fr_category-subcat-count",
+ "args": [
+ 1,
+ 1
+ ],
+ "result": "Cette cat\u00e9gorie comprend la sous-cat\u00e9gorie ci-dessous.",
+ "lang": "fr"
+ },
+ {
+ "name": "fr category-subcat-count 1,2",
+ "key": "fr_category-subcat-count",
+ "args": [
+ 1,
+ 2
+ ],
+ "result": "Cette cat\u00e9gorie comprend 2 sous-cat\u00e9gories, dont celle ci-dessous.",
+ "lang": "fr"
+ },
+ {
+ "name": "fr category-subcat-count 3,30",
+ "key": "fr_category-subcat-count",
+ "args": [
+ 3,
+ 30
+ ],
+ "result": "Cette cat\u00e9gorie comprend 30 sous-cat\u00e9gories, dont les 3 ci-dessous.",
+ "lang": "fr"
+ },
+ {
+ "name": "ar undelete_short 0",
+ "key": "ar_undelete_short",
+ "args": [
+ 0
+ ],
+ "result": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 \u062a\u0639\u062f\u064a\u0644 \u0648\u0627\u062d\u062f",
+ "lang": "ar"
+ },
+ {
+ "name": "ar undelete_short 1",
+ "key": "ar_undelete_short",
+ "args": [
+ 1
+ ],
+ "result": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 \u062a\u0639\u062f\u064a\u0644\u064a\u0646",
+ "lang": "ar"
+ },
+ {
+ "name": "ar undelete_short 2",
+ "key": "ar_undelete_short",
+ "args": [
+ 2
+ ],
+ "result": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 2 \u062a\u0639\u062f\u064a\u0644\u0627\u062a",
+ "lang": "ar"
+ },
+ {
+ "name": "ar undelete_short 5",
+ "key": "ar_undelete_short",
+ "args": [
+ 5
+ ],
+ "result": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 5 \u062a\u0639\u062f\u064a\u0644",
+ "lang": "ar"
+ },
+ {
+ "name": "ar undelete_short 21",
+ "key": "ar_undelete_short",
+ "args": [
+ 21
+ ],
+ "result": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 21 \u062a\u0639\u062f\u064a\u0644\u0627",
+ "lang": "ar"
+ },
+ {
+ "name": "ar undelete_short 101",
+ "key": "ar_undelete_short",
+ "args": [
+ 101
+ ],
+ "result": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 101 \u062a\u0639\u062f\u064a\u0644\u0627",
+ "lang": "ar"
+ },
+ {
+ "name": "ar category-subcat-count 0,10",
+ "key": "ar_category-subcat-count",
+ "args": [
+ 0,
+ 10
+ ],
+ "result": "\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0641\u064a\u0647 \u060c \u0645\u0646 \u0625\u062c\u0645\u0627\u0644\u064a 10.",
+ "lang": "ar"
+ },
+ {
+ "name": "ar category-subcat-count 1,1",
+ "key": "ar_category-subcat-count",
+ "args": [
+ 1,
+ 1
+ ],
+ "result": "\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0641\u064a\u0647 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0627\u0644\u0641\u0631\u0639\u064a \u0627\u0644\u062a\u0627\u0644\u064a \u0641\u0642\u0637.",
+ "lang": "ar"
+ },
+ {
+ "name": "ar category-subcat-count 1,2",
+ "key": "ar_category-subcat-count",
+ "args": [
+ 1,
+ 2
+ ],
+ "result": "\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0641\u064a\u0647 \u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0627\u0644\u0641\u0631\u0639\u064a\u060c \u0645\u0646 \u0625\u062c\u0645\u0627\u0644\u064a 2.",
+ "lang": "ar"
+ },
+ {
+ "name": "ar category-subcat-count 3,30",
+ "key": "ar_category-subcat-count",
+ "args": [
+ 3,
+ 30
+ ],
+ "result": "\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0641\u064a\u0647 \u0647\u0630\u0647 \u0627\u06443 \u062a\u0635\u0627\u0646\u064a\u0641 \u0627\u0644\u0641\u0631\u0639\u064a\u0629\u060c \u0645\u0646 \u0625\u062c\u0645\u0627\u0644\u064a 30.",
+ "lang": "ar"
+ },
+ {
+ "name": "jp undelete_short 0",
+ "key": "jp_undelete_short",
+ "args": [
+ 0
+ ],
+ "result": "Undelete 0 edits",
+ "lang": "jp"
+ },
+ {
+ "name": "jp undelete_short 1",
+ "key": "jp_undelete_short",
+ "args": [
+ 1
+ ],
+ "result": "Undelete one edit",
+ "lang": "jp"
+ },
+ {
+ "name": "jp undelete_short 2",
+ "key": "jp_undelete_short",
+ "args": [
+ 2
+ ],
+ "result": "Undelete 2 edits",
+ "lang": "jp"
+ },
+ {
+ "name": "jp undelete_short 5",
+ "key": "jp_undelete_short",
+ "args": [
+ 5
+ ],
+ "result": "Undelete 5 edits",
+ "lang": "jp"
+ },
+ {
+ "name": "jp undelete_short 21",
+ "key": "jp_undelete_short",
+ "args": [
+ 21
+ ],
+ "result": "Undelete 21 edits",
+ "lang": "jp"
+ },
+ {
+ "name": "jp undelete_short 101",
+ "key": "jp_undelete_short",
+ "args": [
+ 101
+ ],
+ "result": "Undelete 101 edits",
+ "lang": "jp"
+ },
+ {
+ "name": "jp category-subcat-count 0,10",
+ "key": "jp_category-subcat-count",
+ "args": [
+ 0,
+ 10
+ ],
+ "result": "This category has the following 0 subcategories, out of 10 total.",
+ "lang": "jp"
+ },
+ {
+ "name": "jp category-subcat-count 1,1",
+ "key": "jp_category-subcat-count",
+ "args": [
+ 1,
+ 1
+ ],
+ "result": "This category has only the following subcategory.",
+ "lang": "jp"
+ },
+ {
+ "name": "jp category-subcat-count 1,2",
+ "key": "jp_category-subcat-count",
+ "args": [
+ 1,
+ 2
+ ],
+ "result": "This category has the following subcategory, out of 2 total.",
+ "lang": "jp"
+ },
+ {
+ "name": "jp category-subcat-count 3,30",
+ "key": "jp_category-subcat-count",
+ "args": [
+ 3,
+ 30
+ ],
+ "result": "This category has the following 3 subcategories, out of 30 total.",
+ "lang": "jp"
+ },
+ {
+ "name": "zh undelete_short 0",
+ "key": "zh_undelete_short",
+ "args": [
+ 0
+ ],
+ "result": "\u6062\u590d0\u4e2a\u88ab\u5220\u9664\u7684\u7f16\u8f91",
+ "lang": "zh"
+ },
+ {
+ "name": "zh undelete_short 1",
+ "key": "zh_undelete_short",
+ "args": [
+ 1
+ ],
+ "result": "\u6062\u590d1\u4e2a\u88ab\u5220\u9664\u7684\u7f16\u8f91",
+ "lang": "zh"
+ },
+ {
+ "name": "zh undelete_short 2",
+ "key": "zh_undelete_short",
+ "args": [
+ 2
+ ],
+ "result": "\u6062\u590d2\u4e2a\u88ab\u5220\u9664\u7684\u7f16\u8f91",
+ "lang": "zh"
+ },
+ {
+ "name": "zh undelete_short 5",
+ "key": "zh_undelete_short",
+ "args": [
+ 5
+ ],
+ "result": "\u6062\u590d5\u4e2a\u88ab\u5220\u9664\u7684\u7f16\u8f91",
+ "lang": "zh"
+ },
+ {
+ "name": "zh undelete_short 21",
+ "key": "zh_undelete_short",
+ "args": [
+ 21
+ ],
+ "result": "\u6062\u590d21\u4e2a\u88ab\u5220\u9664\u7684\u7f16\u8f91",
+ "lang": "zh"
+ },
+ {
+ "name": "zh undelete_short 101",
+ "key": "zh_undelete_short",
+ "args": [
+ 101
+ ],
+ "result": "\u6062\u590d101\u4e2a\u88ab\u5220\u9664\u7684\u7f16\u8f91",
+ "lang": "zh"
+ },
+ {
+ "name": "zh category-subcat-count 0,10",
+ "key": "zh_category-subcat-count",
+ "args": [
+ 0,
+ 10
+ ],
+ "result": "\u672c\u5206\u7c7b\u5305\u542b\u4e0b\u52170\u4e2a\u5b50\u5206\u7c7b\uff0c\u517110\u4e2a\u5b50\u5206\u7c7b\u3002",
+ "lang": "zh"
+ },
+ {
+ "name": "zh category-subcat-count 1,1",
+ "key": "zh_category-subcat-count",
+ "args": [
+ 1,
+ 1
+ ],
+ "result": "\u672c\u5206\u7c7b\u53ea\u6709\u4e0b\u5217\u4e00\u4e2a\u5b50\u5206\u7c7b\u3002",
+ "lang": "zh"
+ },
+ {
+ "name": "zh category-subcat-count 1,2",
+ "key": "zh_category-subcat-count",
+ "args": [
+ 1,
+ 2
+ ],
+ "result": "\u672c\u5206\u7c7b\u5305\u542b\u4e0b\u52171\u4e2a\u5b50\u5206\u7c7b\uff0c\u51712\u4e2a\u5b50\u5206\u7c7b\u3002",
+ "lang": "zh"
+ },
+ {
+ "name": "zh category-subcat-count 3,30",
+ "key": "zh_category-subcat-count",
+ "args": [
+ 3,
+ 30
+ ],
+ "result": "\u672c\u5206\u7c7b\u5305\u542b\u4e0b\u52173\u4e2a\u5b50\u5206\u7c7b\uff0c\u517130\u4e2a\u5b50\u5206\u7c7b\u3002",
+ "lang": "zh"
+ }
+ ]
+};
--- /dev/null
+QUnit.module( 'jquery.hidpi', QUnit.newMwEnvironment() );
+
+QUnit.test( 'devicePixelRatio', function ( assert ) {
+ var devicePixelRatio = $.devicePixelRatio();
+ assert.equal( typeof devicePixelRatio, 'number', '$.devicePixelRatio() returns a number' );
+});
+
+QUnit.test( 'matchSrcSet', function ( assert ) {
+ var srcset = 'onefive.png 1.5x, two.png 2x';
+
+ // Nice exact matches
+ assert.equal( $.matchSrcSet( 1, srcset ), null, '1.0 gives no match' );
+ assert.equal( $.matchSrcSet( 1.5, srcset ), 'onefive.png', '1.5 gives match' );
+ assert.equal( $.matchSrcSet( 2, srcset ), 'two.png', '2 gives match' );
+
+ // Non-exact matches; should return the next-biggest specified
+ assert.equal( $.matchSrcSet( 1.25, srcset ), null, '1.25 gives no match' );
+ assert.equal( $.matchSrcSet( 1.75, srcset ), 'onefive.png', '1.75 gives match to 1.5' );
+ assert.equal( $.matchSrcSet( 2.25, srcset ), 'two.png', '2.25 gives match to 2' );
+});
var ascendingName = [earth, jupiter, mars, mercury, saturn, venus];
var ascendingRadius = [mercury, mars, venus, earth, saturn, jupiter];
+tableTest(
+ 'Basic planet table: sorting initially - ascending by name',
+ header,
+ planets,
+ ascendingName,
+ function ( $table ) {
+ $table.tablesorter( { sortList: [ { 0: 'asc' } ] } );
+ }
+);
+tableTest(
+ 'Basic planet table: sorting initially - descending by radius',
+ header,
+ planets,
+ reversed(ascendingRadius),
+ function ( $table ) {
+ $table.tablesorter( { sortList: [ { 1: 'desc' } ] } );
+ }
+);
tableTest(
'Basic planet table: ascending by name',
header,
}
);
+// Sample data set to test multiple column sorting
+var header = [ 'column1' , 'column2'],
+ a1 = [ 'A', '1' ],
+ a2 = [ 'A', '2' ],
+ a3 = [ 'A', '3' ],
+ b1 = [ 'B', '1' ],
+ b2 = [ 'B', '2' ],
+ b3 = [ 'B', '3' ];
+var initial = [a2, b3, a1, a3, b2, b1];
+var asc = [a1, a2, a3, b1, b2, b3];
+var descasc = [b1, b2, b3, a1, a2, a3];
+
+tableTest(
+ 'Sorting multiple columns by passing sort list',
+ header,
+ initial,
+ asc,
+ function ( $table ) {
+ $table.tablesorter(
+ { sortList: [ { 0: 'asc' }, { 1: 'asc' } ] }
+ );
+ }
+);
+tableTest(
+ 'Sorting multiple columns by programmatically triggering sort()',
+ header,
+ initial,
+ descasc,
+ function ( $table ) {
+ $table.tablesorter();
+ $table.data( 'tablesorter' ).sort(
+ [ { 0: 'desc' }, { 1: 'asc' } ]
+ );
+ }
+);
+tableTest(
+ 'Reset to initial sorting by triggering sort() without any parameters',
+ header,
+ initial,
+ asc,
+ function ( $table ) {
+ $table.tablesorter(
+ { sortList: [ { 0: 'asc' }, { 1: 'asc' } ] }
+ );
+ $table.data( 'tablesorter' ).sort(
+ [ { 0: 'desc' }, { 1: 'asc' } ]
+ );
+ $table.data( 'tablesorter' ).sort();
+ }
+);
+QUnit.test( 'Reset sorting making table appear unsorted', 3, function ( assert ) {
+ var $table = tableCreate( header, initial );
+ $table.tablesorter(
+ { sortList: [ { 0: 'desc' }, { 1: 'asc' } ] }
+ );
+ $table.data( 'tablesorter' ).sort( [] );
+
+ assert.equal(
+ $table.find( 'th.headerSortUp' ).length + $table.find( 'th.headerSortDown' ).length,
+ 0,
+ 'No sort specific sort classes addign to header cells'
+ );
+
+ assert.equal(
+ $table.find( 'th' ).first().attr( 'title' ),
+ mw.msg( 'sort-ascending' ),
+ 'First header cell has default title'
+ );
+
+ assert.equal(
+ $table.find( 'th' ).first().attr( 'title' ),
+ $table.find( 'th' ).last().attr( 'title' ),
+ 'Both header cells\' titles match'
+ );
+} );
// Regression tests!
tableTest(
planets,
planetsRowspan,
function ( $table ) {
- // Modify the table to have a multiuple-row-spanning cell:
+ // Modify the table to have a multiple-row-spanning cell:
// - Remove 2nd cell of 4th row, and, 2nd cell or 5th row.
$table.find( 'tr:eq(3) td:eq(1), tr:eq(4) td:eq(1)' ).remove();
// - Set rowspan for 2nd cell of 3rd row to 3.
$table.find( '.headerSort:eq(0)' ).click();
}
);
+tableTest(
+ 'Basic planet table: same value for multiple rows via rowspan (sorting initially)',
+ header,
+ planets,
+ planetsRowspan,
+ function ( $table ) {
+ // Modify the table to have a multiple-row-spanning cell:
+ // - Remove 2nd cell of 4th row, and, 2nd cell or 5th row.
+ $table.find( 'tr:eq(3) td:eq(1), tr:eq(4) td:eq(1)' ).remove();
+ // - Set rowspan for 2nd cell of 3rd row to 3.
+ // This covers the removed cell in the 4th and 5th row.
+ $table.find( 'tr:eq(2) td:eq(1)' ).prop( 'rowspan', '3' );
+
+ $table.tablesorter( { sortList: [ { 0: 'asc' } ] } );
+ }
+);
tableTest(
'Basic planet table: Same value for multiple rows via rowspan II',
header,
planets,
planetsRowspanII,
function ( $table ) {
- // Modify the table to have a multiuple-row-spanning cell:
+ // Modify the table to have a multiple-row-spanning cell:
// - Remove 1st cell of 4th row, and, 1st cell or 5th row.
$table.find( 'tr:eq(3) td:eq(0), tr:eq(4) td:eq(0)' ).remove();
// - Set rowspan for 1st cell of 3rd row to 3.
}
);
+QUnit.test( 'Test detection routine', function ( assert ) {
+ var $table;
+ $table = $(
+ '<table class="sortable">' +
+ '<caption>CAPTION</caption>' +
+ '<tr><th>THEAD</th></tr>' +
+ '<tr><td>1</td></tr>' +
+ '<tr class="sortbottom"><td>text</td></tr>' +
+ '</table>'
+ );
+ $table.tablesorter();
+
+ assert.equal(
+ $table.data( 'tablesorter' ).config.parsers[0].id,
+ 'number',
+ 'Correctly detected column content skipping sortbottom'
+ );
+} );
/** FIXME: the diff output is not very readeable. */
QUnit.test( 'bug 32047 - caption must be before thead', function ( assert ) {
-QUnit.module( 'mediawiki.jqueryMsg' );
+( function ( mw, $ ) {
-QUnit.test( 'mw.jqueryMsg Plural', 3, function ( assert ) {
+QUnit.module( 'mediawiki.jqueryMsg', QUnit.newMwEnvironment( {
+ setup: function () {
+ this.orgMwLangauge = mw.language;
+ mw.language = $.extend( true, {}, this.orgMwLangauge );
+ },
+ teardown: function () {
+ mw.language = this.orgMwLangauge;
+ }
+}) );
+
+var mwLanguageCache = {};
+function getMwLanguage( langCode, cb ) {
+ if ( mwLanguageCache[langCode] !== undefined ) {
+ mwLanguageCache[langCode].add( cb );
+ return;
+ }
+ mwLanguageCache[langCode] = $.Callbacks( 'once memory' );
+ mwLanguageCache[langCode].add( cb );
+ $.ajax({
+ url: mw.util.wikiScript( 'load' ),
+ data: {
+ skin: mw.config.get( 'skin' ),
+ lang: langCode,
+ debug: mw.config.get( 'debug' ),
+ modules: [
+ 'mediawiki.language.data',
+ 'mediawiki.language'
+ ].join( '|' ),
+ only: 'scripts'
+ },
+ dataType: 'script'
+ }).done( function () {
+ mwLanguageCache[langCode].fire( mw.language );
+ }).fail( function () {
+ mwLanguageCache[langCode].fire( false );
+ });
+}
+
+QUnit.test( 'Plural', 3, function ( assert ) {
var parser = mw.jqueryMsg.getMessageFunction();
mw.messages.set( 'plural-msg', 'Found $1 {{PLURAL:$1|item|items}}' );
} );
-QUnit.test( 'mw.jqueryMsg Gender', 11, function ( assert ) {
+QUnit.test( 'Gender', 11, function ( assert ) {
// TODO: These tests should be for mw.msg once mw.msg integrated with mw.jqueryMsg
// TODO: English may not be the best language for these tests. Use a language like Arabic or Russian
var user = mw.user,
);
} );
-
-QUnit.test( 'mw.jqueryMsg Grammar', 2, function ( assert ) {
+QUnit.test( 'Grammar', 2, function ( assert ) {
var parser = mw.jqueryMsg.getMessageFunction();
// Assume the grammar form grammar_case_foo is not valid in any language
mw.messages.set( 'grammar-msg-wrong-syntax', 'Przeszukaj {{GRAMMAR:grammar_case_xyz}}' );
assert.equal( parser( 'grammar-msg-wrong-syntax' ), 'Przeszukaj ' , 'Grammar Test with wrong grammar template syntax' );
} );
+
+QUnit.test( 'Output matches PHP parser', mw.libs.phpParserData.tests.length, function ( assert ) {
+ mw.messages.set( mw.libs.phpParserData.messages );
+ $.each( mw.libs.phpParserData.tests, function ( i, test ) {
+ QUnit.stop();
+ getMwLanguage( test.lang, function ( langClass ) {
+ QUnit.start();
+ if ( !langClass ) {
+ assert.ok( false, 'Language "' + test.lang + '" failed to load' );
+ return;
+ }
+ mw.config.set( 'wgUserLanguage', test.lang ) ;
+ var parser = new mw.jqueryMsg.parser( { language: langClass } );
+ assert.equal(
+ parser.parse( test.key, test.args ).html(),
+ test.result,
+ test.name
+ );
+ } );
+ } );
+});
+
+}( mediaWiki, jQuery ) );
// If it is passing or if we timed out, run the real test and stop the loop
if ( isCssImportApplied() || styleTestSince > styleTestTimeout ) {
assert.equal( $element.css( prop ), val,
- 'style from url is applied (after ' + styleTestSince + 'ms)'
+ 'style "' + prop + ': ' + val + '" from url is applied (after ' + styleTestSince + 'ms)'
);
if ( fn ) {
]);
} );
-QUnit.asyncTest( 'mw.loader.implement( styles={ "url": { <media>: [url, ..] } } )', 4, function ( assert ) {
- var $element = $( '<div class="mw-test-implement-b"></div>' ).appendTo( '#qunit-fixture' ),
- $element2 = $( '<div class="mw-test-implement-b2"></div>' ).appendTo( '#qunit-fixture' );
+QUnit.asyncTest( 'mw.loader.implement( styles={ "url": { <media>: [url, ..] } } )', 7, function ( assert ) {
+ var $element1 = $( '<div class="mw-test-implement-b1"></div>' ).appendTo( '#qunit-fixture' ),
+ $element2 = $( '<div class="mw-test-implement-b2"></div>' ).appendTo( '#qunit-fixture' ),
+ $element3 = $( '<div class="mw-test-implement-b3"></div>' ).appendTo( '#qunit-fixture' );
assert.notEqual(
- $element.css( 'float' ),
- 'right',
+ $element1.css( 'text-align' ),
+ 'center',
'style is clear'
);
assert.notEqual(
- $element2.css( 'text-align' ),
- 'center',
+ $element2.css( 'float' ),
+ 'left',
+ 'style is clear'
+ );
+ assert.notEqual(
+ $element3.css( 'text-align' ),
+ 'right',
'style is clear'
);
mw.loader.implement(
'test.implement.b',
function () {
- assertStyleAsync( assert, $element, 'float', 'right', function () {
+ assertStyleAsync( assert, $element2, 'float', 'left', function () {
+ assert.notEqual( $element1.css( 'text-align' ), 'center', 'print style is not applied' );
- assert.notEqual( $element2.css( 'text-align' ), 'center', 'print style is not applied' );
+ QUnit.start();
+ } );
+ assertStyleAsync( assert, $element3, 'float', 'right', function () {
+ assert.notEqual( $element1.css( 'text-align' ), 'center', 'print style is not applied' );
QUnit.start();
} );
},
{
'url': {
- 'screen': [urlStyleTest( '.mw-test-implement-b', 'float', 'right' )],
- 'print': [urlStyleTest( '.mw-test-implement-b2', 'text-align', 'center' )]
+ 'print': [urlStyleTest( '.mw-test-implement-b1', 'text-align', 'center' )],
+ 'screen': [
+ // bug 40834: Make sure it actually works with more than 1 stylesheet reference
+ urlStyleTest( '.mw-test-implement-b2', 'float', 'left' ),
+ urlStyleTest( '.mw-test-implement-b3', 'float', 'right' )
+ ]
}
},
{}
$img = wfLocalFile( $fileName );
}
+ // Check the source file title
+ if ( !$img ) {
+ wfThumbError( 404, wfMessage( 'badtitletext' )->text() );
+ wfProfileOut( __METHOD__ );
+ return;
+ }
+
// Check permissions if there are read restrictions
$varyHeader = array();
if ( !in_array( 'read', User::getGroupPermissions( array( '*' ) ), true ) ) {
}
// Check the source file storage path
- if ( !$img ) {
- wfThumbError( 404, wfMessage( 'badtitletext' )->text() );
- wfProfileOut( __METHOD__ );
- return;
- }
if ( !$img->exists() ) {
wfThumbError( 404, 'The source file for the specified thumbnail does not exist.' );
wfProfileOut( __METHOD__ );
* @ingroup Media
*/
-require './thumb.php';
+require './thumb.php';