use or update a custom session provider if needed.
* Deprecated APIEditBeforeSave hook in favor of EditFilterMergedContent.
* The 'UploadVerification' hook is deprecated. Use 'UploadVerifyFile' instead.
+* SiteConfiguration::isLocalVHost() was removed (deprecated since 1.25).
== Compatibility ==
This document is intended to provide useful advice for parties seeking to
-redistribute MediaWiki to end users. It's targeted particularly at maintainers
+redistribute MediaWiki to end users. It's targeted particularly at maintainers
for Linux distributions, since it's been observed that distribution packages of
-MediaWiki often break. We've consistently had to recommend that users seeking
+MediaWiki often break. We've consistently had to recommend that users seeking
support use official tarballs instead of their distribution's packages, and
-this often solves whatever problem the user is having. It would be nice if
+this often solves whatever problem the user is having. It would be nice if
this could change.
== Background: why web applications are different ==
MediaWiki is intended to be usable on any web host that provides support for
-PHP and a database. Many users of low-end shared hosting have very limited
+PHP and a database. Many users of low-end shared hosting have very limited
access to their machine: often only FTP access to some subdirectory of the web
-root. Support for these users entails several restrictions, such as:
+root. Support for these users entails several restrictions, such as:
- 1) We cannot require installation of any files outside the web root. Few of
+ 1) We cannot require installation of any files outside the web root. Few of
our users have access to directories like /usr or /etc.
2) We cannot require the ability to run any utility on the command line.
Many shared hosts have exec() and similar PHP functions disabled.
- 3) We cannot assume that the software has write access anywhere useful. The
+ 3) We cannot assume that the software has write access anywhere useful. The
user account that MediaWiki (including its installer) runs under is often
different from the account the user used to upload the files, and we might be
restricted by PHP settings such as safe mode or open_basedir.
Since anything that works on cheap shared hosting will work if you have shell
or root access too, MediaWiki's design is based around catering to the lowest
-common denominator. Although we support higher-end setups as well (like
+common denominator. Although we support higher-end setups as well (like
Wikipedia!), the way many things work by default is tailored toward shared
-hosting. These defaults are unconventional from the point of view of normal
+hosting. These defaults are unconventional from the point of view of normal
(non-web) applications -- they might conflict with distributors' policies, and
they certainly aren't ideal for someone who's installing MediaWiki as root.
== Directory structure ==
Because of constraint (1) above, MediaWiki does not conform to normal
-Unix filesystem layout. Hopefully we'll offer direct support for standard
+Unix filesystem layout. Hopefully we'll offer direct support for standard
layouts in the future, but for now *any change to the location of files is
-unsupported*. Moving things and leaving symlinks will *probably* not break
+unsupported*. Moving things and leaving symlinks will *probably* not break
anything, but it is *strongly* advised not to try any more intrusive changes to
-get MediaWiki to conform more closely to your filesystem hierarchy. Any such
+get MediaWiki to conform more closely to your filesystem hierarchy. Any such
attempt will almost certainly result in unnecessary bugs.
The standard recommended location to install MediaWiki, relative to the web
-root, is /w (so, e.g., /var/www/w). Rewrite rules can then be used to enable
-"pretty URLs" like /wiki/Article instead of /w/index.php?title=Article. (This
+root, is /w (so, e.g., /var/www/w). Rewrite rules can then be used to enable
+"pretty URLs" like /wiki/Article instead of /w/index.php?title=Article. (This
is the convention Wikipedia uses.) In theory, it should be possible to enable
the appropriate rewrite rules by default, if you can reconfigure the web
-server, but you'd need to alter LocalSettings.php too. See
+server, but you'd need to alter LocalSettings.php too. See
<https://www.mediawiki.org/wiki/Manual:Short_URL> for details on short URLs.
If you really must mess around with the directory structure, note that the
correctly:
* api.php, img_auth.php, index.php, load.php, opensearch_desc.php, thumb.php,
- profileinfo.php, redirect.php, trackback.php. These are the entry points for
- normal usage. This list may be incomplete and is subject to change.
+ profileinfo.php. These are the entry points for normal usage. This list may be
+ incomplete and is subject to change.
* mw-config/index.php: Used for web-based installation (sets up the database,
prompts for the name of the wiki, etc.).
- * images/: Used for uploaded files. This could be somewhere else if
+ * images/: Used for uploaded files. This could be somewhere else if
$wgUploadDirectory and $wgUploadPath are changed appropriately.
* skins/*/: Subdirectories of skins/ contain CSS and JavaScript files that
- must be accessible to web browsers. The PHP files and Skin.sample in skins/
- don't need to be accessible. This could be somewhere else if
+ must be accessible to web browsers. The PHP files and Skin.sample in skins/
+ don't need to be accessible. This could be somewhere else if
$wgStyleDirectory and $wgStylePath are changed appropriately.
* extensions/: Many extensions include CSS and JavaScript files in their
- extensions directory, and will break if they aren't web-accessible. Some
+ extensions directory, and will break if they aren't web-accessible. Some
extensions might theoretically provide additional entry points as well, at
least in principle.
But all files should keep their position relative to the web-visible
-installation directory no matter what. If you must move includes/ somewhere in
-/usr/share, provide a symlink from /var/www/w. If you don't, you *will* break
-something. You have been warned.
+installation directory no matter what. If you must move includes/ somewhere in
+/usr/share, provide a symlink from /var/www/w. If you don't, you *will* break
+something. You have been warned.
== Configuration ==
-MediaWiki is configured using LocalSettings.php. This is a PHP file that's
+MediaWiki is configured using LocalSettings.php. This is a PHP file that's
generated when the user visits mw-config/index.php to install the software, and
-which the user can edit by hand thereafter. It's just a plain old PHP file,
-and can contain any PHP statements. It usually sets global variables that are
+which the user can edit by hand thereafter. It's just a plain old PHP file,
+and can contain any PHP statements. It usually sets global variables that are
used for configuration, and includes files used by any extensions.
-Distributors can easily add extra statements to the autogenerated
-LocalSettings.php by changing mw-config/overrides.php (see that file for details
-and examples).
+Distributors can easily change the installer behavior, including LocalSettings
+generated, by placing their overrides into mw-config/overrides directory. Doing
+that is highly preferred to modifying MediaWiki code directly. See
+mw-config/overrides/README for more details and examples.
There's a new maintenance/install.php script which could be used for performing
an install through the command line.
intelligently:
* $wgEmergencyContact: An e-mail address that can be used to contact the wiki
- administrator. By default, "wikiadmin@ServerName".
+ administrator. By default, "wikiadmin@ServerName".
* $wgPasswordSender: The e-mail address to use when sending password e-mails.
By default, "MediaWiki Mail <apache@ServerName>".
(with ServerName guessed from the http request)
an inconsistent wiki that may produce blank pages (php errors) when new features
using the changed schema would be used.
-Since MediaWiki 1.17 it is possible to upgrade using the installer by providing
+Since MediaWiki 1.17 it is possible to upgrade using the web installer by providing
an arbitrary secret value stored as $wgUpgradeKey in LocalSettings (older versions
needed to rename LocalSettings.php in order to upgrade using the installer).
== Documentation ==
MediaWiki's official documentation is split between two places: the source
-code, and <https://www.mediawiki.org/>. The source code documentation is written
+code, and <https://www.mediawiki.org/>. The source code documentation is written
exclusively by developers, and so is likely to be reliable (at worst,
-outdated). However, it can be pretty sparse. mediawiki.org documentation is
+outdated). However, it can be pretty sparse. mediawiki.org documentation is
often much more thorough, but it's maintained by a wiki that's open to
anonymous edits, so its quality is sometimes sketchy -- don't assume that
anything there is officially endorsed!
== Upstream ==
MediaWiki is a project hosted and led by the Wikimedia Foundation, the
-not-for-profit charity that operates Wikipedia. Wikimedia employs the lead
+not-for-profit charity that operates Wikipedia. Wikimedia employs the lead
developer and several other paid developers, but commit access is given out
-liberally and there are multiple very active volunteer developers as well. A
+liberally and there are multiple very active volunteer developers as well. A
list of developers can be found at <https://www.mediawiki.org/wiki/Developers>.
-MediaWiki's bug tracker is at <https://bugzilla.wikimedia.org>. However, most
-developers follow the bug tracker little or not at all. The best place to
-post if you want to get developers' attention is the wikitech-l mailing list
-<https://lists.wikimedia.org/mailman/listinfo/wikitech-l>. Posts to wikitech-l
-will inevitably be read by multiple experienced MediaWiki developers. There's
+MediaWiki's bug tracker is at <https://phabricator.wikimedia.org>. However, you
+might find that the best place to post if you want to get developers' attention
+is the wikitech-l mailing list
+<https://lists.wikimedia.org/mailman/listinfo/wikitech-l>. Posts to wikitech-l
+will inevitably be read by multiple experienced MediaWiki developers. There's
also an active IRC chat at <irc://irc.freenode.net/mediawiki>, where there are
usually several developers at reasonably busy times of day.
-Unfortunately, we don't have a very good system for patch review. Patches
-should be submitted on Bugzilla (as unified diffs produced with "svn diff"
-against the latest trunk revision), but many patches languish without review
-until they bitrot into uselessness. You might want to get a developer to
-commit to reviewing your patch before you put too much effort into it.
-Reasonably straightforward patches shouldn't be too hard to get accepted if
-there's an interested developer, however -- posting to Bugzilla and then
-dropping a note on wikitech-l if nobody responds is a good tactic.
+Our Git repositories are hosted at <https://gerrit.wikimedia.org>, see
+<https://www.mediawiki.org/wiki/Gerrit> for more information. Patches should
+be submitted there. If you know which developers are best suited to review your
+patch, add them to it, otherwise ask on IRC to get better review time.
All redistributors of MediaWiki should be subscribed to mediawiki-announce
-<https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce>. It's
-extremely low-traffic, with an average of less than one post per month. All
+<https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce>. It's
+extremely low-traffic, with an average of less than one post per month. All
new releases are announced here, including critical security updates.
== Useful software to install ==
* APC (Alternative PHP Cache), XCache, or similar: Will greatly speed up the
execution of MediaWiki, and all other PHP applications, at some cost in
- memory usage. Will be used automatically for the most part.
- * clamav: Can be used for virus scanning of uploaded files. Enable with
+ memory usage. Will be used automatically for the most part.
+ * clamav: Can be used for virus scanning of uploaded files. Enable with
"$wgAntivirus = 'clamav';".
- * DjVuLibre: Allows processing of DjVu files. To enable this, set
+ * DjVuLibre: Allows processing of DjVu files. To enable this, set
"$wgDjvuDump = 'djvudump'; $wgDjvuRenderer = 'ddjvu'; $wgDjvuTxt = 'djvutxt';".
- * HTML Tidy: Fixes errors in HTML at runtime. Can be enabled with
+ * HTML Tidy: Fixes errors in HTML at runtime. Can be enabled with
"$wgUseTidy = true;".
- * ImageMagick: For resizing images. "$wgUseImageMagick = true;" will enable
- it. PHP's GD can also be used, but ImageMagick is preferable.
- * Squid: Can provide a drastic speedup and a major cut in resource
- consumption, but enabling it may interfere with other applications. It might
- be suitable for a separate mediawiki-squid package. For setup details, see:
- <https://www.mediawiki.org/wiki/Manual:Squid_caching>
+ * ImageMagick: For resizing images. "$wgUseImageMagick = true;" will enable
+ it. PHP's GD can also be used, but ImageMagick is preferable.
+ * HTTP cache such as Varnish or Squid: can provide a drastic speedup and a
+ major cut in resource consumption, but enabling it may interfere with other
+ applications. It might be suitable for a separate package. For setup details, see:
+ - <https://www.mediawiki.org/wiki/Manual:Varnish_caching>
+ - <https://www.mediawiki.org/wiki/Manual:Squid_caching>
* rsvg or other SVG rasterizer: ImageMagick can be used for SVG support, but
- is not ideal. Wikipedia (as of the time of this writing) uses rsvg. To
+ is not ideal. Wikipedia (as of the time of this writing) uses rsvg. To
enable, set "$wgSVGConverter = 'rsvg';" (or other as appropriate).
- * texvc: Included with MediaWiki. Instructions for compiling and
- installing it are in the math/ directory.
-MediaWiki uses some standard GNU utilities as well, such as diff and diff3. If
+MediaWiki uses some standard GNU utilities as well, such as diff and diff3. If
these are present in /usr/bin or some other reasonable location, they will be
configured automatically on install.
-MediaWiki also has a "job queue" that handles background processing. Because
+MediaWiki also has a "job queue" that handles background processing. Because
shared hosts often don't provide access to cron, the job queue is run on every
-page view by default. This means the background tasks aren't really done in
-the background. Busy wikis can set $wgJobRunRate to 0 and run
-maintenance/runJobs.php periodically out of cron. Distributors probably
+page view by default. This means the background tasks aren't really done in
+the background. Busy wikis can set $wgJobRunRate to 0 and run
+maintenance/runJobs.php periodically out of cron. Distributors probably
shouldn't set this up as a default, however, since the extra cron job is
unnecessary overhead for a little-used wiki.
== Web server configuration ==
MediaWiki includes several .htaccess files to restrict access to some
-directories. If the web server is not configured to support these files, and
+directories. If the web server is not configured to support these files, and
the relevant directories haven't been moved someplace inaccessible anyway (e.g.
symlinked in /usr/share with the web server configured to not follow symlinks),
then it might be useful to deny web access to those directories in the web
static function srcSet( array $urls ) {
$candidates = [];
foreach ( $urls as $density => $url ) {
- // Cast density to float to strip 'x'.
- $candidates[] = $url . ' ' . (float)$density . 'x';
+ // Cast density to float to strip 'x', then back to string to serve
+ // as array index.
+ $density = (string)(float)$density;
+ $candidates[$density] = $url;
}
+
+ // Remove duplicates that are the same as a smaller value
+ ksort( $candidates, SORT_NUMERIC );
+ $candidates = array_unique( $candidates );
+
+ // Append density info to the url
+ foreach ( $candidates as $density => $url ) {
+ $candidates[$density] = $url . ' ' . $density . 'x';
+ }
+
return implode( ", ", $candidates );
}
}
return Http::request( 'POST', $url, $options, $caller );
}
- /**
- * Check if the URL can be served by localhost
- *
- * @param string $url Full url to check
- * @return bool
- */
- public static function isLocalURL( $url ) {
- global $wgCommandLineMode, $wgLocalVirtualHosts;
-
- if ( $wgCommandLineMode ) {
- return false;
- }
-
- // Extract host part
- $matches = [];
- if ( preg_match( '!^http://([\w.-]+)[/:].*$!', $url, $matches ) ) {
- $host = $matches[1];
- // Split up dotwise
- $domainParts = explode( '.', $host );
- // Check if this domain or any superdomain is listed as a local virtual host
- $domainParts = array_reverse( $domainParts );
-
- $domain = '';
- $countParts = count( $domainParts );
- for ( $i = 0; $i < $countParts; $i++ ) {
- $domainPart = $domainParts[$i];
- if ( $i == 0 ) {
- $domain = $domainPart;
- } else {
- $domain = $domainPart . '.' . $domain;
- }
-
- if ( in_array( $domain, $wgLocalVirtualHosts ) ) {
- return true;
- }
- }
- }
-
- return false;
- }
-
/**
* A standard user-agent we can use for external requests.
* @return string
// Otherwise, fallback to $wgHTTPProxy if this is not a machine
// local URL and proxies are not disabled
- if ( Http::isLocalURL( $this->url ) || $this->noProxy ) {
+ if ( self::isLocalURL( $this->url ) || $this->noProxy ) {
$this->proxy = '';
} else {
$this->proxy = Http::getProxy();
}
}
+ /**
+ * Check if the URL can be served by localhost
+ *
+ * @param string $url Full url to check
+ * @return bool
+ */
+ private static function isLocalURL( $url ) {
+ global $wgCommandLineMode, $wgLocalVirtualHosts;
+
+ if ( $wgCommandLineMode ) {
+ return false;
+ }
+
+ // Extract host part
+ $matches = [];
+ if ( preg_match( '!^https?://([\w.-]+)[/:].*$!', $url, $matches ) ) {
+ $host = $matches[1];
+ // Split up dotwise
+ $domainParts = explode( '.', $host );
+ // Check if this domain or any superdomain is listed as a local virtual host
+ $domainParts = array_reverse( $domainParts );
+
+ $domain = '';
+ $countParts = count( $domainParts );
+ for ( $i = 0; $i < $countParts; $i++ ) {
+ $domainPart = $domainParts[$i];
+ if ( $i == 0 ) {
+ $domain = $domainPart;
+ } else {
+ $domain = $domainPart . '.' . $domain;
+ }
+
+ if ( in_array( $domain, $wgLocalVirtualHosts ) ) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
/**
* Set the user agent
* @param string $UA
return $multi ? $res : current( $res );
}
- /**
- * Returns true if the given vhost is handled locally.
- *
- * @deprecated since 1.25; check if the host is in $wgLocalVirtualHosts instead.
- * @param string $vhost
- * @return bool
- */
- public function isLocalVHost( $vhost ) {
- return in_array( $vhost, $this->localVHosts );
- }
-
/**
* Merge multiple arrays together.
* On encountering duplicate keys, merge the two, but ONLY if they're arrays.
# Parse content; note that HTML generation is only needed if we want to cache the result.
$content = $page->getContent( Revision::RAW );
- $enableParserCache = $this->getConfig()->get( 'EnableParserCache' );
- $p_result = $content->getParserOutput(
- $title,
- $page->getLatest(),
- $popts,
- $enableParserCache
- );
-
- # Logging to better see expensive usage patterns
- if ( $forceRecursiveLinkUpdate ) {
- LoggerFactory::getInstance( 'RecursiveLinkPurge' )->info(
- "Recursive link purge enqueued for {title}",
- [
- 'user' => $this->getUser()->getName(),
- 'title' => $title->getPrefixedText()
- ]
+ if ( $content ) {
+ $enableParserCache = $this->getConfig()->get( 'EnableParserCache' );
+ $p_result = $content->getParserOutput(
+ $title,
+ $page->getLatest(),
+ $popts,
+ $enableParserCache
);
- }
-
- # Update the links tables
- $updates = $content->getSecondaryDataUpdates(
- $title, null, $forceRecursiveLinkUpdate, $p_result );
- DataUpdate::runUpdates( $updates );
-
- $r['linkupdate'] = true;
- if ( $enableParserCache ) {
- $pcache = ParserCache::singleton();
- $pcache->save( $p_result, $page, $popts );
+ # Logging to better see expensive usage patterns
+ if ( $forceRecursiveLinkUpdate ) {
+ LoggerFactory::getInstance( 'RecursiveLinkPurge' )->info(
+ "Recursive link purge enqueued for {title}",
+ [
+ 'user' => $this->getUser()->getName(),
+ 'title' => $title->getPrefixedText()
+ ]
+ );
+ }
+
+ # Update the links tables
+ $updates = $content->getSecondaryDataUpdates(
+ $title, null, $forceRecursiveLinkUpdate, $p_result );
+ DataUpdate::runUpdates( $updates );
+
+ $r['linkupdate'] = true;
+
+ if ( $enableParserCache ) {
+ $pcache = ParserCache::singleton();
+ $pcache->save( $p_result, $page, $popts );
+ }
}
} else {
$error = $this->parseMsg( [ 'actionthrottledtext' ] );
"api-help-examples": "{{PLURAL:$1|Esempio|Esempi}}:",
"api-help-permissions": "{{PLURAL:$1|Permesso|Permessi}}:",
"api-help-open-in-apisandbox": "<small>[apri in una sandbox]</small>",
- "api-help-authmanager-general-usage": "La procedura generale per usare questo modulo é:\n# Ottenere i campi disponibili da <kbd>[[Special:ApiHelp/query+authmanagerinfo|action=query&meta=authmanagerinfo]]</kbd> con <kbd>amirequestsfor=$4</kbd>, e un token <kbd>$5</kbd> da <kbd>[[Special:ApiHelp/query+tokens|action=query&meta=tokens]]</kbd>.\n# Mostra i campi all'utente e ottieni i dati che invia.\n# Esegui un post a questo modulo, fornendo <var>$1returnurl</var> e ogni campo rilevante.\n# Controlla <samp>status</samp> nella response.\n#* Se hai ricevuto <samp>PASS</samp> o <samp>FAIL</samp>, hai finito. L'operazione nel primo caso è andata a buon fine, nel secondo no.\n#* Se hai ricevuto <samp>UI</samp>, mostra i nuovi campi all'utente e ottieni i dati che invia. Esegui un post a questo modulo con <var>$1continue</var> e i campi rilevanti settati, quindi ripeti il punto 4.\n#* Se hai ricevuto <samp>REDIRECT</samp>, dirigi l'utente a <samp>redirecttarget</samp> e aspetta che ritorni a <var>$1returnurl</var>. A quel punto esegui un post a questo modulo con <var>$1continue</var> e ogni campo passato all'URL di ritorno, e ripeti il punto 4.\n#* Se hai ricevuto <samp>RESTART</samp>, vuol dire che l'autenticazione ha funzionato ma non abbiamo un account collegato. Potresti considerare questo caso come <samp>UI</samp> o come <samp>FAIL</samp>.",
+ "api-help-authmanager-general-usage": "La procedura generale per usare questo modulo è:\n# Ottenere i campi disponibili da <kbd>[[Special:ApiHelp/query+authmanagerinfo|action=query&meta=authmanagerinfo]]</kbd> con <kbd>amirequestsfor=$4</kbd>, e un token <kbd>$5</kbd> da <kbd>[[Special:ApiHelp/query+tokens|action=query&meta=tokens]]</kbd>.\n# Mostra i campi all'utente e ottieni i dati che invia.\n# Esegui un post a questo modulo, fornendo <var>$1returnurl</var> e ogni campo rilevante.\n# Controlla <samp>status</samp> nella response.\n#* Se hai ricevuto <samp>PASS</samp> o <samp>FAIL</samp>, hai finito. L'operazione nel primo caso è andata a buon fine, nel secondo no.\n#* Se hai ricevuto <samp>UI</samp>, mostra i nuovi campi all'utente e ottieni i dati che invia. Esegui un post a questo modulo con <var>$1continue</var> e i campi rilevanti settati, quindi ripeti il punto 4.\n#* Se hai ricevuto <samp>REDIRECT</samp>, dirigi l'utente a <samp>redirecttarget</samp> e aspetta che ritorni a <var>$1returnurl</var>. A quel punto esegui un post a questo modulo con <var>$1continue</var> e ogni campo passato all'URL di ritorno, e ripeti il punto 4.\n#* Se hai ricevuto <samp>RESTART</samp>, vuol dire che l'autenticazione ha funzionato ma non abbiamo un account collegato. Potresti considerare questo caso come <samp>UI</samp> o come <samp>FAIL</samp>.",
"api-help-authmanagerhelper-messageformat": "Formato da utilizzare per per la restituzione dei messaggi.",
"api-help-authmanagerhelper-preservestate": "Conserva lo stato da un precedente tentativo di accesso non riuscito, se possibile.",
"api-help-authmanagerhelper-returnurl": "URL di ritorno per i flussi di autenticazione di terze parti, deve essere assoluto. E' necessario fornirlo, oppure va fornito <var>$1continue</var>.\n\nAlla ricezione di una risposta <samp>REDIRECT</samp>, in genere si apre un browser o una vista web all'URL specificato <samp>redirecttarget</samp> per un flusso di autenticazione di terze parti. Quando questo è completato, la terza parte invierà il browser o la vista web a questo URL. Dovresti estrarre qualsiasi parametro POST o della richiesta dall'URL e passarli come un request <var>$1continue</var> a questo modulo API.",
'be-tarask' => [ "Ё" ],
'cy' => [ "Ch", "Dd", "Ff", "Ng", "Ll", "Ph", "Rh", "Th" ],
'en' => [],
- 'fa' => [ "آ", "ء", "ه" ],
+ // RTL, let's put each letter on a new line
+ 'fa' => [
+ "آ",
+ "ء",
+ "ه",
+ "ا",
+ "و"
+ ],
'fi' => [ "Å", "Ä", "Ö" ],
'fr' => [],
'hu' => [ "Cs", "Dz", "Dzs", "Gy", "Ly", "Ny", "Ö", "Sz", "Ty", "Ü", "Zs" ],
*
* @param IDatabase $dbw
* @param integer $pageId
- * @return ScopedCallback|null Returns null on failure
+ * @param string $why One of (job, atomicity)
+ * @return ScopedCallback
* @throws RuntimeException
* @since 1.27
*/
- public static function acquirePageLock( IDatabase $dbw, $pageId ) {
- $scopedLock = $dbw->getScopedLockAndFlush(
- "LinksUpdate:pageid:$pageId",
- __METHOD__,
- 15
- );
+ public static function acquirePageLock( IDatabase $dbw, $pageId, $why = 'atomicity' ) {
+ $key = "LinksUpdate:$why:pageid:$pageId";
+ $scopedLock = $dbw->getScopedLockAndFlush( $key, __METHOD__, 15 );
if ( !$scopedLock ) {
- throw new RuntimeException( "Could not acquire lock on page #$pageId." );
+ throw new RuntimeException( "Could not acquire lock '$key'." );
}
return $scopedLock;
}
$pageId = $this->params['pageId'];
+
+ // Serialize links updates by page ID so they see each others' changes
+ $scopedLock = LinksUpdate::acquirePageLock( wfGetDB( DB_MASTER ), $pageId, 'job' );
+
if ( WikiPage::newFromID( $pageId, WikiPage::READ_LATEST ) ) {
// The page was restored somehow or something went wrong
$this->setLastError( "deleteLinks: Page #$pageId exists" );
* @return bool
*/
protected function runForTitle( Title $title ) {
+ $stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
+
$page = WikiPage::factory( $title );
$page->loadPageData( WikiPage::READ_LATEST );
+
+ // Serialize links updates by page ID so they see each others' changes
+ $scopedLock = LinksUpdate::acquirePageLock( wfGetDB( DB_MASTER ), $page->getId(), 'job' );
+ // Get the latest ID *after* acquirePageLock() flushed the transaction.
+ // This is used to detect edits/moves after loadPageData() but before the scope lock.
+ // The works around the chicken/egg problem of determining the scope lock key.
+ $latest = $title->getLatestRevID( Title::GAID_FOR_UPDATE );
+
if ( !empty( $this->params['triggeringRevisionId'] ) ) {
// Fetch the specified revision; lockAndGetLatest() below detects if the page
// was edited since and aborts in order to avoid corrupting the link tables
$revision = Revision::newFromTitle( $title, false, Revision::READ_LATEST );
}
- $stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
-
if ( !$revision ) {
$stats->increment( 'refreshlinks.rev_not_found' );
$this->setLastError( "Revision not found for {$title->getPrefixedDBkey()}" );
return false; // just deleted?
- } elseif ( !$revision->isCurrent() || $revision->getPage() != $page->getId() ) {
- // If the revision isn't current, there's no point in doing a bunch
- // of work just to fail at the lockAndGetLatest() check later.
+ } elseif ( $revision->getId() != $latest || $revision->getPage() !== $page->getId() ) {
+ // Do not clobber over newer updates with older ones. If all jobs where FIFO and
+ // serialized, it would be OK to update links based on older revisions since it
+ // would eventually get to the latest. Since that is not the case (by design),
+ // only update the link tables to a state matching the current revision's output.
$stats->increment( 'refreshlinks.rev_not_current' );
$this->setLastError( "Revision {$revision->getId()} is not current" );
return false;
}
}
- $latestNow = $page->lockAndGetLatest();
- if ( !$latestNow || $revision->getId() != $latestNow ) {
- // Do not clobber over newer updates with older ones. If all jobs where FIFO and
- // serialized, it would be OK to update links based on older revisions since it
- // would eventually get to the latest. Since that is not the case (by design),
- // only update the link tables to a state matching the current revision's output.
- $stats->increment( 'refreshlinks.rev_cas_failure' );
- $this->setLastError( "page_latest changed from {$revision->getId()} to $latestNow" );
- return false;
- }
-
DataUpdate::runUpdates( $updates );
InfoAction::invalidateCache( $title );
}
// Additional densities for responsive images, if specified.
- if ( !empty( $this->responsiveUrls ) ) {
- $attribs['srcset'] = Html::srcSet( $this->responsiveUrls );
+ // If any of these urls is the same as src url, it'll be excluded.
+ $responsiveUrls = array_diff( $this->responsiveUrls, [ $this->url ] );
+ if ( !empty( $responsiveUrls ) ) {
+ $attribs['srcset'] = Html::srcSet( $responsiveUrls );
}
Hooks::run( 'ThumbnailBeforeProduceHTML', [ $this, &$attribs, &$linkAttribs ] );
return false;
}
- $title = $this->mTitle;
- wfGetDB( DB_MASTER )->onTransactionIdle( function() use ( $title ) {
- // Invalidate the cache in auto-commit mode
- $title->invalidateCache();
- } );
-
+ $this->mTitle->invalidateCache();
// Send purge after above page_touched update was committed
DeferredUpdates::addUpdate(
- new CdnCacheUpdate( $title->getCdnUrls() ),
+ new CdnCacheUpdate( $this->mTitle->getCdnUrls() ),
DeferredUpdates::PRESEND
);
"prefs-namespaces": "Namensräume",
"default": "Voreinstellung",
"prefs-files": "Dateien",
- "prefs-custom-css": "Benutzerdefinierte CSS",
+ "prefs-custom-css": "Benutzerdefiniertes CSS",
"prefs-custom-js": "Benutzerdefiniertes JavaScript",
"prefs-common-css-js": "Gemeinsames CSS/JavaScript aller Benutzeroberflächen:",
"prefs-reset-intro": "Du kannst diese Seite verwenden, um die Einstellungen auf die Standards zurückzusetzen.\nDies kann nicht mehr rückgängig gemacht werden.",
"undelete_short": "Restaurar {{PLURAL:$1|una edición|$1 ediciones}}",
"viewdeleted_short": "Ver {{PLURAL:$1|una edición borrada|$1 ediciones borradas}}",
"protect": "Proteger",
- "protect_change": "Cambiar",
+ "protect_change": "cambiar",
"protectthispage": "Proteger esta página",
"unprotect": "Cambiar protección",
"unprotectthispage": "Cambiar la protección de esta página",
"newpage": "Página nueva",
"talkpage": "Discutir esta página",
- "talkpagelinktext": "Discusión",
+ "talkpagelinktext": "discusión",
"specialpage": "Página especial",
"personaltools": "Herramientas personales",
"articlepage": "Ver artículo",
"pt-userlogout": "Se déconnecter",
"php-mail-error-unknown": "Erreur inconnue dans la fonction <code>mail()</code> de PHP.",
"user-mail-no-addy": "Impossible d’envoyer un courriel sans adresse de courriel.",
- "user-mail-no-body": "Essai d’envoi d’un courriel avec un corps vide ou déraisonnablement court.",
+ "user-mail-no-body": "Essai d’envoi d’un courriel avec un corps vide ou anormalement court.",
"changepassword": "Changer de mot de passe",
"resetpass_announce": "Pour terminer votre inscription, vous devez fournir un nouveau mot de passe.",
"resetpass_text": "<!-- Ajoutez le texte ici -->",
"newarticletext": "Vous avez suivi un lien vers une page qui n’existe pas encore. \nAfin de créer cette page, entrez votre texte dans la boîte ci-après (vous pouvez consulter [$1 la page d’aide] pour plus d’informations). \nSi vous êtes arrivé{{GENDER:||e}} ici par erreur, cliquez sur le bouton <strong>Retour</strong> de votre navigateur.",
"anontalkpagetext": "----\n<em>Vous êtes sur la page de discussion d’un utilisateur anonyme qui n’a pas encore créé de compte ou qui n’en utilise pas</em>.\nPour cette raison, nous devons utiliser son adresse IP pour l’identifier.\nUne adresse IP peut être partagée par plusieurs utilisateurs.\nSi vous êtes un{{GENDER:||e|}} utilisat{{GENDER:|eur|rice|eur}} anonyme et si vous constatez que des commentaires qui ne vous concernent pas vous ont été adressés, vous pouvez [[Special:CreateAccount|créer un compte]] ou [[Special:UserLogin|vous connecter]] afin d’éviter toute confusion future avec d’autres contributeurs anonymes.",
"noarticletext": "Il n’y a pour l’instant aucun texte sur cette page.\nVous pouvez [[Special:Search/{{PAGENAME}}|lancer une recherche sur ce titre]] dans les autres pages,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} rechercher dans les opérations liées]\nou [{{fullurl:{{FULLPAGENAME}}|action=edit}} créer cette page]</span>.",
- "noarticletext-nopermission": "Il n'y a pour l'instant aucun texte sur cette page.\nVous pouvez [[Special:Search/{{PAGENAME}}|faire une recherche sur ce titre]] dans les autres pages,\nou <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} rechercher dans les journaux associés]</span>.",
+ "noarticletext-nopermission": "Il n'y a pour l'instant aucun texte sur cette page.\nVous pouvez [[Special:Search/{{PAGENAME}}|faire une recherche sur ce titre]] dans les autres pages,\nou <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} rechercher dans les journaux associés]</span>, mais vous n'avez pas la permission de créer cette page.",
"missing-revision": "La révision nº $1 de la page intitulée « {{FULLPAGENAME}} » n’existe pas.\n\nCela survient en général en suivant un lien historique obsolète vers une page qui a été supprimée.\nVous pouvez trouver plus de détails dans le [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} journal des suppressions].",
"userpage-userdoesnotexist": "Le compte utilisateur « <nowiki>$1</nowiki> » n’est pas enregistré. Veuillez vérifier que vous voulez créer cette page.",
"userpage-userdoesnotexist-view": "Le compte utilisateur « $1 » n'est pas enregistré.",
"permissionserrorstext": "Vous n'avez pas la permission d'effectuer l'opération demandée pour {{PLURAL:$1|la raison suivante|les raisons suivantes}} :",
"permissionserrorstext-withaction": "Vous ne pouvez pas $2, pour {{PLURAL:$1|la raison suivante|les raisons suivantes}} :",
"contentmodelediterror": "Vous ne pouvez pas modifier cette révision car son modèle de contenu est <code>$1</code>, ce qui diffère du modèle de contenu actuel de la page <code>$2</code>.",
- "recreate-moveddeleted-warn": "'''Attention : vous êtes en train de recréer une page qui a été précédemment supprimée.'''\n\nAssurez-vous qu'il est pertinent de poursuivre les modifications sur cette page. Le journal des suppressions et des déplacements est affiché ci-dessous :",
- "moveddeleted-notice": "Cette page a été supprimée. Le journal des suppressions et des déplacements est affiché ci-dessous pour référence.",
- "moveddeleted-notice-recent": "Désolé, cette page a été récemment supprimée (dans les dernières 24 heures).\nLes journaux des suppressions et des renommages pour la page sont fournis ci-dessous à titre d’information.",
+ "recreate-moveddeleted-warn": "<strong>Attention : vous êtes en train de recréer une page qui a été précédemment supprimée.</strong>\n\nAssurez-vous qu'il est pertinent de poursuivre les modifications sur cette page. \nLe journal des suppressions et des déplacements pour cette page est affiché ci-dessous à titre d'information :",
+ "moveddeleted-notice": "Cette page a été supprimée. \nLe journal des suppressions et des déplacements de la page est affiché ci-dessous pour référence.",
+ "moveddeleted-notice-recent": "Désolé, cette page a été récemment supprimée (dans les dernières 24 heures).\nLes journaux des suppressions et des renommages pour la page sont fournis ci-dessous pour référence.",
"log-fulllog": "Voir le journal complet",
"edit-hook-aborted": "Échec de la modification par une extension.\nAucune explication n’a été retournée.",
"edit-gone-missing": "N’a pas pu mettre à jour la page.\nIl semble qu’elle ait été supprimée.",
"logdelete-text": "Les évènements du journal supprimés continueront à apparaître dans les journaux, mais une partie de leur contenu sera indisponible au public.",
"revdelete-text-others": "Les autres administrateurs seront toujours en mesure d'accéder au contenu caché et le restaurer, à moins que des restrictions supplémentaires soient fixées.",
"revdelete-confirm": "Confirmez que vous voulez effectuer cette action, que vous en comprenez les conséquences, et que vous le faites en accord avec [[{{MediaWiki:Policy-url}}|les règles]].",
- "revdelete-suppress-text": "La suppression ne doit être utilisée '''que''' dans les cas suivants :\n* Informations potentiellement diffamatoires\n* Informations personnelles inappropriées\n*: ''adresse, numéro de téléphone, numéro de sécurité sociale, …''",
- "revdelete-legend": "Mettre en place des restrictions de visibilité :",
+ "revdelete-suppress-text": "La suppression ne doit être utilisée <strong>que</strong> dans les cas suivants :\n* informations potentiellement diffamatoires\n* informations personnelles inappropriées\n*: <em>adresse, numéro de téléphone, numéro de sécurité sociale, …</em>",
+ "revdelete-legend": "Mettre en place des restrictions de visibilité",
"revdelete-hide-text": "Texte de la révision",
"revdelete-hide-image": "Masquer le contenu du fichier",
"revdelete-hide-name": "Masquer la cible et les paramètres",
"group-user-member": "صارف",
"group-autoconfirmed-member": "خودتوثیق شدہ صارف",
"group-bot-member": "خودکار صارف",
- "group-sysop-member": "منتظم",
+ "group-sysop-member": "{{GENDER:$1|منتظم}}",
"group-bureaucrat-member": "{{GENDER:$1|مامور اداری}}",
"group-suppress-member": "{{GENDER:$1|suppressor}}",
"grouppage-user": "{{ns:project}}:صارفین",
"grouppage-autoconfirmed": "{{ns:project}}:خود توثیق شدہ صارف",
"grouppage-bot": "{{ns:project}}:روبہ جات",
"grouppage-sysop": "{{ns:project}}:منتظمین",
- "grouppage-bureaucrat": "بیورو کریٹ",
+ "grouppage-bureaucrat": "{{ns:project}}:مامورین اداری",
"right-upload": "ملفات زبراثقال (اپ لوڈ) کریں",
"right-writeapi": "اے پی آئی لکھائی کا استعمال",
"right-delete": "صفحات حذف کریں",
"logentry-move-move": "$1 نے صفحہ $3 کو بجانب $4 منتقل کیا",
"logentry-newusers-create": "صارف کھاتہ $1 {{GENDER:$2|بنایا گیا}}",
"logentry-protect-modify": "$1 نے $3 کا درجۂ حفاظت {{GENDER:$2|تبدیل کیا}} $4",
+ "logentry-rights-rights": "$1 نے {{GENDER:$6|$3}} کی گروہی رکنیت از $4 تا $5 {{GENDER:$2|تبدیل کی}}",
"logentry-upload-upload": "$1 {{GENDER:$2|اپلوڈ}} $3",
"rightsnone": "(کچھ نہیں)",
"revdelete-summary": "خلاصۂ تدوین",
"youhavenewmessagesmanyusers": "您有来自多个用户的$1($2)。",
"newmessageslinkplural": "{{PLURAL:$1|新信息|999=新消息}}",
"newmessagesdifflinkplural": "最后{{PLURAL:$1|更改|999=更改}}",
- "youhavenewmessagesmulti": "你在$1有新信息",
+ "youhavenewmessagesmulti": "您在$1有新信息",
"editsection": "编辑",
"editold": "编辑",
"viewsourceold": "查看源代码",
"password-login-forbidden": "这个用户名称及密码的使用是被禁止的。",
"mailmypassword": "重置密码",
"passwordremindertitle": "{{SITENAME}}的新临时密码",
- "passwordremindertext": "有人(可能是您,来自IP地址$1)已请求{{SITENAME}}的新密码($4)。\n用户“$2”的一个新临时密码现在已被设置好为“$3”。\n如果这个动作是您所指示的,您便需要立即登录并选择一个新的密码。\n您的临时密码会于$5天内过期。\n\n如果是其他人发出了该请求,或者您已经记起了您的密码并不准备改变它,您可以忽略此消息并继续使用您的旧密码。",
+ "passwordremindertext": "有人(可能是您,来自IP地址$1)已请求{{SITENAME}}的新密码($4)。用户“$2”的一个新临时密码现在已被设置好为“$3”。如果这个动作是您所指示的,您便需要立即登录并选择一个新的密码。您的临时密码会于{{PLURAL:$5|一天|$5天}}内过期。\n\n如果是其他人发出了该请求,或者您已经记起了您的密码并不准备改变它,您可以忽略此消息并继续使用您的旧密码。",
"noemail": "用户\"$1\"没有登记电子邮件地址。",
"noemailcreate": "您需要提供一个有效的电子邮件地址",
"passwordsent": "用户\"$1\"的新密码已经寄往所登记的电子邮件地址。\n请在收到后再登录。",
"clearyourcache": "<strong>注意:</strong>在保存之后,您可能需要清除浏览器缓存才能看到所作出的变更的影响。\n* <strong>Firefox或Safari:</strong>按住<em>Shift</em>的同时单击<em>刷新</em>,或按<em>Ctrl-F5</em>或<em>Ctrl-R</em>(Mac为<em>⌘-R</em>)\n* <strong>Google Chrome:</strong>按<em>Ctrl-Shift-R</em>(Mac为<em>⌘-Shift-R</em>)\n* <strong>Internet Explorer:</strong>按住<em>Ctrl</em>的同时单击<em>刷新</em>,或按<em>Ctrl-F5</em>\n* <strong>Opera:</strong>前往<em>菜单 → 设置</em>(Mac为<em>Opera → Preferences</em>),然后<em>隐私和安全 → 清除浏览数据 → 缓存的图片和文件</em>。",
"usercssyoucanpreview": "<strong>提示:</strong>在保存前请用“{{int:showpreview}}”按钮来测试您新的 CSS 。",
"userjsyoucanpreview": "<strong>提示:</strong>在保存前请用“{{int:showpreview}}”按钮来测试您新的 JavaScript 。",
- "usercsspreview": "<strong>请记住您现在只是在预览你的用户CSS。它尚未保存!</strong>",
+ "usercsspreview": "<strong>请记住您现在只是在预览您的用户CSS。它尚未保存!</strong>",
"userjspreview": "<strong>请记住您现在只是在测试/预览您的用户JavaScript。它尚未保存!</strong>",
- "sitecsspreview": "<strong>请记住你现在只是在预览该CSS。它尚未保存!</strong>",
+ "sitecsspreview": "<strong>请记住您现在只是在预览该CSS。它尚未保存!</strong>",
"sitejspreview": "<strong>请记住您现在只是在预览该JavaScript代码。它尚未保存!</strong>",
"userinvalidcssjstitle": "<strong>警告:</strong>不存在皮肤“$1”。注意自定义的 .css 和 .js 页要使用小写标题,例如,{{ns:user}}:Foo/vector.css 不同于 {{ns:user}}:Foo/Vector.css。",
"updated": "(已更新)",
"emailpagetext": "您可以使用下面的表格发送电子邮件信息至该{{GENDER:$1|用户}}。您在[[Special:Preferences|系统设置]]中输入的电子邮件地址将显示为邮件的“发件人”地址,所以该用户将可以直接回复您。",
"defemailsubject": "来自{{SITENAME}}用户“$1”的电子邮件",
"usermaildisabled": "用户电子邮件停用",
- "usermaildisabledtext": "你不能发送电子邮件至本wiki的其他用户",
+ "usermaildisabledtext": "您不能发送电子邮件至本wiki的其他用户",
"noemailtitle": "无电子邮件地址",
"noemailtext": "该用户还没有指定一个有效的电子邮件地址。",
"nowikiemailtext": "该用户已经选择不接收来自其他用户的电子邮件。",
"rcpatroldisabled": "最近更改巡查已禁用",
"rcpatroldisabledtext": "最近更改巡查功能目前已关闭。",
"markedaspatrollederror": "不能标志为已检查",
- "markedaspatrollederrortext": "你需要指定一个版本以标记为已巡查。",
+ "markedaspatrollederrortext": "您需要指定一个版本以标记为已巡查。",
"markedaspatrollederror-noautopatrol": "你不能把自己的更改标记为已检查。",
"markedaspatrollednotify": "$1的更改已被标记为已巡查。",
"markedaspatrollederrornotify": "标记为已巡查失败。",
],
'mediawiki.content.json' => [
'position' => 'top',
- 'styles' => 'resources/src/mediawiki/mediawiki.content.json.css',
+ 'styles' => 'resources/src/mediawiki/mediawiki.content.json.less',
],
'mediawiki.confirmCloseWindow' => [
'scripts' => [
+++ /dev/null
-/*!
- * CSS for styling HTML-formatted JSON Schema objects
- *
- * @file
- * @author Munaf Assaf <massaf@wikimedia.org>
- */
-
-.mw-json {
- border-collapse: collapse;
- border-spacing: 0;
- font-style: normal;
-}
-
-.mw-json th,
-.mw-json td {
- border: 1px solid #808080;
- font-size: 16px;
- padding: 0.5em 1em;
-}
-
-.mw-json .value,
-.mw-json-single-value {
- background-color: #dcfae3;
- font-family: monospace, monospace;
- white-space: pre-wrap;
-}
-
-.mw-json-single-value {
- background-color: #eee;
-}
-
-.mw-json-empty {
- background-color: #fff;
- font-style: italic;
-}
-
-.mw-json tr {
- margin-bottom: 0.5em;
- background-color: #eee;
-}
-
-.mw-json th {
- background-color: #fff;
- font-weight: normal;
-}
-
-.mw-json caption {
- /* For stylistic reasons, suppress the caption of the outermost table */
- display: none;
-}
-
-.mw-json table caption {
- color: #808080;
- display: inline-block;
- font-size: 10px;
- font-style: italic;
- margin-bottom: 0.5em;
- text-align: left;
-}
--- /dev/null
+/*!
+ * CSS for styling HTML-formatted JSON Schema objects
+ *
+ * @file
+ * @author Munaf Assaf <massaf@wikimedia.org>
+ */
+
+.mw-json {
+ border-collapse: collapse;
+ border-spacing: 0;
+ font-style: normal;
+}
+
+.mw-json th,
+.mw-json td {
+ border: 1px solid #808080;
+ font-size: 16px;
+ padding: 0.5em 1em;
+}
+
+.mw-json .value,
+.mw-json-single-value {
+ background-color: #dcfae3;
+ font-family: monospace, monospace;
+ white-space: pre-wrap;
+}
+
+.mw-json-single-value {
+ background-color: #eee;
+}
+
+.mw-json-empty {
+ background-color: #fff;
+ font-style: italic;
+}
+
+.mw-json tr {
+ margin-bottom: 0.5em;
+ background-color: #eee;
+}
+
+.mw-json th {
+ background-color: #fff;
+ font-weight: normal;
+}
+
+.mw-json caption {
+ /* For stylistic reasons, suppress the caption of the outermost table */
+ display: none;
+}
+
+.mw-json table caption {
+ color: #808080;
+ display: inline-block;
+ font-size: 10px;
+ font-style: italic;
+ margin-bottom: 0.5em;
+ text-align: left;
+}
<li>b</li>
</ul>
!! end
+
+!! test
+Thumbnail output
+!! wikitext
+[[File:Thumb.png|thumb]]
+!! html/php+tidy
+<div class="thumb tright">
+<div class="thumbinner" style="width:137px;"><a href="/wiki/File:Thumb.png" class="image"><img alt="Thumb.png" src="http://example.com/images/e/ea/Thumb.png" width="135" height="135" class="thumbimage" /></a>
+<div class="thumbcaption">
+<div class="magnify"><a href="/wiki/File:Thumb.png" class="internal" title="Enlarge"></a></div>
+</div>
+</div>
+</div>
+!! end
'1x.png 1x, 1_5x.png 1.5x, 2x.png 2x',
'pixel depth keys may omit a trailing "x"'
],
+ [
+ [ '1' => 'small.png', '1.5' => 'large.png', '2' => 'large.png' ],
+ 'small.png 1x, large.png 1.5x',
+ 'omit larger duplicates'
+ ],
+ [
+ [ '1' => 'small.png', '2' => 'large.png', '1.5' => 'large.png' ],
+ 'small.png 1x, large.png 1.5x',
+ 'omit larger duplicates in irregular order'
+ ],
];
}
$this->assertFalse( $testXML->wellFormed );
}
+ /**
+ * Verify we check for recursive entity DOS
+ *
+ * (If the DOS isn't properly handled, the test runner will probably go OOM...)
+ */
+ public function testRecursiveEntity() {
+ $xml = <<<'XML'
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE foo [
+ <!ENTITY test "&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;">
+ <!ENTITY a "&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;">
+ <!ENTITY b "&c;&c;&c;&c;&c;&c;&c;&c;&c;&c;&c;&c;&c;&c;&c;&c;&c;&c;&c;&c;&c;&c;&c;&c;">
+ <!ENTITY c "&d;&d;&d;&d;&d;&d;&d;&d;&d;&d;&d;&d;&d;&d;&d;&d;&d;&d;&d;&d;&d;&d;&d;&d;">
+ <!ENTITY d "&e;&e;&e;&e;&e;&e;&e;&e;&e;&e;&e;&e;&e;&e;&e;&e;&e;&e;&e;&e;&e;&e;&e;&e;">
+ <!ENTITY e "&f;&f;&f;&f;&f;&f;&f;&f;&f;&f;&f;&f;&f;&f;&f;&f;&f;&f;&f;&f;&f;&f;&f;&f;">
+ <!ENTITY f "&g;&g;&g;&g;&g;&g;&g;&g;&g;&g;&g;&g;&g;&g;&g;&g;&g;&g;&g;&g;&g;&g;&g;&g;">
+ <!ENTITY g "-00000000000000000000000000000000000000000000000000000000000000000000000-">
+]>
+<foo>
+<bar>&test;</bar>
+</foo>
+XML;
+ $check = XmlTypeCheck::newFromString( $xml );
+ $this->assertFalse( $check->wellFormed );
+ }
+
/**
* @covers XMLTypeCheck::processingInstructionHandler
*/
}
/**
- * @covers ExtensionProcessor::extractConfig
+ * @covers ExtensionProcessor::extractConfig1
*/
public function testExtractConfig() {
$processor = new ExtensionProcessor;