Merge "Optimize ChronologyProtector writes for multi-DC case"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Thu, 25 Aug 2016 03:00:39 +0000 (03:00 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Thu, 25 Aug 2016 03:00:39 +0000 (03:00 +0000)
37 files changed:
includes/MediaWiki.php
includes/SiteStats.php
includes/api/ApiBase.php
includes/api/i18n/es.json
includes/db/DBConnRef.php
includes/db/Database.php
includes/db/DatabaseMysqlBase.php
includes/db/DatabaseMysqli.php
includes/db/IDatabase.php
includes/db/loadbalancer/LBFactory.php
includes/db/loadbalancer/LoadBalancer.php
includes/deferred/SiteStatsUpdate.php
includes/installer/i18n/es.json
includes/page/Article.php
includes/page/WikiPage.php
includes/resourceloader/ResourceLoader.php
includes/specialpage/AuthManagerSpecialPage.php
languages/i18n/de.json
languages/i18n/diq.json
languages/i18n/dty.json
languages/i18n/es.json
languages/i18n/fr.json
languages/i18n/km.json
languages/i18n/ko.json
languages/i18n/mk.json
languages/i18n/oc.json
languages/i18n/qqq.json
languages/i18n/sl.json
languages/i18n/sv.json
languages/i18n/tcy.json
languages/i18n/uk.json
tests/TestsAutoLoader.php
tests/phpunit/ResourceLoaderTestCase.php
tests/phpunit/includes/db/DatabaseTest.php
tests/phpunit/includes/installer/DatabaseUpdaterTest.php
tests/phpunit/includes/resourceloader/ResourceLoaderStartUpModuleTest.php
tests/phpunit/includes/resourceloader/ResourceLoaderTest.php

index 77ac76a..2a00900 100644 (file)
@@ -286,6 +286,16 @@ class MediaWiki {
                                // may still be a wikipage redirect to another article or URL.
                                $article = $this->initializeArticle();
                                if ( is_object( $article ) ) {
+                                       $url = $request->getFullRequestURL(); // requested URL
+                                       if (
+                                               $request->getMethod() === 'GET' &&
+                                               $url === $article->getTitle()->getCanonicalURL() &&
+                                               $article->checkTouched() &&
+                                               $output->checkLastModified( $article->getTouched() )
+                                       ) {
+                                               wfDebug( __METHOD__ . ": done 304\n" );
+                                               return;
+                                       }
                                        $this->performAction( $article, $requestTitle );
                                } elseif ( is_string( $article ) ) {
                                        $output->redirect( $article );
index 03b4b8c..604ab93 100644 (file)
@@ -36,6 +36,10 @@ class SiteStats {
        /** @var int[] */
        private static $pageCount = [];
 
+       static function unload() {
+               self::$loaded = false;
+       }
+
        static function recache() {
                self::load( true );
        }
index fcb748c..66c1b53 100644 (file)
@@ -1171,6 +1171,7 @@ abstract class ApiBase extends ContextSource {
                        : self::LIMIT_SML1;
 
                if ( self::truncateArray( $valuesList, $sizeLimit ) ) {
+                       $this->logFeatureUsage( "too-many-$valueName-for-{$this->getModulePath()}" );
                        $this->setWarning( "Too many values supplied for parameter '$valueName': " .
                                "the limit is $sizeLimit" );
                }
index f733c47..b8756c5 100644 (file)
        "apihelp-linkaccount-example-link": "Iniciar el proceso de vincular a una cuenta de <kbd>Ejemplo</kbd>.",
        "apihelp-login-description": "Iniciar sesión y obtener las cookies de autenticación.\n\nEsta acción solo se debe utilizar en combinación con [[Special:BotPasswords]]; para la cuenta de inicio de sesión no se utiliza y puede fallar sin previo aviso. Para iniciar la sesión de forma segura a la cuenta principal, utilice <kbd>[[Special:ApiHelp/clientlogin|action=clientlogin]]</kbd>.",
        "apihelp-login-description-nobotpasswords": "Iniciar sesión y obtener las cookies de autenticación.\n\nEsta acción esta obsoleta y puede fallar sin previo aviso. Para conectarse de forma segura, utilice <kbd>[[Special:ApiHelp/clientlogin|action=clientlogin]]</kbd>.",
-       "apihelp-login-description-nonauthmanager": "Iniciar sesión y obtener cookies de autenticación.\n\nSi inicias sesión sin problemas, las cookies necesarias se incluirán en los encabezados de respuesta HTTP. Si se produce algún error al iniciar sesión y este persiste, se puede regular para evitar los ataques masivos automatizados de adivinar contraseñas.",
        "apihelp-login-param-name": "Nombre de usuario.",
        "apihelp-login-param-password": "Contraseña.",
        "apihelp-login-param-domain": "Dominio (opcional).",
        "apihelp-query+alllinks-example-unique-generator": "Obtiene todos los títulos enlazados, marcando los que falten.",
        "apihelp-query+alllinks-example-generator": "Obtiene páginas que contienen los enlaces.",
        "apihelp-query+allmessages-description": "Devolver los mensajes de este sitio.",
+       "apihelp-query+allmessages-param-messages": "Qué mensajes mostrar. <kbd>*</kbd> (por defecto) significa todos los mensajes.",
        "apihelp-query+allmessages-param-prop": "Qué propiedades se obtendrán.",
+       "apihelp-query+allmessages-param-enableparser": "Establecer para habilitar el analizador, se preprocesará el wikitexto del mensaje (sustitución de palabras mágicas, uso de plantillas, etc.).",
        "apihelp-query+allmessages-param-nocontent": "Si se establece, no incluya el contenido de los mensajes en la salida.",
        "apihelp-query+allmessages-param-args": "Los argumentos que se sustituyen en el mensaje.",
        "apihelp-query+allmessages-param-filter": "Devolver solo mensajes con nombres que contengan esta cadena.",
        "apihelp-query+mystashedfiles-description": "Obtener una lista de archivos en la corriente de carga de usuarios.",
        "apihelp-query+mystashedfiles-param-prop": "Propiedades a buscar para los archivos.",
        "apihelp-query+mystashedfiles-paramvalue-prop-size": "Buscar el tamaño del archivo y las dimensiones de la imagen.",
+       "apihelp-query+mystashedfiles-paramvalue-prop-type": "Obtener el tipo MIME y tipo multimedia del archivo.",
        "apihelp-query+mystashedfiles-param-limit": "Cuántos archivos obtener.",
        "apihelp-query+alltransclusions-param-from": "El título de la transclusión por la que empezar la enumeración.",
        "apihelp-query+alltransclusions-param-to": "El título de la transclusión por la que terminar la enumeración.",
        "apihelp-query+alltransclusions-param-limit": "Número de elementos que se desea obtener.",
        "apihelp-query+alltransclusions-example-unique": "Listar títulos transcluidos de forma única.",
        "apihelp-query+alltransclusions-example-unique-generator": "Obtiene todos los títulos transcluidos, marcando los que faltan.",
+       "apihelp-query+alltransclusions-example-generator": "Obtiene las páginas que contienen las transclusiones.",
        "apihelp-query+allusers-description": "Enumerar todos los usuarios registrados.",
        "apihelp-query+allusers-param-prefix": "Buscar todos los usuarios que empiecen con este valor.",
        "apihelp-query+allusers-param-group": "Incluir solo usuarios en los grupos dados.",
        "apihelp-query+allusers-paramvalue-prop-blockinfo": "Añade información sobre un bloque actual al usuario.",
        "apihelp-query+allusers-paramvalue-prop-groups": "Lista los grupos a los que el usuario pertenece. Esto utiliza más recursos del servidor y puede devolver menos resultados que el límite.",
        "apihelp-query+allusers-paramvalue-prop-rights": "Lista los permisos que tiene el usuario.",
+       "apihelp-query+allusers-paramvalue-prop-editcount": "Añade el número de ediciones del usuario.",
+       "apihelp-query+allusers-paramvalue-prop-registration": "Añade la marca de tiempo del momento en que el usuario se registró, si está disponible (puede estar en blanco).",
        "apihelp-query+allusers-param-limit": "Cuántos nombres de usuario se devolverán.",
        "apihelp-query+allusers-param-activeusers": "Solo listar usuarios activos en {{PLURAL:$1|el último día|los $1 últimos días}}.",
        "apihelp-query+allusers-example-Y": "Listar usuarios que empiecen por <kbd>Y</kbd>.",
        "apihelp-query+filerepoinfo-example-login": "Captura de las solicitudes que puede ser utilizadas al comienzo de inicio de sesión.",
+       "apihelp-query+backlinks-description": "Encuentra todas las páginas que enlazan a la página dada.",
        "apihelp-query+backlinks-param-pageid": "Identificador de página que buscar. No puede usarse junto con <var>$1title</var>",
        "apihelp-query+backlinks-param-filterredir": "Cómo filtrar redirecciones. Si se establece a <kbd>nonredirects</kbd> cuando está activo <var>$1redirect</var>, esto sólo se aplica al segundo nivel.",
        "apihelp-query+backlinks-param-limit": "Cuántas páginas en total se devolverán. Si está activo <var>$1redirect</var>, el límite aplica a cada nivel por separado (lo que significa que se pueden devolver hasta 2 * <var>$1limit</var> resultados).",
        "apihelp-query+blocks-paramvalue-prop-reason": "Añade la razón dada para el bloqueo.",
        "apihelp-query+blocks-example-simple": "Listar bloques.",
        "apihelp-query+categories-param-prop": "Qué propiedades adicionales obtener para cada categoría:",
+       "apihelp-query+categories-paramvalue-prop-timestamp": "Añade la marca de tiempo del momento en que se añadió la categoría.",
        "apihelp-query+categories-param-show": "Qué tipo de categorías mostrar.",
        "apihelp-query+categories-param-limit": "Cuántas categorías se devolverán.",
        "apihelp-query+categories-example-generator": "Obtener información acerca de todas las categorías utilizadas en la página <kbd>Albert Einstein</kbd>.",
        "apihelp-query+categoryinfo-description": "Devuelve información acerca de las categorías dadas.",
        "apihelp-query+categoryinfo-example-simple": "Obtener información acerca de <kbd>Category:Foo</kbd> y <kbd>Category:Bar</kbd>",
+       "apihelp-query+categorymembers-description": "Lista todas las páginas en una categoría dada.",
        "apihelp-query+categorymembers-param-prop": "Qué piezas de información incluir:",
        "apihelp-query+categorymembers-paramvalue-prop-ids": "Añade el identificador de página.",
        "apihelp-query+categorymembers-paramvalue-prop-title": "Agrega el título y el identificador del espacio de nombres de la página.",
        "apihelp-query+categorymembers-paramvalue-prop-type": "Añade el tipo en el que se categorizó la página (<samp>page</samp>, <samp>subcat</samp> or <samp>file</samp>).",
+       "apihelp-query+categorymembers-paramvalue-prop-timestamp": "Añade la marca de tiempo del momento en que se incluyó la página.",
        "apihelp-query+categorymembers-param-sort": "Propiedad por la que realizar la ordenación.",
        "apihelp-query+categorymembers-param-dir": "Dirección en la que desea ordenar.",
        "apihelp-query+categorymembers-param-startsortkey": "Utilizar $1starthexsortkey en su lugar.",
        "apihelp-query+filearchive-param-to": "El título de imagen para detener la enumeración.",
        "apihelp-query+filearchive-param-prefix": "Buscar todos los títulos de las imágenes que comiencen con este valor.",
        "apihelp-query+filearchive-param-prop": "Qué información de imagen se obtendrá:",
+       "apihelp-query+filearchive-paramvalue-prop-timestamp": "Añade la marca de tiempo de la versión subida.",
+       "apihelp-query+filearchive-paramvalue-prop-user": "Agrega el usuario que subió la versión de la imagen.",
        "apihelp-query+filearchive-paramvalue-prop-size": "Agrega el tamaño de la imagen en bytes y la altura, la anchura y el número de páginas (si es aplicable).",
        "apihelp-query+filearchive-paramvalue-prop-dimensions": "Alias del tamaño.",
        "apihelp-query+filearchive-paramvalue-prop-description": "Añade la descripción de la versión de la imagen.",
        "apihelp-query+imageinfo-param-extmetadatafilter": "Si se especifica y no vacío, sólo estas claves serán devueltos por $1prop=extmetadata.",
        "apihelp-query+imageinfo-param-urlparam": "Un controlador específico de la cadena de parámetro. Por ejemplo, los archivos Pdf pueden utilizar <kbd>page15-100px</kbd>. <var>$1urlwidth</var> debe ser utilizado y debe ser consistente con <var>$1urlparam</var>.",
        "apihelp-query+imageinfo-param-localonly": "Buscar solo archivos en el repositorio local.",
+       "apihelp-query+imageinfo-example-simple": "Obtener información sobre la versión actual de [[:File:Albert Einstein Head.jpg]].",
+       "apihelp-query+imageinfo-example-dated": "Obtener información sobre las versiones de [[:File:Test.jpg]] a partir de 2008.",
        "apihelp-query+images-description": "Devuelve todos los archivos contenidos en las páginas dadas.",
        "apihelp-query+images-param-limit": "Cuántos archivos se devolverán.",
        "apihelp-query+images-example-simple": "Obtener una lista de los archivos usados en la [[Main Page|Portada]].",
        "apihelp-query+info-example-protection": "Obtén información general y protección acerca de la página <kbd>Main Page</kbd>.",
        "apihelp-query+iwbacklinks-param-limit": "Cuántas páginas se devolverán.",
        "apihelp-query+iwbacklinks-param-prop": "Qué propiedades se obtendrán:",
+       "apihelp-query+iwbacklinks-paramvalue-prop-iwtitle": "Añade el título del interwiki.",
        "apihelp-query+iwbacklinks-example-simple": "Obtener las páginas enlazadas a [[wikibooks:Test]]",
+       "apihelp-query+iwlinks-description": "Devuelve todos los enlaces interwiki de las páginas dadas.",
        "apihelp-query+iwlinks-param-prop": "Qué propiedades adicionales obtener para cada enlace interlingüe:",
        "apihelp-query+iwlinks-paramvalue-prop-url": "Añade el URL completo.",
+       "apihelp-query+iwlinks-param-limit": "Cuántos enlaces interwiki se desea devolver.",
+       "apihelp-query+iwlinks-param-prefix": "Devolver únicamente enlaces interwiki con este prefijo.",
        "apihelp-query+langbacklinks-param-lang": "Idioma del enlace de idioma.",
        "apihelp-query+langbacklinks-param-limit": "Cuántas páginas en total se devolverán.",
        "apihelp-query+langbacklinks-param-prop": "Qué propiedades se obtendrán:",
        "apihelp-query+langbacklinks-paramvalue-prop-lltitle": "Añade el título del enlace de idioma.",
        "apihelp-query+langbacklinks-example-simple": "Obtener las páginas enlazadas a [[:fr:Test]]",
        "apihelp-query+langbacklinks-example-generator": "Obtener información acerca de las páginas enlazadas a [[:fr:Test]].",
+       "apihelp-query+langlinks-param-url": "Obtener la URL completa o no (no se puede usar con <var>$1prop</var>).",
        "apihelp-query+langlinks-param-prop": "Qué propiedades adicionales obtener para cada enlace interlingüe:",
        "apihelp-query+langlinks-paramvalue-prop-url": "Añade el URL completo.",
        "apihelp-query+langlinks-paramvalue-prop-autonym": "Añade el nombre del idioma nativo.",
        "apihelp-query+recentchanges-param-excludeuser": "No listar cambios de este usuario.",
        "apihelp-query+recentchanges-param-tag": "Listar solo los cambios con esta etiqueta.",
        "apihelp-query+recentchanges-param-prop": "Incluir piezas adicionales de información:",
+       "apihelp-query+recentchanges-paramvalue-prop-comment": "Añade el comentario de la edición.",
        "apihelp-query+recentchanges-paramvalue-prop-parsedcomment": "Añade el comentario analizado para la edición.",
        "apihelp-query+recentchanges-paramvalue-prop-flags": "Añade marcas para la edición.",
+       "apihelp-query+recentchanges-paramvalue-prop-timestamp": "Añade la marca de tiempo de la edición.",
+       "apihelp-query+recentchanges-paramvalue-prop-title": "Añade el título de la página de la edición.",
+       "apihelp-query+recentchanges-paramvalue-prop-ids": "Añade los códigos ID de la página, de los cambios recientes y de las revisiones antigua y nueva.",
+       "apihelp-query+recentchanges-paramvalue-prop-sizes": "Añade la longitud antigua y la longitud nueva de la página en bytes.",
+       "apihelp-query+recentchanges-paramvalue-prop-redirect": "Etiqueta la edición si la página es una redirección.",
        "apihelp-query+recentchanges-paramvalue-prop-patrolled": "Etiqueta ediciones verificables como verificadas o no verificadas.",
        "apihelp-query+recentchanges-param-token": "Usa <kbd>[[Special:ApiHelp/query+tokens|action=query&meta=tokens]]</kbd> en su lugar.",
        "apihelp-query+recentchanges-param-limit": "Cuántos cambios en total se devolverán.",
        "apihelp-query+redirects-paramvalue-prop-pageid": "Identificador de página de cada redirección.",
        "apihelp-query+redirects-paramvalue-prop-title": "Título de cada redirección.",
        "apihelp-query+redirects-paramvalue-prop-fragment": "Fragmento de cada redirección, si los hubiere.",
+       "apihelp-query+redirects-param-namespace": "Incluir solo páginas de estos espacios de nombres.",
        "apihelp-query+redirects-param-limit": "Cuántas redirecciones se devolverán.",
        "apihelp-query+redirects-example-simple": "Mostrar una lista de las redirecciones a la [[Main Page|Portada]]",
+       "apihelp-query+redirects-example-generator": "Obtener información sobre todas las redirecciones a la [[Main Page|Portada]]",
+       "apihelp-query+revisions-param-end": "Enumerar hasta esta marca de tiempo.",
+       "apihelp-query+revisions-param-user": "Incluir solo las revisiones realizadas por el usuario.",
+       "apihelp-query+revisions-param-excludeuser": "Excluir las revisiones realizadas por el usuario.",
        "apihelp-query+revisions-example-last5": "Mostrar las últimas 5 revisiones de la <kbd>Main Page</kbd>.",
        "apihelp-query+revisions+base-param-prop": "Las propiedades que se obtendrán para cada revisión:",
        "apihelp-query+revisions+base-paramvalue-prop-ids": "El identificador de la revisión.",
index e5b6d05..790a073 100644 (file)
@@ -115,11 +115,15 @@ class DBConnRef implements IDatabase {
                return $this->__call( __FUNCTION__, func_get_args() );
        }
 
-       public function setFlag( $flag ) {
+       public function setFlag( $flag, $remember = self::REMEMBER_NOTHING ) {
                return $this->__call( __FUNCTION__, func_get_args() );
        }
 
-       public function clearFlag( $flag ) {
+       public function clearFlag( $flag, $remember = self::REMEMBER_NOTHING ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function restoreFlags( $state = self::RESTORE_PRIOR ) {
                return $this->__call( __FUNCTION__, func_get_args() );
        }
 
index 6ddc9f7..59c1207 100644 (file)
@@ -198,6 +198,9 @@ abstract class DatabaseBase implements IDatabase {
        /** @var float UNIX timestamp */
        protected $lastPing = 0.0;
 
+       /** @var int[] Prior mFlags values */
+       private $priorFlags = [];
+
        /** @var TransactionProfiler */
        protected $trxProfiler;
 
@@ -430,14 +433,33 @@ abstract class DatabaseBase implements IDatabase {
                return $this->mOpened;
        }
 
-       public function setFlag( $flag ) {
+       public function setFlag( $flag, $remember = self::REMEMBER_NOTHING ) {
+               if ( $remember === self::REMEMBER_PRIOR ) {
+                       array_push( $this->priorFlags, $this->mFlags );
+               }
                $this->mFlags |= $flag;
        }
 
-       public function clearFlag( $flag ) {
+       public function clearFlag( $flag, $remember = self::REMEMBER_NOTHING ) {
+               if ( $remember === self::REMEMBER_PRIOR ) {
+                       array_push( $this->priorFlags, $this->mFlags );
+               }
                $this->mFlags &= ~$flag;
        }
 
+       public function restoreFlags( $state = self::RESTORE_PRIOR ) {
+               if ( !$this->priorFlags ) {
+                       return;
+               }
+
+               if ( $state === self::RESTORE_INITIAL ) {
+                       $this->mFlags = reset( $this->priorFlags );
+                       $this->priorFlags = [];
+               } else {
+                       $this->mFlags = array_pop( $this->priorFlags );
+               }
+       }
+
        public function getFlag( $flag ) {
                return !!( $this->mFlags & $flag );
        }
index fa3756c..93814d2 100644 (file)
@@ -38,7 +38,14 @@ abstract class DatabaseMysqlBase extends Database {
        protected $lagDetectionOptions = [];
        /** @var bool bool Whether to use GTID methods */
        protected $useGTIDs = false;
-
+       /** @var string|null */
+       protected $sslKeyPath;
+       /** @var string|null */
+       protected $sslCertPath;
+       /** @var string|null */
+       protected $sslCAPath;
+       /** @var string[]|null */
+       protected $sslCiphers;
        /** @var string|null */
        private $serverVersion = null;
 
@@ -53,6 +60,10 @@ abstract class DatabaseMysqlBase extends Database {
         *       ID of this server's master will be used. Set the "conds" field to
         *       override the query conditions, e.g. ['shard' => 's1'].
         *   - useGTIDs : use GTID methods like MASTER_GTID_WAIT() when possible.
+        *   - sslKeyPath : path to key file [default: null]
+        *   - sslCertPath : path to certificate file [default: null]
+        *   - sslCAPath : parth to certificate authority PEM files [default: null]
+        *   - sslCiphers : array list of allowable ciphers [default: null]
         * @param array $params
         */
        function __construct( array $params ) {
@@ -65,6 +76,12 @@ abstract class DatabaseMysqlBase extends Database {
                        ? $params['lagDetectionOptions']
                        : [];
                $this->useGTIDs = !empty( $params['useGTIDs' ] );
+               foreach ( [ 'KeyPath', 'CertPath', 'CAPath', 'Ciphers' ] as $name ) {
+                       $var = "ssl{$name}";
+                       if ( isset( $params[$var] ) ) {
+                               $this->$var = $params[$var];
+                       }
+               }
        }
 
        /**
index cb580cc..e468601 100644 (file)
@@ -81,9 +81,18 @@ class DatabaseMysqli extends DatabaseMysqlBase {
                        $socket = $hostAndSocket[1];
                }
 
+               $mysqli = mysqli_init();
+
                $connFlags = 0;
                if ( $this->mFlags & DBO_SSL ) {
                        $connFlags |= MYSQLI_CLIENT_SSL;
+                       $mysqli->ssl_set(
+                               $this->sslKeyPath,
+                               $this->sslCertPath,
+                               null,
+                               $this->sslCAPath,
+                               $this->sslCiphers
+                       );
                }
                if ( $this->mFlags & DBO_COMPRESS ) {
                        $connFlags |= MYSQLI_CLIENT_COMPRESS;
@@ -92,7 +101,6 @@ class DatabaseMysqli extends DatabaseMysqlBase {
                        $realServer = 'p:' . $realServer;
                }
 
-               $mysqli = mysqli_init();
                if ( $wgDBmysql5 ) {
                        // Tell the server we're communicating with it in UTF-8.
                        // This may engage various charset conversions.
index 25100db..1aa931e 100644 (file)
@@ -50,6 +50,15 @@ interface IDatabase {
        /** @var string Transaction operation comes from the database class internally */
        const FLUSHING_INTERNAL = 'flush';
 
+       /** @var string No not remember the prior flags */
+       const REMEMBER_NOTHING = '';
+       /** @var string Remember the prior flags */
+       const REMEMBER_PRIOR = 'remember';
+       /** @var string Restore to the prior flag state */
+       const RESTORE_PRIOR = 'prior';
+       /** @var string Restore to the initial flag state */
+       const RESTORE_INITIAL = 'initial';
+
        /**
         * A string describing the current software version, and possibly
         * other details in a user-friendly way. Will be listed on Special:Version, etc.
@@ -230,8 +239,9 @@ interface IDatabase {
         *   - DBO_DEFAULT: automatically sets DBO_TRX if not in command line mode
         *       and removes it in command line mode
         *   - DBO_PERSISTENT: use persistant database connection
+        * @param string $remember IDatabase::REMEMBER_* constant [default: REMEMBER_NOTHING]
         */
-       public function setFlag( $flag );
+       public function setFlag( $flag, $remember = self::REMEMBER_NOTHING );
 
        /**
         * Clear a flag for this connection
@@ -243,8 +253,17 @@ interface IDatabase {
         *   - DBO_DEFAULT: automatically sets DBO_TRX if not in command line mode
         *       and removes it in command line mode
         *   - DBO_PERSISTENT: use persistant database connection
+        * @param string $remember IDatabase::REMEMBER_* constant [default: REMEMBER_NOTHING]
+        */
+       public function clearFlag( $flag, $remember = self::REMEMBER_NOTHING );
+
+       /**
+        * Restore the flags to their prior state before the last setFlag/clearFlag call
+        *
+        * @param string $state IDatabase::RESTORE_* constant. [default: RESTORE_PRIOR]
+        * @since 1.28
         */
-       public function clearFlag( $flag );
+       public function restoreFlags( $state = self::RESTORE_PRIOR );
 
        /**
         * Returns a boolean whether the flag $flag is set for this connection
index b320544..f560cf1 100644 (file)
@@ -216,6 +216,22 @@ abstract class LBFactory implements DestructibleService {
                );
        }
 
+       /**
+        * Flush any master transaction snapshots and set DBO_TRX (if DBO_DEFAULT is set)
+        *
+        * The DBO_TRX setting will be reverted to the default in each of these methods:
+        *   - commitMasterChanges()
+        *   - rollbackMasterChanges()
+        *   - commitAll()
+        * This allows for custom transaction rounds from any outer transaction scope.
+        *
+        * @param string $fname
+        * @since 1.28
+        */
+       public function beginMasterChanges( $fname = __METHOD__ ) {
+               $this->forEachLBCallMethod( 'beginMasterChanges', [ $fname ] );
+       }
+
        /**
         * Commit on all connections. Done for two reasons:
         * 1. To commit changes to the masters.
index 13a0879..65cd3b3 100644 (file)
@@ -1062,7 +1062,7 @@ class LoadBalancer {
         */
        public function commitAll( $fname = __METHOD__ ) {
                $this->forEachOpenConnection( function ( DatabaseBase $conn ) use ( $fname ) {
-                       $conn->commit( $fname, IDatabase::FLUSHING_ALL_PEERS );
+                       $conn->commit( $fname, $conn::FLUSHING_ALL_PEERS );
                } );
        }
 
@@ -1119,6 +1119,42 @@ class LoadBalancer {
                } );
        }
 
+       /**
+        * Flush any master transaction snapshots and set DBO_TRX (if DBO_DEFAULT is set)
+        *
+        * The DBO_TRX setting will be reverted to the default in each of these methods:
+        *   - commitMasterChanges()
+        *   - rollbackMasterChanges()
+        *   - commitAll()
+        * This allows for custom transaction rounds from any outer transaction scope.
+        *
+        * @param string $fname
+        * @since 1.28
+        */
+       public function beginMasterChanges( $fname = __METHOD__ ) {
+               $this->forEachOpenMasterConnection( function ( DatabaseBase $conn ) use ( $fname ) {
+                       if ( $conn->writesOrCallbacksPending() ) {
+                               throw new DBTransactionError(
+                                       $conn,
+                                       "Transaction with pending writes still active."
+                               );
+                       } elseif ( $conn->trxLevel() ) {
+                               $conn->commit( $fname, $conn::FLUSHING_ALL_PEERS );
+                       }
+                       if ( $conn->getFlag( DBO_DEFAULT ) ) {
+                               // DBO_TRX is controlled entirely by CLI mode presence with DBO_DEFAULT.
+                               // Force DBO_TRX even in CLI mode since a commit round is expected soon.
+                               $conn->setFlag( DBO_TRX, $conn::REMEMBER_PRIOR );
+                               $conn->onTransactionResolution( function () use ( $conn ) {
+                                       $conn->restoreFlags( $conn::RESTORE_PRIOR );
+                               } );
+                       } else {
+                               // Config has explicitly requested DBO_TRX be either on or off; respect that.
+                               // This is useful for things like blob stores which use auto-commit mode.
+                       }
+               } );
+       }
+
        /**
         * Issue COMMIT on all master connections where writes where done
         * @param string $fname Caller name
@@ -1126,7 +1162,7 @@ class LoadBalancer {
        public function commitMasterChanges( $fname = __METHOD__ ) {
                $this->forEachOpenMasterConnection( function ( DatabaseBase $conn ) use ( $fname ) {
                        if ( $conn->writesOrCallbacksPending() ) {
-                               $conn->commit( $fname, IDatabase::FLUSHING_ALL_PEERS );
+                               $conn->commit( $fname, $conn::FLUSHING_ALL_PEERS );
                        }
                } );
        }
@@ -1138,10 +1174,10 @@ class LoadBalancer {
         */
        public function runMasterPostCommitCallbacks() {
                $e = null; // first exception
-               $this->forEachOpenMasterConnection( function ( DatabaseBase $db ) use ( &$e ) {
-                       $db->setPostCommitCallbackSupression( false );
+               $this->forEachOpenMasterConnection( function ( DatabaseBase $conn ) use ( &$e ) {
+                       $conn->setPostCommitCallbackSupression( false );
                        try {
-                               $db->runOnTransactionIdleCallbacks( IDatabase::TRIGGER_COMMIT );
+                               $conn->runOnTransactionIdleCallbacks( $conn::TRIGGER_COMMIT );
                        } catch ( Exception $ex ) {
                                $e = $e ?: $ex;
                        }
@@ -1168,7 +1204,7 @@ class LoadBalancer {
                        foreach ( $conns2[$masterIndex] as $conn ) {
                                if ( $conn->trxLevel() && $conn->writesOrCallbacksPending() ) {
                                        try {
-                                               $conn->rollback( $fname, IDatabase::FLUSHING_ALL_PEERS );
+                                               $conn->rollback( $fname, $conn::FLUSHING_ALL_PEERS );
                                        } catch ( DBError $e ) {
                                                MWExceptionHandler::logException( $e );
                                                $failedServers[] = $conn->getServer();
index b8e2726..30aae15 100644 (file)
@@ -122,6 +122,9 @@ class SiteStatsUpdate implements DeferrableUpdate {
                        // Commit the updates and unlock the table
                        $dbw->unlock( $lockKey, __METHOD__ );
                }
+
+               // Invalid cache used by parser functions
+               SiteStats::unload();
        }
 
        /**
@@ -152,6 +155,9 @@ class SiteStatsUpdate implements DeferrableUpdate {
                        __METHOD__
                );
 
+               // Invalid cache used by parser functions
+               SiteStats::unload();
+
                return $activeUsers;
        }
 
index b1c3d48..09f3a40 100644 (file)
@@ -30,7 +30,8 @@
                        "Matiia",
                        "AlvaroMolina",
                        "Indiralena",
-                       "Peter Bowman"
+                       "Peter Bowman",
+                       "Dgstranz"
                ]
        },
        "config-desc": "El instalador de MediaWiki",
        "config-subscribe": "Suscribirse a la [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce lista de correo de anuncios de versiones].",
        "config-subscribe-help": "Esta es una lista de divulgación de bajo volumen para anuncios de lanzamiento de versiones nuevas, incluyendo anuncios de seguridad importantes.\nTe recomendamos suscribirte y actualizar tu instalación MediaWiki cada vez que se lance una nueva versión.",
        "config-subscribe-noemail": "Has intentado suscribirte a la lista de correo de anuncios de nuevos lanzamientos sin proporcionar una dirección de correo electrónico.\nProporciona una dirección de correo electrónico si quieres suscribirte a la lista de correo.",
+       "config-pingback": "Compartir datos sobre esta instalación con los desarrolladores de MediaWiki.",
+       "config-pingback-help": "Si seleccionas esta opción, MediaWiki enviará periódicamente a https://www.mediawiki.org datos básicos sobre esta instancia de MediaWiki. Se trata de datos tales como el tipo de sistema, la versión de PHP y la base de datos elegida. La Fundación Wikimedia comparte estos datos con los desarrolladores de MediaWiki para ayudar a guiar el desarrollo futuro. Se enviarán los siguientes datos para tu sistema:\n<pre>$1</pre>",
        "config-almost-done": "¡Ya casi has terminado!\nAhora puedes saltarte el resto de los pasos e instalar el wiki ya.",
        "config-optional-continue": "Hazme más preguntas.",
        "config-optional-skip": "Ya estoy aburrido, sólo instala el wiki.",
index 6396aaa..b3a97f7 100644 (file)
@@ -543,13 +543,8 @@ class Article implements Page {
                                }
                        }
 
-                       # Is it client cached?
-                       if ( $outputPage->checkLastModified( $timestamp ) ) {
-                               wfDebug( __METHOD__ . ": done 304\n" );
-
-                               return;
-                       # Try file cache
-                       } elseif ( $wgUseFileCache && $this->tryFileCache() ) {
+                       # Try to stream the output from file cache
+                       if ( $wgUseFileCache && $this->tryFileCache() ) {
                                wfDebug( __METHOD__ . ": done file cache\n" );
                                # tell wgOut that output is taken care of
                                $outputPage->disable();
index 3851d15..4066501 100644 (file)
@@ -504,13 +504,13 @@ class WikiPage implements Page, IDBAccessObject {
 
        /**
         * Loads page_touched and returns a value indicating if it should be used
-        * @return bool True if not a redirect
+        * @return bool True if this page exists and is not a redirect
         */
        public function checkTouched() {
                if ( !$this->mDataLoaded ) {
                        $this->loadPageData();
                }
-               return !$this->mIsRedirect;
+               return ( $this->mId && !$this->mIsRedirect );
        }
 
        /**
index 6426fea..9ce2c5a 100644 (file)
@@ -56,7 +56,7 @@ class ResourceLoader implements LoggerAwareInterface {
        protected $moduleInfos = [];
 
        /** @var Config $config */
-       private $config;
+       protected $config;
 
        /**
         * Associative array mapping framework ids to a list of names of test suite modules
@@ -1333,10 +1333,10 @@ MESSAGE;
         *       Register sources with the given IDs and properties.
         *
         * @param string $id Source ID
-        * @param array $properties Source properties (see addSource())
+        * @param string $loadUrl load.php url
         * @return string
         */
-       public static function makeLoaderSourcesScript( $id, $properties = null ) {
+       public static function makeLoaderSourcesScript( $id, $loadUrl = null ) {
                if ( is_array( $id ) ) {
                        return Xml::encodeJsCall(
                                'mw.loader.addSource',
@@ -1346,7 +1346,7 @@ MESSAGE;
                } else {
                        return Xml::encodeJsCall(
                                'mw.loader.addSource',
-                               [ $id, $properties ],
+                               [ $id, $loadUrl ],
                                ResourceLoader::inDebugMode()
                        );
                }
index 68f0d00..3adf5a6 100644 (file)
@@ -554,38 +554,46 @@ abstract class AuthManagerSpecialPage extends SpecialPage {
        }
 
        /**
-        * Returns true if the form built from the given AuthenticationRequests has fields which take
-        * values. If all available providers use the redirect flow, the form might contain nothing
-        * but submit buttons, in which case we should not add an extra submit button which does nothing.
+        * Returns true if the form built from the given AuthenticationRequests needs a submit button.
+        * Providers using redirect flow (e.g. Google login) need their own submit buttons; if using
+        * one of those custom buttons is the only way to proceed, there is no point in displaying the
+        * default button which won't do anything useful.
         *
         * @param AuthenticationRequest[] $requests An array of AuthenticationRequests from which the
         *  form will be built
         * @return bool
         */
        protected function needsSubmitButton( array $requests ) {
+               $customSubmitButtonPresent = false;
+
+               // Secondary and preauth providers always need their data; they will not care what button
+               // is used, so they can be ignored. So can OPTIONAL buttons createdby primary providers;
+               // that's the point in being optional. Se we need to check whether all primary providers
+               // have their own buttons and whether there is at least one button present.
                foreach ( $requests as $req ) {
-                       if ( $req->required === AuthenticationRequest::PRIMARY_REQUIRED &&
-                               $this->doesRequestNeedsSubmitButton( $req )
-                       ) {
-                               return true;
+                       if ( $req->required === AuthenticationRequest::PRIMARY_REQUIRED ) {
+                               if ( $this->hasOwnSubmitButton( $req ) ) {
+                                       $customSubmitButtonPresent = true;
+                               } else {
+                                       return true;
+                               }
                        }
                }
-               return false;
+               return !$customSubmitButtonPresent;
        }
 
        /**
-        * Checks if the given AuthenticationRequest needs a submit button or not.
-        *
-        * @param AuthenticationRequest $req The request to check
+        * Checks whether the given AuthenticationRequest has its own submit button.
+        * @param AuthenticationRequest $req
         * @return bool
         */
-       protected function doesRequestNeedsSubmitButton( AuthenticationRequest $req ) {
+       protected function hasOwnSubmitButton( AuthenticationRequest $req ) {
                foreach ( $req->getFieldInfo() as $field => $info ) {
                        if ( $info['type'] === 'button' ) {
-                               return false;
+                               return true;
                        }
                }
-               return true;
+               return false;
        }
 
        /**
index 8a8fe7b..5e989ef 100644 (file)
        "linkaccounts-submit": "Benutzerkonten verknüpfen",
        "unlinkaccounts": "Benutzerkonten trennen",
        "unlinkaccounts-success": "Das Benutzerkonto wurde getrennt.",
-       "authenticationdatachange-ignored": "Die Änderung der Authentifizierungsdaten wurde nicht bearbeitet. Vielleicht wurde kein Anbieter konfiguriert?"
+       "authenticationdatachange-ignored": "Die Änderung der Authentifizierungsdaten wurde nicht bearbeitet. Vielleicht wurde kein Anbieter konfiguriert?",
+       "userjsispublic": "Bitte beachten: JavaScript-Unterseiten sollten keine vertraulichen Daten enthalten, da sie von anderen Benutzern eingesehen werden können.",
+       "usercssispublic": "Bitte beachten: CSS-Unterseiten sollten keine vertraulichen Daten enthalten, da sie von anderen Benutzern eingesehen werden können."
 }
index 608f042..a99ff64 100644 (file)
        "undo-summary": "Vırnayışê $1'i [[Special:Contributions/$2|$2i]] ([[User talk:$2|Werênayış]]) peyser gırot",
        "undo-summary-username-hidden": "Rewizyona veri $1'i hewada",
        "cantcreateaccount-text": "Hesabvıraştışê na IP adrese ('''$1''') terefê [[User:$3|$3]] kılit biyo.\n\nSebebo ke terefê $3 ra diyao ''$2''",
-       "viewpagelogs": "Qeydanê ena perer bıvin",
+       "viewpagelogs": "Qeydanê na pele bımocne",
        "nohistory": "Verê vurnayışanê na pele çıniyo.",
        "currentrev": "Çımraviyarnayışo rocane",
        "currentrev-asof": "$1 ra tepya mewcud weziyeta pela",
        "last": "peyên",
        "page_first": "verên",
        "page_last": "peyên",
-       "histlegend": "Ferqê weçinıtışi: Qutiya versiyonan qandé pêver sanayış işaret ke u dest be ''enter''i ya zi gocega cêrêne ro ne.<br />\nCedwel: <strong>({{int:ferq}})</strong> = ferqê versiyonê peyêni, <strong>({{int:peyên}})</strong> = ferqê versiyonê verêni, <strong>{{int:q}}</strong> = vurnayışo werdi.",
+       "histlegend": "Ferqê weçinıtışi: Qutiya versiyonan seba têversanayış işaret ke û dest be ''enter''i ya zi gocega cêrêne ro ne.<br />\nCedwel: <strong>({{int:ferq}})</strong> = ferqê verziyonê peyêni, <strong>({{int:peyên}})</strong> = ferqê versiyonê verêni, <strong>{{int:q}}</strong> = vurnayışo werdi.",
        "history-fieldset-title": "Tarixi bıvêne",
        "history-show-deleted": "Tenya esterıtey",
        "histfirst": "Verênêr",
        "lineno": "Xeta $1:",
        "compareselectedversions": "Rewizyonanê weçineyan pêver ke",
        "showhideselectedversions": "Revizyonanê weçinıtan bımocne/bınımne",
-       "editundo": "peyser bıya",
+       "editundo": "peyser biya",
        "diff-empty": "(Babetna niyo)",
        "diff-multi-sameuser": "(Terefê eyni karberi ra {{PLURAL:$1|yew revizyono miyanên nêmocno|$1 revizyonê miyanêni nêmocnê}})",
        "diff-multi-otherusers": "(Terefê {{PLURAL:$2|yew karberi|$2 karberan}} ra {{PLURAL:$1|yew revizyono miyanên nêmocno|$1 revizyonê miyanêni nêmocnê}})",
        "recentchanges-summary": "Wiki sero vurriyayışê peyêni asenê.",
        "recentchanges-noresult": "Goreyê kriteranê kıfşkerdeyan ra qet yew vurnayış nêvêniya.",
        "recentchanges-feed-description": "Ena feed dı vurnayişanê tewr peniyan teqip bık.",
-       "recentchanges-label-newpage": "Pera newiy vıraziya ya",
+       "recentchanges-label-newpage": "Enê vurnayışi ra yew pela newiye vıraziye",
        "recentchanges-label-minor": "Vurriyayışo werdiyo",
-       "recentchanges-label-bot": "Ena vırnayışa boti ya",
+       "recentchanges-label-bot": "Eno vurnayış terefê yew boti ra vıraziyo",
        "recentchanges-label-unpatrolled": "Eno vurnayış hewna dewriya nêbiyo",
-       "recentchanges-label-plusminus": "No nımre erca ebata perer asıneno (maypuşt)",
+       "recentchanges-label-plusminus": "Ebadê pele de bazê bayti de vayeyê cı",
        "recentchanges-legend-heading": "<strong>Kıtabekê Vurriyayışê peyêni:</strong>",
        "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} ([[Special:NewPages|Lista pelanê neweyan]] zi bıvêne)",
        "recentchanges-legend-plusminus": "''(±123)''",
        "recentchangeslinked-summary": "Lista cêrêne, pela bêlikerdiye rê (ya zi karberanê kategoriya bêlikerdiye rê) pelanê gırêdayoğan de lista de vurnayışê peyênana.\n[[Special:Watchlist|Lista şımaya seyrkedışi de]] peli be nuşteyo '''qolınd''' bêli kerdê.",
        "recentchangeslinked-page": "Nameyê pele:",
        "recentchangeslinked-to": "Heruna pela ke yena dayene, vurnayışanê pelanê ke daye ra gırêdayiyê inan bımocne",
-       "recentchanges-page-added-to-category": "[[:$1]] de kerd kategori miyan",
+       "recentchanges-page-added-to-category": "[[:$1]] kerd kategoriye miyan",
        "recentchanges-page-removed-from-category": "[[:$1]] kategoriye ra vet",
        "autochange-username": "MediaWiki vurnayışo otomatik",
        "upload": "Dosya bar ke",
index 6b1e366..61e06c0 100644 (file)
        "grant-viewdeleted": "नयाँ फाइलहरू अपलोड\nसम्पादन गर",
        "grant-viewmywatchlist": "आफनो अबलोकन सुची हेर",
        "newuserlogpage": "प्रयोगकर्ता श्रृजना लग",
+       "action-move": "ये पानालाई अर्खिठौर सार",
        "action-move-subpages": "यै पानाको रे यैका उपपानाको नाम बदल्न्या",
+       "action-move-rootuserpages": "मूल प्रयोगकर्ता पृष्ठहरू सार्ने",
+       "action-move-categorypages": "श्रेणी पृष्ठ सार",
+       "action-movefile": "ये फाईललाइ सार",
+       "action-upload": "ये फाइल अपलोड गर",
        "action-unwatchedpages": "कसैले ध्यान नराख्याका पाननको सूची हेद्या",
        "action-userrights-interwiki": "अन्य विकिका प्रयोगकर्तानको प्रयोगकर्ता अधिकार सम्पादन गद्या",
        "action-applychangetags": "तमरो परिवर्तनसँगै ट्यागहरू लागु गर्न्या",
index 3d1d039..7099422 100644 (file)
        "permissionserrors": "Error de permisos",
        "permissionserrorstext": "No tienes permiso para hacer eso, por {{PLURAL:$1|el siguiente motivo|los siguientes motivos}}:",
        "permissionserrorstext-withaction": "No tienes permiso para $2, por {{PLURAL:$1|el siguiente motivo|los siguientes motivos}}:",
-       "contentmodelediterror": "No puedes editar esta revisión porque su modelo de contenido es <code>$1</code>, la cual difiere del modelo actual de la página <code>$2</code>.",
+       "contentmodelediterror": "No puedes editar esta revisión porque su modelo de contenido es <code>$1</code>, que difiere del modelo actual de contenido de la página <code>$2</code>.",
        "recreate-moveddeleted-warn": "<strong>Atención: estás volviendo a crear una página que ha sido borrada anteriormente.</strong>\n\nPiensa si es adecuado continuar editando la página.\nA continuación, se proporciona el registro de borrado y traslados de esta página para más información:",
        "moveddeleted-notice": "Esta página ha sido borrada.\nA continuación, se proporciona el registro de borrados y traslados de la página para más información.",
-       "moveddeleted-notice-recent": "Esta página se ha eliminado recientemente (dentro de las últimas 24 horas).\nEl registro de eliminación y traslado de la página se muestran a continuación como referencia.",
+       "moveddeleted-notice-recent": "Esta página se ha eliminado recientemente (durante las últimas 24 horas).\nEl registro de eliminación y traslado de la página se muestran a continuación como referencia.",
        "log-fulllog": "Ver el registro completo",
        "edit-hook-aborted": "Una extensión ha evitado la edición.\nNo hay explicación disponible.",
        "edit-gone-missing": "No se ha podido actualizar la página.\nParece haber sido borrada.",
        "content-json-empty-object": "Objeto vacío",
        "content-json-empty-array": "Matriz vacía",
        "deprecated-self-close-category": "Páginas que utilizan etiquetas HTML autocerradas no válidas",
-       "deprecated-self-close-category-desc": "Esta página contiene etiquetas HTML de auto-cierre invalidas, tales como <code>&lt;b/></code> o <code>&lt;span/></code>. El comportamiento de estas en  cambiará pronto para ser coherente con la especificación de HTML5, por lo que su utilización en wikitext está en desuso.",
+       "deprecated-self-close-category-desc": "Esta página contiene etiquetas HTML de autocierre inválidas, tales como <code>&lt;b/></code> o <code>&lt;span/></code>. El comportamiento de estas cambiará pronto para ser coherente con la especificación de HTML5, por lo que su utilización en el wikitexto está en desuso.",
        "duplicate-args-warning": "<strong>Aviso:</strong> [[:$1]] llama a [[:$2]] con más de un valor para el parámetro «$3». Se usará solo el último valor proporcionado.",
        "duplicate-args-category": "Páginas que usan argumentos duplicados en invocaciones de plantillas",
        "duplicate-args-category-desc": "La página contiene invocaciones de plantillas que utilizan argumentos duplicados, como <code><nowiki>{{foo|bar=1|bar=2}}</nowiki></code> o <code><nowiki>{{foo|bar|1=baz}}</nowiki></code>.",
        "expensive-parserfunction-warning": "<strong>Advertencia:</strong> esta página contiene demasiadas llamadas a funciones sintácticas costosas.\n\nTiene {{PLURAL:$1|una llamada|$1 llamadas}}, pero debería tener menos de {{PLURAL:$2|una|$2}}.",
-       "expensive-parserfunction-category": "Páginas con llamadas a funciones sintácticas demasiado costosas",
+       "expensive-parserfunction-category": "Páginas con demasiadas llamadas a funciones sintácticas costosas",
        "post-expand-template-inclusion-warning": "<strong>Aviso:</strong> El tamaño de las plantillas incluidas es muy grande.\nAlgunas de ellas no se incluirán.",
        "post-expand-template-inclusion-category": "Páginas con sobrecarga de plantillas",
        "post-expand-template-argument-warning": "Aviso: Esta página contiene al menos un parámetro de plantilla con un tamaño de expansión demasiado grande.\nSe han descartado esos parámetros.",
        "grant-group-high-volume": "Realizar actividad de volumen alto",
        "grant-group-customization": "Personalización y preferencias",
        "grant-group-administration": "Realizar acciones administrativas",
+       "grant-group-private-information": "Acceder a información privada sobre ti",
        "grant-group-other": "Actividades diversas",
        "grant-blockusers": "Bloquear y desbloquear usuarios",
        "grant-createaccount": "Crear cuentas",
        "grant-highvolume": "Gran cantidad de ediciones",
        "grant-oversight": "Ocultar a los usuarios y suprimir las revisiones",
        "grant-patrol": "Verificar cambios a páginas",
+       "grant-privateinfo": "Acceder a información privada",
        "grant-protect": "Proteger y desproteger páginas",
        "grant-rollback": "Revertir cambios a páginas",
        "grant-sendemail": "Enviar un correo electrónico a otros usuarios",
        "linkaccounts-submit": "Vincular cuentas",
        "unlinkaccounts": "Desvincular cuentas",
        "unlinkaccounts-success": "Se ha desvinculado la cuenta.",
-       "authenticationdatachange-ignored": "El cambio den los datos de autentificacion no fue realizado. ¿Tal vez, no se configuró un proveedor?"
+       "authenticationdatachange-ignored": "El cambio den los datos de autentificacion no fue realizado. ¿Tal vez, no se configuró un proveedor?",
+       "userjsispublic": "Recuerda: Las subpáginas JavaScript no deberían contener datos confidenciales, pues otros usuarios los pueden ver.",
+       "usercssispublic": "Recuerda: Las subpáginas CSS no deberían contener datos confidenciales, pues otros usuarios los pueden ver."
 }
index 5710a6f..c851a85 100644 (file)
        "linkaccounts-submit": "Lier les comptes",
        "unlinkaccounts": "Dissocier les comptes",
        "unlinkaccounts-success": "Le compte a été dissocié.",
-       "authenticationdatachange-ignored": "Les modifications de données d’authentification n’ont pas été gérées. Peut-être aucun fournisseur n’a-t-il été configuré ?"
+       "authenticationdatachange-ignored": "Les modifications de données d’authentification n’ont pas été gérées. Peut-être aucun fournisseur n’a-t-il été configuré ?",
+       "userjsispublic": "Veuillez noter: les sous-pages JavaScript ne doivent pas contenir de données confidentielles parce qu'elles sont visibles des autres utilisateurs.",
+       "usercssispublic": "Veuillez noter: les sous-pages CSS ne doivent pas contenir de données confidentielles parce qu'elles sont visibles des autres utilisateurs."
 }
index 6059e1a..282444d 100644 (file)
@@ -15,7 +15,8 @@
                        "តឹក ប៊ុនលី",
                        "វ័ណថារិទ្ធ",
                        "아라",
-                       "Macofe"
+                       "Macofe",
+                       "Dcljr"
                ]
        },
        "tog-underline": "គូសបន្ទាត់ក្រោម​តំណភ្ជាប់៖",
        "passwordreset-emailtext-user": "អ្នកប្រើប្រាស់ $1 នៅក្នុង {{SITENAME}} បានស្នើសុំស្ដារពាក្យសម្ងាត់របស់អ្នកនៅក្នុង {{SITENAME}} ($4)។\n {{PLURAL:$3|គណនី|គណនី}}អ្នកប្រើប្រាស់ដូចតទៅនេះមានជាប់ទាក់ទិននឹងអាសយដ្ឋានអ៊ីមែលនេះ៖\n\n$2\n\n{{PLURAL:$3|ពាក្យសម្ងាត់បណ្ដោះអាសន្ននេះ|ពាក្យសម្ងាត់បណ្ដោះអាសន្នទាំងនេះ}} និងហួសសុពលភាពក្នុងរយៈពេល {{PLURAL:$5|មួយថ្ងៃ|$5 ថ្ងៃ}}។\nយកល្អអ្នកគួរតែកត់ឈ្មោះចូលរួចជ្រើសរើសពាក្យសម្ងាត់ថ្មីមួយ។ ប្រសិនបើមាននរណាម្នាក់ផ្សេងធ្វើការស្នើសុំនេះ \nឬប្រសិនបើអ្នកនឹកឃើញពាក្យសម្ងាត់ដើមរបស់អ្នក ហើយអ្នកមិនប្រាថ្នាផ្លាស់ប្ដូរវាទៀតទេនោះ អ្នកគ្រាន់តែ\nបំភ្លេចអំពីសារមួយនេះ ហើយបន្តប្រើប្រាស់ពាក្យសម្ងាត់ចាស់របស់អ្នកទៅបានហើយ។",
        "passwordreset-emailelement": "អត្តនាម៖ \n$1\n\nពាក្យសម្ងាត់បណ្ដោះអាសន្ន៖ \n$2",
        "passwordreset-emailsentemail": "បើសិនជានេះអាសយដ្ឋានអ៊ីមែលដែលត្រូវបានចុះឈ្មោះសម្រាប់គណនីរបស់អ្នក នោះអ៊ីមែលសម្រាប់ស្ដារពាក្យសម្ងាត់មួយនឹងត្រូវបានផ្ញើទៅ។",
-       "passwordreset-emailsent-capture": "អ៊ីមែលស្ដារពាក្យសម្ងាត់មួយដូចបង្ហាញខាងក្រោមត្រូវបានផ្ញើទៅហើយ។",
-       "passwordreset-emailerror-capture": "អ៊ីមែលស្ដារពាក្យសម្ងាត់មួយដូចបង្ហាញខាងក្រោមត្រូវបានបង្កើតហើយ ប៉ុន្តែការផ្ញើទៅកាន់ {{GENDER:$2|អ្នកប្រើប្រាស់}}មិនបានសំរេចទេ៖ $1",
        "changeemail": "ផ្លាស់ប្ដូរឬលុបអាសយដ្ឋានអ៊ីមែល",
        "changeemail-header": "សូមបំពេញសំណុំបែបបទនេះដើម្បីផ្លាស់ប្ដូរអាសយដ្ឋានអ៊ីមែល។ បើសិនជាអ្នកចង់លុបការតភ្ជាប់អាសយដ្ឋានអ៊ីមែលពីគណនីរបស់អ្នក សូមដាក់ប្រឡោះអាសយដ្ឋានថ្មីអោយនៅទំនេរពេលសម្រេចដាក់សំណុំបែបបទ។",
        "changeemail-no-info": "អ្នក​ចាំបាច់​ត្រូវតែ​កត់ឈ្មោះចូល ដើម្បី​ចូលទៅកាន់​ទំព័រ​នេះ​ដោយផ្ទាល់​។",
        "undo-norev": "កំណែ​មិន​អាច​មិន​ធ្វើ​ឡើង​វិញ​បាន​ទេ​ ពីព្រោះ​វា​មិន​មាន​ឬ​ត្រូវ​បាន​លុប​បាត់​ទៅ​ហើយ​។",
        "undo-summary": "មិន​ធ្វើ​វិញ​នូវ​កំណែ​ប្រែ $1 ដោយ​ [[Special:Contributions/$2|$2]] ([[User talk:$2|ការពិភាក្សា​]])",
        "undo-summary-username-hidden": "មិន​ធ្វើ​វិញ​នូវ​កំណែ​ប្រែ $1 ដោយអ្នកប្រើប្រាស់លាក់ឈ្មោះ",
-       "cantcreateaccounttitle": "មិនអាចបង្កើតគណនីបានទេ",
        "cantcreateaccount-text": "ការបង្កើតគណនីពីអាសយដ្ឋាន IP ('''$1''') នេះ ត្រូវបានរារាំងដោយ [[User:$3|$3]]។\n\nហេតុផលដែលត្រូវលើកឡើងដោយ $3 គឺ ''$2''",
        "viewpagelogs": "មើលកំណត់ហេតុសម្រាប់ទំព័រនេះ",
        "nohistory": "មិនមានប្រវត្តិកំណែប្រែ​ចំពោះទំព័រនេះ។",
        "enotif_subject_moved": "ទំព័រ {{SITENAME}} មានចំណងជើងថា $1 ត្រូវបាន {{GENDER:$2|ប្ដូរទីតាំង}} ដោយ $2",
        "enotif_subject_restored": "ទំព័រ {{SITENAME}} មានចំណងជើងថា $1 ត្រូវបាន {{GENDER:$2|ស្ដារឡើងវិញ}} ដោយ $2",
        "enotif_subject_changed": "ទំព័រ {{SITENAME}} មានចំណងជើងថា $1 ត្រូវបាន {{GENDER:$2|ផ្លាស់ប្ដូរ}} ដោយ $2",
-       "enotif_body_intro_deleted": "ទំព័រ {{SITENAME}} មានចំណងជើងថា $1 ត្រូវបាន {{GENDER:$2|លុបចោល}} នៅ $PAGEEDITDATE ដោយ $2។ សូមអាន $3។",
+       "enotif_body_intro_deleted": "ទំព័រ {{SITENAME}} មានចំណងជើងថា $1 ត្រូវបាន {{GENDER:$2|លុបចោល}} នៅ $PAGEEDITDATE ដោយ $2 ។ សូមអាន $3 ។",
        "enotif_body_intro_created": "ទំព័រ {{SITENAME}} មានចំណងជើងថា $1 ត្រូវបាន {{GENDER:$2|បង្កើត}} នៅ $PAGEEDITDATE ដោយ $2។ សូមអាន $3 សម្រាប់កំណែបច្ចុប្បន្ន។",
        "enotif_body_intro_moved": "ទំព័រ {{SITENAME}} មានចំណងជើងថា $1 ត្រូវបាន {{GENDER:$2|ប្ដូរទីតាំង}} នៅ $PAGEEDITDATE ដោយ $2។ សូមអាន $3 សម្រាប់កំណែបច្ចុប្បន្ន។",
        "enotif_body_intro_restored": "ទំព័រ {{SITENAME}} មានចំណងជើងថា $1 ត្រូវបាន {{GENDER:$2|ស្ដារឡើងវិញ}} នៅ $PAGEEDITDATE ដោយ $2។ សូមអាន $3 សម្រាប់កំណែបច្ចុប្បន្ន។",
        "special-characters-title-minus": "សញ្ញាដក",
        "mw-widgets-dateinput-no-date": "គ្មានកាលបរិច្ឆេទត្រូវបានជ្រើសរើស",
        "mw-widgets-titleinput-description-new-page": "ទំព័រមិនទាន់មាននៅឡើយទេ",
-       "mw-widgets-titleinput-description-redirect": "បញ្ជូនបន្តទៅ $1",
-       "api-error-blacklisted": "សូមជ្រើសរើសឈ្មោះផ្សេងដែលក្បោះក្បាយជាង។"
+       "mw-widgets-titleinput-description-redirect": "បញ្ជូនបន្តទៅ $1"
 }
index fd27331..688ee15 100644 (file)
        "linkaccounts-submit": "계정 연결",
        "unlinkaccounts": "계정 연결 해제",
        "unlinkaccounts-success": "계정의 연결이 해제되었습니다.",
-       "authenticationdatachange-ignored": "인증 데이터 변경을 처리하지 못했습니다. 제공자를 설정하지 않으셨습니까?"
+       "authenticationdatachange-ignored": "인증 데이터 변경을 처리하지 못했습니다. 제공자를 설정하지 않으셨습니까?",
+       "userjsispublic": "주목해 주십시오: 자바스크립트의 하위 문서들은 다른 사용자들이 볼 수 있기 때문에 기밀 데이터를 포함해서는 안 됩니다.",
+       "usercssispublic": "주목해 주십시오: CSS의 하위 문서들은 다른 사용자들이 볼 수 있기 때문에 기밀 데이터를 포함해서는 안 됩니다."
 }
index fe9097d..33ea41a 100644 (file)
        "linkaccounts-submit": "Поврзи сметки",
        "unlinkaccounts": "Одврзи сметки",
        "unlinkaccounts-success": "Сметката е одврзана.",
-       "authenticationdatachange-ignored": "Промената на податоците во заверката не е обработена. Можеби не е поставен услужник?"
+       "authenticationdatachange-ignored": "Промената на податоците во заверката не е обработена. Можеби не е поставен услужник?",
+       "userjsispublic": "Напомена: потстраниците со JavaScript не треба да содржат дсоверливи податоци бидејќи истите се видливи и за други корисници.",
+       "usercssispublic": "Напомена: потстраниците со CSS не треба да содржат дсоверливи податоци бидејќи истите се видливи и за други корисници."
 }
index 33fc57f..e329be0 100644 (file)
        "minoredit": "Aquò es un cambiament menor",
        "watchthis": "Seguir aquesta pagina",
        "savearticle": "Salvar",
+       "publishpage": "Publicar la pagina",
        "publishchanges": "Publicar las modificacions",
        "preview": "Previsualizar",
        "showpreview": "Previsualizar",
index 6a3d604..2c5041c 100644 (file)
        "linkaccounts-submit": "Text of the main submit button on [[Special:LinkAccounts]] (when there is one)",
        "unlinkaccounts": "Title of the special page [[Special:UnlinkAccounts]] which allows the user to remove linked remote accounts.",
        "unlinkaccounts-success": "Account unlinking form success message",
-       "authenticationdatachange-ignored": "Shown when authentication data change was unsuccessful due to configuration problems.\n\nCf. e.g. {{msg-mw|Passwordreset-ignored}}."
+       "authenticationdatachange-ignored": "Shown when authentication data change was unsuccessful due to configuration problems.\n\nCf. e.g. {{msg-mw|Passwordreset-ignored}}.",
+       "userjsispublic": "A reminder to users that Javascript subpages are not preferences but normal pages, and thus can be viewed by other users and the general public. This message is shown to a user whenever they are editing a subpage in their own user-space that ends in .js. See also {{msg-mw|usercssispublic}}.",
+       "usercssispublic": "A reminder to users that CSS subpages are not preferences but normal pages, and thus can be viewed by other users and the general public. This message is shown to a user whenever they are editing a subpage in their own user-space that ends in .css. See also {{msg-mw|userjsispublic}}"
 }
index 7ef457b..f1fad3c 100644 (file)
        "linkaccounts-submit": "Poveži račune",
        "unlinkaccounts": "Razveži račune",
        "unlinkaccounts-success": "Račun smo razvezali.",
-       "authenticationdatachange-ignored": "Sprememba overitvenih podatkov ni bila obdelana. Morda ni bil konfiguriran noben ponudnik?"
+       "authenticationdatachange-ignored": "Sprememba overitvenih podatkov ni bila obdelana. Morda ni bil konfiguriran noben ponudnik?",
+       "userjsispublic": "Pomnite: Podstrani JavaScript naj ne vsebujejo zaupnih podatkov, saj so vidne tudi drugim uporabnikom.",
+       "usercssispublic": "Pomnite: Podstrani CSS naj ne vsebujejo zaupnih podatkov, saj so vidne tudi drugim uporabnikom."
 }
index 3cea9f2..33c3522 100644 (file)
        "linkaccounts-submit": "Länka konton",
        "unlinkaccounts": "Avlänka konton",
        "unlinkaccounts-success": "Kontot avlänkades.",
-       "authenticationdatachange-ignored": "Ändringen av autentiseringsdata hanterades inte. Kanske ingen tillhandahållare har konfigurerats?"
+       "authenticationdatachange-ignored": "Ändringen av autentiseringsdata hanterades inte. Kanske ingen tillhandahållare har konfigurerats?",
+       "userjsispublic": "Observera: JavaScript-undersidor bör inte innehålla konfidentiella uppgifter eftersom de kan ses av andra användare.",
+       "usercssispublic": "Observera: CSS-undersidor bör inte innehålla konfidentiella uppgifter eftersom de kan ses av andra användare."
 }
index 4f78206..d9a5592 100644 (file)
        "filesource": "ಮೂಲ",
        "savefile": "ಕಡತನ್ ಒರಿಪಾಲೆ",
        "upload-source": "ಮೂಲ ಕಡತ",
+       "upload-options": "ಅಪ್ಲೋಡ್ ಆಯ್ಕೆಲು",
+       "watchthisupload": "ಈ ಪುಟೊನು ತೂಲೆ",
        "upload-file-error": "ಆ೦ತರಿಕ ದೋಷ",
+       "upload-dialog-title": "ಫೈಲ್ ಅಪ್ಲೋಡ್",
        "upload-dialog-button-cancel": "ವಜಾ ಮಲ್ಪುಲೆ",
        "upload-dialog-button-done": "ಆಂಡ್",
        "upload-dialog-button-save": "ಒರಿಪಾಲೆ",
        "listfiles_size": "ಗಾತ್ರೊ",
        "listfiles_description": "ವಿವರಣೆ",
        "listfiles_count": "ಆವೃತ್ತಿಲು",
+       "listfiles-latestversion": "ಪ್ರಸಕ್ತ ಆವೃತ್ತಿ",
        "listfiles-latestversion-yes": "ಅಂದ್",
        "listfiles-latestversion-no": "ಅತ್ತ್",
        "file-anchor-link": "ಫೈಲ್",
        "filehist-datetime": "ದಿನೊ/ಪೊರ್ತು",
        "filehist-thumb": "ಎಲ್ಯಚಿತ್ರೊ",
        "filehist-thumbtext": "$1ತ ಆವೃತ್ತಿದ ಎಲ್ಯಚಿತ್ರೊ",
+       "filehist-nothumb": "ಎಲ್ಯಚಿತ್ರೊ ಇಜ್ಜಿ",
        "filehist-user": "ಬಳಕೆದಾರೆರ್",
        "filehist-dimensions": "ಆಯಾಮೊಲು",
        "filehist-filesize": "ಫೈಲ್’ದ ಗಾತ್ರ",
        "upload-disallowed-here": "ಈರ್ ಈ ಫೈಲ್‍ನ್ ಕುಡೊರೊ ಬರೆವರೆ ಸಾದ್ಯೊ ಇದ್ದಿ.",
        "filerevert-comment": "ಕಾರಣ:",
        "filerevert-submit": "ದುಂಬುದ ಲೆಕ ಮಲ್ಪುಲೆ",
+       "filedelete-legend": "ಕಡತನ್ ಮಾಜಾಲೆ",
        "filedelete-comment": "ಕಾರಣ",
        "filedelete-submit": "ಮಾಜಾಲೆ",
        "filedelete-reason-otherlist": "ಬೇತೆ ಕಾರಣ",
        "download": "ಡೌನ್‍ಲೋಡ್",
        "randompage": "ಯಾದೃಚ್ಛಿಕ ಪುಟೊ",
+       "randomincategory-category": "ವರ್ಗೊ:",
        "randomincategory-submit": "ಪೋಲೆ",
        "statistics": "ಅಂಕಿ ಅಂಶೊಲು",
        "statistics-header-pages": "ಪುಟೊತ ಅಂಕಿ ಅಂಶಲು",
        "statistics-pages": "ಪುಟಕುಲು",
+       "statistics-users-active": "ಸಕ್ರಿಯ ಬಳಕೆದಾರೆರ್",
        "pageswithprop-submit": "ಪೋಲೆ",
        "brokenredirects-edit": "ಸಂಪೊಲಿಪುಲೆ",
        "brokenredirects-delete": "ಮಾಜಾಲೆ",
        "wantedfiles": "ಬೋಡಾಯಿನ ಕಡತೊಲು",
        "prefixindex": "ಪೂರ್ವನಾಮೊಲ್ದ ಸೂಚಿಕೆ",
        "prefixindex-submit": "ತೋಜಾಲೆ",
+       "shortpages": "ಎಲ್ಯ ಪುಟೊಕುಲು",
+       "longpages": "ಉದ್ದ ಪುಟೊಕುಲು",
+       "protectedpages": "ಸಂರಕ್ಷಿತ ಪುಟೊ",
        "protectedpages-page": "ಪುಟೊ",
        "protectedpages-reason": "ಕಾರಣೊ",
        "protectedpages-unknown-timestamp": "ಗೊತ್ತಿಜ್ಜಾಂದಿನ",
+       "protectedpages-unknown-performer": "ಅಜ್ಞಾತ ಬಳಕೆದಾರೆ",
+       "protectedtitles": "ಸಂರಕ್ಷಿತ ಶೀರ್ಷಿಕೆಲು",
        "listusers": "ಬಳಕೆದಾರರೆನ ತಖ್ತೆ",
        "newpages": "ಪೊಸ ಪುಟೊಲು",
        "newpages-submit": "ತೋಜಾಲೆ",
        "emailsend": "ಕಡಪುಡುಲೆ",
        "watchlist": "ವೀಕ್ಷಣಾ ಪಟ್ಟಿ",
        "mywatchlist": "ಎನ್ನ ವೀಕ್ಷಣಾಪಟ್ಟಿ",
+       "watchnologin": "ಲಾಗಿನ್ ಆತ್‍ಜರ್",
        "watch": "ತೂಲೆ",
        "watchthispage": "ಈ ಪುಟೊನು ತೂಲೆ",
        "unwatch": "ವೀಕ್ಷಣಾಪಟ್ಟಿರ್ದ್ ದೆಪ್ಪು",
        "watchlist-hide": "ಅಡೆಂಗಾವು",
        "watchlist-submit": "ತೋಜಾವು",
        "wlshowhideminor": "ಎಲ್ಯೆಲ್ಯ ಬದಲಾವಣೆಲು",
+       "wlshowhideliu": "ನೋಂದವಣೆ ಆತಿನಂಚಿನ ಸದಸ್ಯೆರ್",
+       "wlshowhideanons": "ಪುದರ್ ಇದ್ಯಾಂದಿನ ಸದಸ್ಯೆರ್",
        "watchlist-options": "ವೀಕ್ಷಣಾಪಟ್ಟಿ ಆಯ್ಕೆಲು",
        "watching": "ವೀಕ್ಷಣಾಪಟ್ಟಿಗ್ ಸೇರ್ಪಾವೊಂದುಂಡು...",
        "unwatching": "ವೀಕ್ಷಣಾಪಟ್ಟಿರ್ದ್ ದೆತ್ತೊಂದುಂಡು...",
+       "deletepage": "ಪುಟೊಕುಲೆನ್ ಮಾಜಾಲೆ",
        "confirm": "ಗಟ್ಟಿಮಲ್ಪುಲೆ",
        "delete-legend": "ಮಾಜಾಲೆ",
        "historyaction-submit": "ತೋಜಾಲೆ",
        "actioncomplete": "ಕಾರ್ಯ ಸಂಪೂರ್ಣ",
        "dellogpage": "ಡಿಲೀಟ್ ಮಲ್ತಿನ ಫೈಲ್‍ದ ದಾಕಲೆ",
+       "deletionlog": "ಡಿಲೀಟ್ ಮಲ್ತಿನ ಫೈಲ್‍ದ ದಾಕಲೆ",
        "deletecomment": "ಕಾರಣ:",
        "deletereasonotherlist": "ಬೇತೆ ಕಾರಣ",
+       "delete-edit-reasonlist": "ಮಾಜಾಯಿನ ಕಾರಣೊಲೆನ್ ಸಂಪಾದನೆ ಮಲ್ಪುಲೆ",
        "rollbacklink": "ಪುಡತ್ತ್ ಪಾಡ್",
        "rollbacklinkcount": "ಪಿರ ದೆತೊನ್ಲೆ $1 {{PLURAL:$1|edit|ಸಂಪದನೆಲು}}",
+       "changecontentmodel-title-label": "ಪುಟೊದ ಪುದರ್",
        "changecontentmodel-reason-label": "ಕಾರಣ:",
        "changecontentmodel-submit": "ಬದಲಾವಣೆ",
        "logentry-contentmodel-change-revertlink": "ದುಂಬುದ ಲೆಕ ಮಲ್ಪುಲೆ",
        "ipbreason": "ಕಾರಣೊ:",
        "ipboptions": "2 ಗಂಟೆಲು:2 hours,1 ದಿನ:1 day,3 ದಿನೊಲು:3 days,1 ವಾರ:1 week,2 ವಾರೊಲು:2 weeks,1 ತಿಂಗೊಲು:1 month,3 ತಿಂಗೊಲು:3 months,6 ತಿಂಗೊಲು:6 months,1 ವರ್ಷ:1 year,ಅನಿರ್ಧಿಷ್ಟ:infinite",
        "ipblocklist": "ತಡೆಪತ್ತ್’ದಿನ ಐ.ಪಿ ವಿಳಾಸೊಲು ಅಂಚೆನೆ ಬಳಕೆದ ಪುದರ್’ಲು",
+       "blocklist-target": "ಗುರಿ",
+       "blocklist-reason": "ಕಾರಣೊ",
+       "ipblocklist-submit": "ನಾಡ್‍ಲೆ",
        "blocklink": "ಅಡ್ಡ ಪತ್ತ್‌ಲೆ",
        "unblocklink": "ಅಡ್ಡನ್ ದೆಪ್ಪುಲೆ",
        "change-blocklink": "ಬ್ಲಾಕ್’ನ್ ಬದಲಾಲೆ",
        "contribslink": "ಕಾಣಿಕೆಲು",
+       "emaillink": "ಇ-ಅಂಚೆ ಕಡಪುಡುಲೆ",
        "blocklogpage": "ತಡೆಪತ್ತ್’ದ್’ನ ಸದಸ್ಯೆರ್ನ ದಿನಚರಿ",
        "blocklogentry": "[[$1]] ಖಾತೆನ್ $2 $3 ಮುಟ್ಟ ತಡೆಪತ್ತ್’ದ್’ನ್ಡ್",
        "unblocklogentry": "$1 ಖಾತೆನ್ ಅನ್-ಬ್ಲಾಕ್ ಮಲ್ತ್’ನ್ಡ್",
        "block-log-flags-nocreate": "ಖಾತೆ ಸೃಷ್ಟಿನ್ ತಡೆಪತ್ತ್’ದ್’ನ್ಡ್",
        "movelogpage": "ಸ್ತಲಾಂತರೊದ ದಾಕಲೆ",
+       "movereason": "ಕಾರಣೊ:",
        "revertmove": "ದುಂಬುದ ಲೆಕೆ ಮಲ್ಪುಲೆ",
        "export": "ಪುಟೊಲೆನ್ ಕಡಪುಡ್ಲೆ",
+       "export-submit": "ರಫ್ತು ಮಲ್ಪುಲೆ",
+       "export-addcat": "ಸೇರಾಲೆ",
+       "export-addns": "ಸೇರಾಲೆ",
+       "export-download": "ಕಡತನ್ ಒರಿಪಾಲೆ",
        "allmessagesname": "ಪುದರ್",
+       "allmessages-filter-legend": "ಅರಿಪೆ",
+       "allmessages-filter-all": "ಮಾತಾ",
+       "allmessages-filter-modified": "ಬದಲಾಯಿನ",
+       "allmessages-language": "ಬಾಸೆ:",
+       "allmessages-filter-submit": "ಪೋ",
+       "allmessages-filter-translate": "ಭಾಷಾಂತರ ಮಲ್ಪುಲೆ",
        "thumbnail-more": "ಮಲ್ಲೆ ಮಲ್ಪುಲೆ",
        "thumbnail_error": "ಮುನ್ನೋಟ ಚಿತ್ರೊನು ಸೃಷ್ಟಿ ಮನ್ಪುನಗ ದೋಷ: $1",
+       "import": "ಪುಟೊಲೆನ್ ಕಡಪುಡ್ಲೆ",
+       "import-interwiki-sourcepage": "ಮೂಲ ಪುಟ",
+       "import-interwiki-submit": "ಆಮದು",
+       "import-upload-filename": "ಕಡತದ ಪುದರ್:",
+       "import-comment": "ಅಭಿಪ್ರಾಯೊ:",
        "tooltip-pt-userpage": "{{GENDER:|ಎನ್ನ ಸದಸ್ಯ}} ಪುಟೊ",
        "tooltip-pt-mytalk": "{{GENDER:|ಎನ್ನ}} ಚರ್ಚೆ ಪುಟೊ",
        "tooltip-pt-preferences": "{{GENDER:|ಎನ್ನ}} ಇಸ್ಟೊಲು",
        "tooltip-undo": "\"ವಜಾ ಮಲ್ಪುಲೆ\" ಈ ಬದಲಾವಣೆನ್ ದೆತೊನುಜಿ ಬುಕ್ಕೊ ಪ್ರಿವ್ಯೂ ಮೋಡ್‍ಡ್ ಬದಲಾವಣೆ ಮಲ್ಪೆರ್ ಕೊನೊಪು೦ಡು. ಅ೦ಚೆನೆ ಸಾರಾಂಸೊಡು ಬದಲಾವಣೆಗ್ ಕಾರಣ ಸೇರಾಯರ ಆಪು೦ಡು.",
        "tooltip-summary": "ಒಂಜಿ ಎಲ್ಯ ಸಾರಾಂಸೊ ಕೊರ್ಲೆ",
        "simpleantispam-label": "ಯಾಂಟಿ-ಸ್ಪಾಮ್ ಚೆಕ್.\nಮುಲ್ಪ <strong>ದಿಂಜಾವೊಡ್ಚಿ</strong>",
+       "pageinfo-article-id": "ಪುಟೊದ ಐಡಿ",
        "pageinfo-toolboxlink": "ಪುಟೊದ ಮಾಹಿತಿ",
+       "pageinfo-contentpage-yes": "ಅಂದ್",
+       "pageinfo-protect-cascading-yes": "ಅಂದ್",
+       "pageinfo-category-pages": "ಪುಟೊಕುಲೆ ಸಂಕ್ಯೆ",
        "previousdiff": "← ದುಂಬುದ ಸಂಪದನೆ",
        "nextdiff": "ಬುಕ್ಕೊದ ಸಂಪದನೆ →",
+       "thumbsize": "ಕಿರುನೋಟದ ಗಾತ್ರೊ:",
        "file-info-size": "$1 × $2 ಚಿತ್ರಬಿಂದುಲು, ಫೈಲ್‍ದ ಗಾತ್ರೊ: $3, MIME ಪ್ರಕಾರೊ: $4",
        "file-nohires": "ಇಂದೆರ್ದ್ ಜಾಸ್ತಿ ರೆಸಲ್ಯೂಶನ್ ಇದ್ದಿ,",
        "svg-long-desc": "ಎಸ್.ವಿ.ಜಿ ಫೈಲ್, ಸುಮಾರಾದ್ $1 × $2 ಚಿತ್ರೊಬಿಂದು, ಫೈಲ್‍ದ ಗಾತ್ರ: $3",
        "show-big-image-preview": "ಪಿರವುದ ಪುಟೊದ ಗಾತ್ರೊ: $1.",
        "show-big-image-other": "ಬೇತೆ{{PLURAL:$2|resolution|ನಿರ್ನಯೊಲು}}: $1.",
        "show-big-image-size": "$1 × $2 ಚಿತ್ರೊಬಿಂದುಲು",
+       "newimages-legend": "ಅರಿಪೆ",
+       "ilsubmit": "ನಾಡ್‍ಲೆ",
        "bad_image_list": "ವ್ಯವಸ್ಥೆದ ಆಕಾರ ಈ ರೀತಿ ಉಂಡು:\n\nಪಟ್ಟಿಡುಪ್ಪುನಂಚಿನ ದಾಖಲೆಲೆನ್ (* ರ್ದ್ ಶುರು ಆಪುನ ಸಾಲ್’ಲು) ಮಾತ್ರ ಪರಿಗಣನೆಗ್ ದೆತೊನೆರಾಪುಂಡು.\nಪ್ರತಿ ಸಾಲ್’ದ ಶುರುತ ಲಿಂಕ್ ಒಂಜಿ ದೋಷ ಉಪ್ಪುನಂಚಿನ ಫೈಲ್’ಗ್ ಲಿಂಕಾದುಪ್ಪೊಡು.\nಅವ್ವೇ ಸಾಲ್’ದ ಶುರುತ ಪೂರಾ ಲಿಂಕ್’ಲೆನ್ ಪರಿಗನೆರ್ದ್ ದೆಪ್ಪೆರಾಪುಂಡು, ಪಂಡ ಓವು ಪುಟೊಲೆಡ್ ಫೈಲ್’ದ ಬಗ್ಗೆ ಬರ್ಪುಂಡೋ ಔಲು.",
        "metadata": "ಮೆಟಾಡೇಟಾ",
        "metadata-help": "ಈ ಪೈಲ್‍ಡ್ ಜಾಸ್ತಿ ಮಾಹಿತಿ ಉಂಡು. ಹೆಚ್ಚಿನಂಸೊ ಪೈಲ್‍ನ್ ಉಂಡು ಮಲ್ಪೆರೆ ಉಪಯೋಗ ಮಲ್ತಿನ ಡಿಜಿಟಲ್ ಕ್ಯಾಮೆರರ್ದ್ ಅತ್ತ್ಂಡ ಸ್ಕ್ಯಾನರ್‌ರ್ದ್ ಈ ಮಾಹಿತಿ ಬತ್ತ್ಂಡ್.\nಮೂಲಪ್ರತಿರ್ದ್ ಈ ಪೈಲ್ ಬದಲಾದಿತ್ತ್ಂಡ್, ಈ ಮಾಹಿತಿ ಬದಲಾತಿನ ಪೈಲ್‍ದ ವಿವರೊಲೆಗ್ ಸರಿಯಾದ್ ಹೊಂದಂದೆ ಉಪ್ಪು.",
        "metadata-expand": "ವಿಸ್ತಾರವಾಯಿನ ವಿವರೊಲೆನ್ ತೊಜ್ಪಾವು",
        "metadata-collapse": "ವಿಸ್ತಾರವಾಯಿನ ವಿವರೊಲೆನ್ ದೆಂಗಾವು",
        "metadata-fields": "ಈ ಸಂದೇಸೊಡು ಪಟ್ಟಿ ಮಲ್ತಿನಂಚಿನ EXIF ಮಿತ್ತ ದರ್ಜೆದ ಮಾಹಿತಿನ್ ಚಿತ್ರೊ ಪುಟೊಕು ಸೇರ್ಪಾಯೆರೆ ಆವೊಂದುಂಡು. ಪುಟೊಟು ಮಿತ್ತ ದರ್ಜೆ ಮಾಹಿತಿದ ಪಟ್ಟಿನ್ ದೆಪ್ಪುನಗ ಉಂದು ತೋಜುಂಡು.\nಒರಿದನವು ಮೂಲೊ ಸ್ಥಿತಿಟ್ ಅಡೆಂಗ್‍ದುಂಡು.\n*ಮಲ್ಪುಲೆ\n*ಮಾದರಿ\n*ದಿನೊ ಪೊರ್ತು ಮೂಲೊ\n*ಮಾನಾದಿಗೆದ ಸಮಯೊ\n*ಫ್‍ಸಂಖ್ಯೆ\n*ಐಎಸ್ಒ ವೇಗೊದ ರೇಟಿಂಗ್\n*ತೂಪಿನ ಜಾಗೆದ ದೂರ\n*ಕಲಾವಿದೆ\n*ಕೃತಿಸ್ವಾಮ್ಯೊ\n*ಚಿತ್ರೊ ವಿವರಣೆ\n*ಜಿಪಿಎಸ್ ಅಕ್ಷಾಂಸೊ\n*ಜಿಪಿಎಸ್ ರೇಖಾಂಸೊ\n*ಜಿಪಿಎಸ್ ಎತ್ತರೊ",
+       "exif-imagewidth": "ಅಗೆಲ",
+       "exif-imagelength": "ಎತ್ತರೊ",
        "exif-orientation": "ದಿಕ್ಕ್ ದಿಸೆ",
        "exif-xresolution": "ಅಡ್ಡಗಲೊದ ರೆಜ಼ಲ್ಯೂಶನ್",
        "exif-yresolution": "ಉದ್ದೊದ ರೆಜ಼ಲ್ಯೂಶನ್",
        "exif-make": "ಕ್ಯಾಮರೊದ ತಯಾರೆಕೆರ್",
        "exif-model": "ಕ್ಯಾಮರೊದ ಮಾದರಿ",
        "exif-software": "ಉಪಯೋಗೊ ಮಲ್ತಿನ ತಂತ್ರಾಂಸೊ",
+       "exif-artist": "ಬರೆತಿನಾರ್",
+       "exif-copyright": "ಹಕ್ಕುದಾರೆ",
        "exif-exifversion": "Exif ಆವೃತ್ತಿ",
        "exif-colorspace": "ಬಣ್ಣೊದ ಜಾಗೆ",
        "exif-datetimeoriginal": "ಮಾಹಿತಿ ಸ್ರಿಸ್ಟಿಸಯಿನ ದಿನೊ ಬೊಕ್ಕ ಪೊರ್ತು",
        "exif-datetimedigitized": "ಗಣಕೀಕರಣೊದ ದಿನೊ ಬೊಕ್ಕ ಪೊರ್ತು",
+       "exif-flash": "ಫ್ಲ್ಯಾಶ್",
+       "exif-source": "ಮೂಲೊ",
+       "exif-languagecode": "ಭಾಸೆ",
+       "exif-iimcategory": "ವರ್ಗೊ",
+       "exif-label": "ಗುರುತು ಪಟ್ಟಿ",
        "exif-orientation-1": "ಸಾದಾರನೊ",
+       "exif-meteringmode-1": "ಸರಾಸರಿ",
+       "exif-meteringmode-255": "ಇತರೊ",
+       "exif-lightsource-0": "ಗೊತ್ತಿಜ್ಜಾಂದಿನ",
+       "exif-lightsource-4": "ಫ್ಲ್ಯಾಶ್",
+       "exif-contrast-0": "ಸಾದಾರನೊ",
+       "exif-saturation-0": "ಸಾದಾರನೊ",
+       "exif-subjectdistancerange-0": "ಗೊತ್ತಿಜ್ಜಾಂದಿನ",
+       "exif-iimcategory-hth": "ಆರೋಗ್ಯ",
        "namespacesall": "ಮಾತ",
        "monthsall": "ಮಾತ",
+       "confirm_purge_button": "ಸರಿ",
+       "confirm-watch-button": "ಸರಿ",
+       "confirm-unwatch-button": "ಸರಿ",
+       "confirm-rollback-button": "ಸರಿ",
+       "quotation-marks": "\"$1\"",
+       "imgmultipageprev": "← ದುಂಬುತ ಪುಟೊ",
+       "imgmultipagenext": "ನನತಾ ಪುಟ →",
+       "img-lang-go": "ಪೋಲೆ",
+       "table_pager_next": "ನನತಾ ಪುಟ",
+       "table_pager_prev": "ದುಂಬುತ ಪುಟೊ",
+       "table_pager_first": "ಸುರುತ ಪುಟೊ",
+       "table_pager_last": "ಕಡೆತ ಪುಟೊ",
+       "table_pager_limit_submit": "ಪೋಲೆ",
+       "watchlistedit-raw-titles": "ತರೆಬರವು:",
        "watchlistedit-clear-title": "ತುಯಿನೇನ್ ಮಾಜಾಲೇ",
+       "watchlistedit-clear-legend": "ತುಯಿನೇನ್ ಮಾಜಾಲೇ",
+       "watchlistedit-clear-titles": "ತರೆಬರವು:",
        "watchlisttools-view": "ಪ್ರಸ್ತುತ ಬದಲಾವಣೆಲ್ ತೋಜಾಲೆ",
        "watchlisttools-edit": "ವೀಕ್ಷಣಾಪಟ್ಟಿನ್ ತೂಲೆ ಬೊಕ್ಕ ಎಡಿಟ್ ಮಲ್ಪುಲೆ",
        "signature": "[[{{ns:user}}:$1|$2]] ([[{{ns:user_talk}}:$1|ಪಾತೆರ್ಲೆ]])",
+       "version": "ಆವೃತ್ತಿ",
+       "version-specialpages": "ವಿಸೇಸೊ ಪುಟೊಲು",
+       "version-other": "ಇತರೊ",
+       "version-ext-license": "ಪರವಾನಗಿ",
+       "version-ext-colheader-name": "ವಿಸ್ತರಣೆ",
+       "version-skin-colheader-name": "ಸ್ಕಿನ್",
+       "version-ext-colheader-version": "ಆವೃತ್ತಿ",
+       "version-ext-colheader-license": "ಪರವಾನಗಿ",
+       "version-ext-colheader-description": "ವಿವರಣೆ",
+       "version-ext-colheader-credits": "ಲೇಖಕೆರ್",
+       "version-poweredby-others": "ಇತರೊ",
+       "version-software-product": "ಉತ್ಪನ್ನ",
+       "version-software-version": "ಆವೃತ್ತಿ",
+       "version-libraries-library": "ಗ್ರಂಥಾಲಯೊ",
+       "version-libraries-version": "ಆವೃತ್ತಿ",
+       "version-libraries-license": "ಪರವಾನಗಿ",
+       "version-libraries-description": "ವಿವರಣೆ",
+       "version-libraries-authors": "ಲೇಖಕೆರ್",
+       "redirect-submit": "ಪೋಲೆ",
+       "redirect-value": "ಮೌಲ್ಯ:",
+       "redirect-user": "ಬಳಕೆದಾರೆರ ID",
+       "redirect-page": "ಪುಟೊದ ಐಡಿ",
+       "redirect-file": "ಕಡತದ ಪುದರ್",
+       "fileduplicatesearch-filename": "ಕಡತದ ಪುದರ್:",
+       "fileduplicatesearch-submit": "ನಾಡ್‍ಲೆ",
        "specialpages": "ವಿಸೇಸೊ ಪುಟೊಲು",
+       "blankpage": "ಖಾಲಿ ಪುಟ",
        "tag-filter": "[[Special:Tags|ಟ್ಯಾಗ್]]ಅರಿಪೆ:",
+       "tag-filter-submit": "ಅರಿಪೆ",
        "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|Tag|ಟ್ಯಾಗುಲು}}]]:$2)",
+       "tags-title": "ತೂಗು ಪಟ್ಟಿಲು",
+       "tags-source-header": "ಮೂಲೊ",
+       "tags-active-header": "ಸಕ್ರಿಯ?",
+       "tags-actions-header": "ಕ್ರಿಯೆಕ್ಕುಲು",
+       "tags-active-yes": "ಅಂದ್",
+       "tags-active-no": "ಅತ್ತ್",
+       "tags-edit": "ಸಂಪೊಲಿಪುಲೆ",
+       "tags-delete": "ಮಾಜಾಲೆ",
+       "tags-create-reason": "ಕಾರಣ:",
+       "tags-create-submit": "ಸೃಷ್ಟಿಸಾಲೆ",
+       "tags-delete-reason": "ಕಾರಣ:",
+       "tags-deactivate-reason": "ಕಾರಣ:",
        "logentry-delete-delete": "$1{{GENDER:$2|ಮಾಜಾದ್‍ಂಡ್}}ಪುಟೊ $3",
        "logentry-move-move": "$1 {{GENDER:$2|ಜಾರಲೆ}} ಪುಟೊ $3 ಡ್ದ್ $4",
        "logentry-newusers-create": "ಬಳಕೆದಾರೆರೆ ಕಾತೆ $1 ನ್ನು {{GENDER:$2|ಸ್ರಿಸ್ಟಿ ಮಲ್ತಾಂಡ್}}",
index f81a37c..2eaa98c 100644 (file)
        "linkaccounts-submit": "Пов'язати облікові записи",
        "unlinkaccounts": "Відв'язати облікові записи",
        "unlinkaccounts-success": "Обліковий запис було відв'язано.",
-       "authenticationdatachange-ignored": "Неопрацьована зміна облікових даних. Можливо, жоден з провайдерів не був налаштований?"
+       "authenticationdatachange-ignored": "Неопрацьована зміна облікових даних. Можливо, жоден з провайдерів не був налаштований?",
+       "userjsispublic": "Будь ласка, зверніть увагу: підсторінки JavaScript не повинні містити конфіденційних даних, бо їх можуть бачити інші користувачі.",
+       "usercssispublic": "Будь ласка, зверніть увагу: підсторінки CSS не повинні містити конфіденційних даних, бо їх можуть бачити інші користувачі."
 }
index ef540a8..5488280 100644 (file)
@@ -45,6 +45,7 @@ $wgAutoloadClasses += [
        'ResourceLoaderTestCase' => "$testDir/phpunit/ResourceLoaderTestCase.php",
        'ResourceLoaderTestModule' => "$testDir/phpunit/ResourceLoaderTestCase.php",
        'ResourceLoaderFileModuleTestModule' => "$testDir/phpunit/ResourceLoaderTestCase.php",
+       'EmptyResourceLoader' => "$testDir/phpunit/ResourceLoaderTestCase.php",
        'TestUser' => "$testDir/phpunit/includes/TestUser.php",
        'TestUserRegistry' => "$testDir/phpunit/includes/TestUserRegistry.php",
        'LessFileCompilationTest' => "$testDir/phpunit/LessFileCompilationTest.php",
index 84bf2fd..18a49f6 100644 (file)
@@ -1,5 +1,8 @@
 <?php
 
+use Psr\Log\LoggerInterface;
+use Psr\Log\NullLogger;
+
 abstract class ResourceLoaderTestCase extends MediaWikiTestCase {
        /**
         * @param string $lang
@@ -128,3 +131,13 @@ class ResourceLoaderTestModule extends ResourceLoaderModule {
 
 class ResourceLoaderFileModuleTestModule extends ResourceLoaderFileModule {
 }
+
+class EmptyResourceLoader extends ResourceLoader {
+       // TODO: This won't be needed once ResourceLoader is empty by default
+       // and default registrations are done from ServiceWiring instead.
+       public function __construct( Config $config = null, LoggerInterface $logger = null ) {
+               $this->setLogger( $logger ?: new NullLogger() );
+               $this->config = $config ?: ConfigFactory::getDefaultInstance()->makeConfig( 'main' );
+               $this->setMessageBlobStore( new MessageBlobStore( $this, $this->getLogger() ) );
+       }
+}
index 7e55a73..0751409 100644 (file)
@@ -23,6 +23,7 @@ class DatabaseTest extends MediaWikiTestCase {
                        $this->dropFunctions();
                        $this->functionTest = false;
                }
+               $this->db->restoreFlags( IDatabase::RESTORE_INITIAL );
        }
        /**
         * @covers DatabaseBase::dropTable
@@ -323,4 +324,42 @@ class DatabaseTest extends MediaWikiTestCase {
                $db->begin( __METHOD__ );
                throw new RunTimeException( "Uh oh!" );
        }
+
+       /**
+        * @covers DatabaseBase::getFlag(
+        * @covers DatabaseBase::setFlag()
+        * @covers DatabaseBase::restoreFlags()
+        */
+       public function testFlagSetting() {
+               $db = $this->db;
+               $origTrx = $db->getFlag( DBO_TRX );
+               $origSsl = $db->getFlag( DBO_SSL );
+
+               if ( $origTrx ) {
+                       $db->clearFlag( DBO_TRX, $db::REMEMBER_PRIOR );
+               } else {
+                       $db->setFlag( DBO_TRX, $db::REMEMBER_PRIOR );
+               }
+               $this->assertEquals( !$origTrx, $db->getFlag( DBO_TRX ) );
+
+               if ( $origSsl ) {
+                       $db->clearFlag( DBO_SSL, $db::REMEMBER_PRIOR );
+               } else {
+                       $db->setFlag( DBO_SSL, $db::REMEMBER_PRIOR );
+               }
+               $this->assertEquals( !$origSsl, $db->getFlag( DBO_SSL ) );
+
+               $db2 = clone $db;
+               $db2->restoreFlags( $db::RESTORE_INITIAL );
+               $this->assertEquals( $origTrx, $db2->getFlag( DBO_TRX ) );
+               $this->assertEquals( $origSsl, $db2->getFlag( DBO_SSL ) );
+
+               $db->restoreFlags();
+               $this->assertEquals( $origSsl, $db->getFlag( DBO_SSL ) );
+               $this->assertEquals( !$origTrx, $db->getFlag( DBO_TRX ) );
+
+               $db->restoreFlags();
+               $this->assertEquals( $origSsl, $db->getFlag( DBO_SSL ) );
+               $this->assertEquals( $origTrx, $db->getFlag( DBO_TRX ) );
+       }
 }
index 09d31fc..81c9faf 100644 (file)
@@ -23,10 +23,10 @@ class FakeDatabase extends DatabaseBase {
        function __construct() {
        }
 
-       function clearFlag( $arg ) {
+       function clearFlag( $arg, $remember = self::REMEMBER_NOTHING ) {
        }
 
-       function setFlag( $arg ) {
+       function setFlag( $arg, $remember = self::REMEMBER_NOTHING ) {
        }
 
        public function insert( $table, $a, $fname = __METHOD__, $options = [] ) {
index 9b62b82..ab1323e 100644 (file)
@@ -310,7 +310,6 @@ mw.loader.register( [
         * @dataProvider provideGetModuleRegistrations
         * @covers ResourceLoaderStartUpModule::compileUnresolvedDependencies
         * @covers ResourceLoaderStartUpModule::getModuleRegistrations
-        * @covers ResourceLoader::makeLoaderSourcesScript
         * @covers ResourceLoader::makeLoaderRegisterScript
         */
        public function testGetModuleRegistrations( $case ) {
index 65cd6ed..53c9926 100644 (file)
@@ -17,18 +17,12 @@ class ResourceLoaderTest extends ResourceLoaderTestCase {
                ] );
        }
 
-       public static function provideValidModules() {
-               return [
-                       [ 'TEST.validModule1', new ResourceLoaderTestModule() ],
-               ];
-       }
-
        /**
-        * Ensures that the ResourceLoaderRegisterModules hook is called when a new
-        * ResourceLoader object is constructed.
+        * Ensure the ResourceLoaderRegisterModules hook is called.
+        *
         * @covers ResourceLoader::__construct
         */
-       public function testCreatingNewResourceLoaderCallsRegistrationHook() {
+       public function testConstructRegistrationHook() {
                $resourceLoaderRegisterModulesHook = false;
 
                $this->setMwGlobals( 'wgHooks', [
@@ -39,66 +33,112 @@ class ResourceLoaderTest extends ResourceLoaderTestCase {
                        ]
                ] );
 
-               $resourceLoader = new ResourceLoader();
+               $unused = new ResourceLoader();
                $this->assertTrue(
                        $resourceLoaderRegisterModulesHook,
                        'Hook ResourceLoaderRegisterModules called'
                );
-
-               return $resourceLoader;
        }
 
        /**
-        * @dataProvider provideValidModules
-        * @depends testCreatingNewResourceLoaderCallsRegistrationHook
         * @covers ResourceLoader::register
         * @covers ResourceLoader::getModule
         */
-       public function testRegisteredValidModulesAreAccessible(
-               $name, ResourceLoaderModule $module, ResourceLoader $resourceLoader
-       ) {
-               $resourceLoader->register( $name, $module );
-               $this->assertEquals( $module, $resourceLoader->getModule( $name ) );
+       public function testRegisterValid() {
+               $module = new ResourceLoaderTestModule();
+               $resourceLoader = new EmptyResourceLoader();
+               $resourceLoader->register( 'test', $module );
+               $this->assertEquals( $module, $resourceLoader->getModule( 'test' ) );
        }
 
        /**
-        * @covers ResourceLoaderFileModule::compileLessFile
+        * @covers ResourceLoader::register
         */
-       public function testLessFileCompilation() {
-               $context = $this->getResourceLoaderContext();
-               $basePath = __DIR__ . '/../../data/less/module';
-               $module = new ResourceLoaderFileModule( [
-                       'localBasePath' => $basePath,
-                       'styles' => [ 'styles.less' ],
-               ] );
-               $module->setName( 'test.less' );
-               $styles = $module->getStyles( $context );
-               $this->assertStringEqualsFile( $basePath . '/styles.css', $styles['all'] );
+       public function testRegisterInvalidName() {
+               $resourceLoader = new EmptyResourceLoader();
+               $this->setExpectedException( 'MWException', "name 'test!invalid' is invalid" );
+               $resourceLoader->register( 'test!invalid', new ResourceLoaderTestModule() );
        }
 
        /**
-        * Strip @noflip annotations from CSS code.
-        * @param string $css
-        * @return string
+        * @covers ResourceLoader::register
         */
-       private static function stripNoflip( $css ) {
-               return str_replace( '/*@noflip*/ ', '', $css );
+       public function testRegisterInvalidType() {
+               $resourceLoader = new EmptyResourceLoader();
+               $this->setExpectedException( 'MWException', 'ResourceLoader module info type error' );
+               $resourceLoader->register( 'test', new stdClass() );
        }
 
        /**
-        * @dataProvider providePackedModules
-        * @covers ResourceLoader::makePackedModulesString
+        * @covers ResourceLoader::getModuleNames
         */
-       public function testMakePackedModulesString( $desc, $modules, $packed ) {
-               $this->assertEquals( $packed, ResourceLoader::makePackedModulesString( $modules ), $desc );
+       public function testGetModuleNames() {
+               // Use an empty one so that core and extension modules don't get in.
+               $resourceLoader = new EmptyResourceLoader();
+               $resourceLoader->register( 'test.foo', new ResourceLoaderTestModule() );
+               $resourceLoader->register( 'test.bar', new ResourceLoaderTestModule() );
+               $this->assertEquals(
+                       [ 'test.foo', 'test.bar' ],
+                       $resourceLoader->getModuleNames()
+               );
        }
 
        /**
-        * @dataProvider providePackedModules
-        * @covers ResourceLoaderContext::expandModuleNames
+        * @covers ResourceLoader::isModuleRegistered
         */
-       public function testexpandModuleNames( $desc, $modules, $packed ) {
-               $this->assertEquals( $modules, ResourceLoaderContext::expandModuleNames( $packed ), $desc );
+       public function testIsModuleRegistered() {
+               $rl = new EmptyResourceLoader();
+               $rl->register( 'test', new ResourceLoaderTestModule() );
+               $this->assertTrue( $rl->isModuleRegistered( 'test' ) );
+               $this->assertFalse( $rl->isModuleRegistered( 'test.unknown' ) );
+       }
+
+       /**
+        * @covers ResourceLoader::getModule
+        */
+       public function testGetModuleUnknown() {
+               $rl = new EmptyResourceLoader();
+               $this->assertSame( null, $rl->getModule( 'test' ) );
+       }
+
+       /**
+        * @covers ResourceLoader::getModule
+        */
+       public function testGetModuleClass() {
+               $rl = new EmptyResourceLoader();
+               $rl->register( 'test', [ 'class' => ResourceLoaderTestModule::class ] );
+               $this->assertInstanceOf(
+                       ResourceLoaderTestModule::class,
+                       $rl->getModule( 'test' )
+               );
+       }
+
+       /**
+        * @covers ResourceLoader::getModule
+        */
+       public function testGetModuleClassDefault() {
+               $rl = new EmptyResourceLoader();
+               $rl->register( 'test', [] );
+               $this->assertInstanceOf(
+                       ResourceLoaderFileModule::class,
+                       $rl->getModule( 'test' ),
+                       'Array-style module registrations default to FileModule'
+               );
+       }
+
+       /**
+        * @covers ResourceLoaderFileModule::compileLessFile
+        */
+       public function testLessFileCompilation() {
+               $context = $this->getResourceLoaderContext();
+               $basePath = __DIR__ . '/../../data/less/module';
+               $module = new ResourceLoaderFileModule( [
+                       'localBasePath' => $basePath,
+                       'styles' => [ 'styles.less' ],
+               ] );
+               $module->setName( 'test.less' );
+               $styles = $module->getStyles( $context );
+               $this->assertStringEqualsFile( $basePath . '/styles.css', $styles['all'] );
        }
 
        public static function providePackedModules() {
@@ -126,20 +166,34 @@ class ResourceLoaderTest extends ResourceLoaderTestCase {
                ];
        }
 
+       /**
+        * @dataProvider providePackedModules
+        * @covers ResourceLoader::makePackedModulesString
+        */
+       public function testMakePackedModulesString( $desc, $modules, $packed ) {
+               $this->assertEquals( $packed, ResourceLoader::makePackedModulesString( $modules ), $desc );
+       }
+
+       /**
+        * @dataProvider providePackedModules
+        * @covers ResourceLoaderContext::expandModuleNames
+        */
+       public function testexpandModuleNames( $desc, $modules, $packed ) {
+               $this->assertEquals( $modules, ResourceLoaderContext::expandModuleNames( $packed ), $desc );
+       }
+
        public static function provideAddSource() {
                return [
-                       [ 'examplewiki', '//example.org/w/load.php', 'examplewiki' ],
-                       [ 'example2wiki', [ 'loadScript' => '//example.com/w/load.php' ], 'example2wiki' ],
+                       [ 'foowiki', 'https://example.org/w/load.php', 'foowiki' ],
+                       [ 'foowiki', [ 'loadScript' => 'https://example.org/w/load.php' ], 'foowiki' ],
                        [
-                               [ 'foowiki' => '//foo.org/w/load.php', 'bazwiki' => '//baz.org/w/load.php' ],
+                               [
+                                       'foowiki' => 'https://example.org/w/load.php',
+                                       'bazwiki' => 'https://example.com/w/load.php',
+                               ],
                                null,
                                [ 'foowiki', 'bazwiki' ]
-                       ],
-                       [
-                               [ 'foowiki' => '//foo.org/w/load.php' ],
-                               null,
-                               false,
-                       ],
+                       ]
                ];
        }
 
@@ -150,10 +204,6 @@ class ResourceLoaderTest extends ResourceLoaderTestCase {
         */
        public function testAddSource( $name, $info, $expected ) {
                $rl = new ResourceLoader;
-               if ( $expected === false ) {
-                       $this->setExpectedException( 'MWException', 'ResourceLoader duplicate source addition error' );
-                       $rl->addSource( $name, $info );
-               }
                $rl->addSource( $name, $info );
                if ( is_array( $expected ) ) {
                        foreach ( $expected as $source ) {
@@ -164,17 +214,23 @@ class ResourceLoaderTest extends ResourceLoaderTestCase {
                }
        }
 
-       public static function fakeSources() {
-               return [
-                       'examplewiki' => [
-                               'loadScript' => '//example.org/w/load.php',
-                               'apiScript' => '//example.org/w/api.php',
-                       ],
-                       'example2wiki' => [
-                               'loadScript' => '//example.com/w/load.php',
-                               'apiScript' => '//example.com/w/api.php',
-                       ],
-               ];
+       /**
+        * @covers ResourceLoader::addSource
+        */
+       public function testAddSourceDupe() {
+               $rl = new ResourceLoader;
+               $this->setExpectedException( 'MWException', 'ResourceLoader duplicate source addition error' );
+               $rl->addSource( 'foo', 'https://example.org/w/load.php' );
+               $rl->addSource( 'foo', 'https://example.com/w/load.php' );
+       }
+
+       /**
+        * @covers ResourceLoader::addSource
+        */
+       public function testAddSourceInvalid() {
+               $rl = new ResourceLoader;
+               $this->setExpectedException( 'MWException', 'with no "loadScript" key' );
+               $rl->addSource( 'foo',  [ 'x' => 'https://example.org/w/load.php' ] );
        }
 
        public static function provideLoaderImplement() {
@@ -204,8 +260,6 @@ mw.example();
                                'name' => 'test.example',
                                'scripts' => 'mw.example();',
                                'styles' => [],
-                               'messages' => new XmlJsCode( '{}' ),
-                               'templates' => [],
 
                                'expected' => 'mw.loader.implement( "test.example", function ( $, jQuery, require, module ) {
 mw.example();
@@ -218,7 +272,6 @@ mw.example();
                                'scripts' => [],
                                'styles' => [ 'css' => [ '.mw-example {}' ] ],
                                'messages' => new XmlJsCode( '{}' ),
-                               'templates' => [],
 
                                'expected' => 'mw.loader.implement( "test.example", [], {
     "css": [
@@ -231,9 +284,7 @@ mw.example();
 
                                'name' => 'test.example',
                                'scripts' => 'mw.example();',
-                               'styles' => [],
                                'messages' => [ 'example' => '' ],
-                               'templates' => [],
 
                                'expected' => 'mw.loader.implement( "test.example", function ( $, jQuery, require, module ) {
 mw.example();
@@ -246,8 +297,6 @@ mw.example();
 
                                'name' => 'test.example',
                                'scripts' => 'mw.example();',
-                               'styles' => [],
-                               'messages' => new XmlJsCode( '{}' ),
                                'templates' => [ 'example.html' => '' ],
 
                                'expected' => 'mw.loader.implement( "test.example", function ( $, jQuery, require, module ) {
@@ -256,14 +305,39 @@ mw.example();
     "example.html": ""
 } );',
                        ] ],
+                       [ [
+                               'title' => 'Implement unwrapped user script',
+
+                               'name' => 'user',
+                               'scripts' => 'mw.example( 1 );',
+
+                               'expected' => 'mw.loader.implement( "user", "mw.example( 1 );" );',
+                       ] ],
+                       [ [
+                               'title' => 'Implement unwrapped user script',
+                               'debug' => false,
+
+                               'name' => 'user',
+                               'scripts' => 'mw.example( 1 );',
+
+                               'expected' => 'mw.loader.implement("user","mw.example(1);");',
+                       ] ],
                ];
        }
 
        /**
         * @dataProvider provideLoaderImplement
         * @covers ResourceLoader::makeLoaderImplementScript
+        * @covers ResourceLoader::trimArray
         */
        public function testMakeLoaderImplementScript( $case ) {
+               $case += [
+                       'styles' => [], 'templates' => [], 'messages' => new XmlJsCode( '{}' ),
+                       'debug' => true
+               ];
+               ResourceLoader::clearCache();
+               $this->setMwGlobals( 'wgResourceLoaderDebug', $case['debug'] );
+
                $this->assertEquals(
                        $case['expected'],
                        ResourceLoader::makeLoaderImplementScript(
@@ -276,6 +350,63 @@ mw.example();
                );
        }
 
+       /**
+        * @covers ResourceLoader::makeLoaderImplementScript
+        */
+       public function testMakeLoaderImplementScriptInvalid() {
+               $this->setExpectedException( 'MWException', 'Invalid scripts error' );
+               ResourceLoader::makeLoaderImplementScript(
+                       'test', // name
+                       123, // scripts
+                       null, // styles
+                       null, // messages
+                       null // templates
+               );
+       }
+
+       /**
+        * @covers ResourceLoader::makeLoaderSourcesScript
+        */
+       public function testMakeLoaderSourcesScript() {
+               $this->assertEquals(
+                       'mw.loader.addSource( "local", "/w/load.php" );',
+                       ResourceLoader::makeLoaderSourcesScript( 'local', '/w/load.php' )
+               );
+               $this->assertEquals(
+                       'mw.loader.addSource( {
+    "local": "/w/load.php"
+} );',
+                       ResourceLoader::makeLoaderSourcesScript( [ 'local' => '/w/load.php' ] )
+               );
+               $this->assertEquals(
+                       'mw.loader.addSource( {
+    "local": "/w/load.php",
+    "example": "https://example.org/w/load.php"
+} );',
+                       ResourceLoader::makeLoaderSourcesScript( [
+                               'local' => '/w/load.php',
+                               'example' => 'https://example.org/w/load.php'
+                       ] )
+               );
+               $this->assertEquals(
+                       'mw.loader.addSource( [] );',
+                       ResourceLoader::makeLoaderSourcesScript( [] )
+               );
+       }
+
+       private static function fakeSources() {
+               return [
+                       'examplewiki' => [
+                               'loadScript' => '//example.org/w/load.php',
+                               'apiScript' => '//example.org/w/api.php',
+                       ],
+                       'example2wiki' => [
+                               'loadScript' => '//example.com/w/load.php',
+                               'apiScript' => '//example.com/w/api.php',
+                       ],
+               ];
+       }
+
        /**
         * @covers ResourceLoader::getLoadScript
         */
@@ -295,14 +426,4 @@ mw.example();
                        $this->assertTrue( true );
                }
        }
-
-       /**
-        * @covers ResourceLoader::isModuleRegistered
-        */
-       public function testIsModuleRegistered() {
-               $rl = new ResourceLoader();
-               $rl->register( 'test.module', new ResourceLoaderTestModule() );
-               $this->assertTrue( $rl->isModuleRegistered( 'test.module' ) );
-               $this->assertFalse( $rl->isModuleRegistered( 'test.modulenotregistered' ) );
-       }
 }