* filterIntval()
* filterTimezoneInput()
* getTimeZoneList()
+* mw.util.jsMessage(), deprecated in 1.20, was removed. Use mw.notify instead.
=== Deprecations in 1.33 ===
* The configuration option $wgUseESI has been deprecated, and is expected
* defined for a given namespace, pages in that namespace will use the CONTENT_MODEL_WIKITEXT
* (except for the special case of JS and CS pages).
*
+ * @note To determine the default model for a new page's main slot, or any slot in general,
+ * use SlotRoleHandler::getDefaultModel() together with SlotRoleRegistry::getRoleHandler().
+ *
* @since 1.21
*/
$wgNamespaceContentModels = [];
*
* @file
*/
+use MediaWiki\MediaWikiServices;
/**
* This is a utility class with only static functions
* Get the default content model for a namespace
* This does not mean that all pages in that namespace have the model
*
+ * @note To determine the default model for a new page's main slot, or any slot in general,
+ * use SlotRoleHandler::getDefaultModel() together with SlotRoleRegistry::getRoleHandler().
+ *
* @since 1.21
* @param int $index Index to check
* @return null|string Default model name for the given namespace, if set
*/
public static function getNamespaceContentModel( $index ) {
- global $wgNamespaceContentModels;
- return $wgNamespaceContentModels[$index] ?? null;
+ $config = MediaWikiServices::getInstance()->getMainConfig();
+ $models = $config->get( 'NamespaceContentModels' );
+ return $models[$index] ?? null;
}
/**
use MediaWiki\Preferences\PreferencesFactory;
use MediaWiki\Shell\CommandFactory;
use MediaWiki\Revision\RevisionRenderer;
+use MediaWiki\Revision\SlotRoleRegistry;
use MediaWiki\Special\SpecialPageFactory;
use MediaWiki\Storage\BlobStore;
use MediaWiki\Storage\BlobStoreFactory;
return $this->getService( 'SkinFactory' );
}
+ /**
+ * @since 1.33
+ * @return SlotRoleRegistry
+ */
+ public function getSlotRoleRegistry() {
+ return $this->getService( 'SlotRoleRegistry' );
+ }
+
/**
* @since 1.31
* @return NameTableStore
*/
use MediaWiki\MediaWikiServices;
+use MediaWiki\Revision\SlotRecord;
/**
* Handles the backend logic of moving a page from one title
$status->fatal(
'content-not-allowed-here',
ContentHandler::getLocalizedName( $this->oldTitle->getContentModel() ),
- $this->newTitle->getPrefixedText()
+ $this->newTitle->getPrefixedText(),
+ SlotRecord::MAIN
);
}
--- /dev/null
+<?php
+/**
+ * This file is part of MediaWiki.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+namespace MediaWiki\Revision;
+
+use MediaWiki\Linker\LinkTarget;
+
+/**
+ * A SlotRoleHandler for providing basic functionality for undefined slot roles.
+ *
+ * This class is intended to be used when encountering slots with a role that used to be defined
+ * by an extension, but no longer is backed by hany specific handler, since the extension in
+ * question has been uninstalled. It may also be used for pages imported from another wiki.
+ *
+ * @since 1.33
+ */
+class FallbackSlotRoleHandler extends SlotRoleHandler {
+
+ public function __construct( $role ) {
+ // treat unknown content as plain text
+ parent::__construct( $role, CONTENT_MODEL_TEXT );
+ }
+
+ /**
+ * @param LinkTarget $page
+ *
+ * @return bool Always false, to prevent undefined slots from being used in new revisions.
+ */
+ public function isAllowedOn( LinkTarget $page ) {
+ return false;
+ }
+
+ /**
+ * @param string $model
+ * @param LinkTarget $page
+ *
+ * @return bool Always false, to prevent undefined slots from being used for
+ * arbitrary content.
+ */
+ public function isAllowedModel( $model, LinkTarget $page ) {
+ return false;
+ }
+
+ public function getOutputLayoutHints() {
+ // TODO: should be return [ 'display' => 'none'] here, causing undefined slots
+ // to be hidden? We'd still need some place to surface the content of such
+ // slots, see T209923.
+
+ return parent::getOutputLayoutHints(); // TODO: Change the autogenerated stub
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * This file is part of MediaWiki.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+namespace MediaWiki\Revision;
+
+use ContentHandler;
+use Hooks;
+use MediaWiki\Linker\LinkTarget;
+use Title;
+
+/**
+ * A SlotRoleHandler for the main slot. While most slot roles serve a specific purpose and
+ * thus typically exhibit the same behaviour on all pages, the main slot is used for different
+ * things in different pages, typically depending on the namespace, a "file extension" in
+ * the page name, or the content model of the slot's content.
+ *
+ * MainSlotRoleHandler implements some of the per-namespace and per-model behavior that was
+ * supported prior to MediaWiki Version 1.33.
+ *
+ * @since 1.33
+ */
+class MainSlotRoleHandler extends SlotRoleHandler {
+
+ /**
+ * @var string[] A mapping of namespaces to content models.
+ * @see $wgNamespaceContentModels
+ */
+ private $namespaceContentModels;
+
+ /**
+ * @param string[] $namespaceContentModels A mapping of namespaces to content models,
+ * typically from $wgNamespaceContentModels.
+ */
+ public function __construct( array $namespaceContentModels ) {
+ parent::__construct( 'main', CONTENT_MODEL_WIKITEXT );
+ $this->namespaceContentModels = $namespaceContentModels;
+ }
+
+ public function supportsArticleCount() {
+ return true;
+ }
+
+ /**
+ * @param string $model
+ * @param LinkTarget $page
+ *
+ * @return bool
+ */
+ public function isAllowedModel( $model, LinkTarget $page ) {
+ $title = Title::newFromLinkTarget( $page );
+ $handler = ContentHandler::getForModelID( $model );
+ return $handler->canBeUsedOn( $title );
+ }
+
+ /**
+ * @param LinkTarget $page
+ *
+ * @return string
+ */
+ public function getDefaultModel( LinkTarget $page ) {
+ // NOTE: this method must not rely on $title->getContentModel() directly or indirectly,
+ // because it is used to initialize the mContentModel member.
+
+ $ext = '';
+ $ns = $page->getNamespace();
+ $model = $this->namespaceContentModels[$ns] ?? null;
+
+ // Hook can determine default model
+ $title = Title::newFromLinkTarget( $page );
+ if ( !Hooks::run( 'ContentHandlerDefaultModelFor', [ $title, &$model ] ) ) {
+ if ( !is_null( $model ) ) {
+ return $model;
+ }
+ }
+
+ // Could this page contain code based on the title?
+ $isCodePage = $ns === NS_MEDIAWIKI && preg_match( '!\.(css|js|json)$!u', $title->getText(), $m );
+ if ( $isCodePage ) {
+ $ext = $m[1];
+ }
+
+ // Is this a user subpage containing code?
+ $isCodeSubpage = $ns === NS_USER
+ && !$isCodePage
+ && preg_match( "/\\/.*\\.(js|css|json)$/", $title->getText(), $m );
+
+ if ( $isCodeSubpage ) {
+ $ext = $m[1];
+ }
+
+ // Is this wikitext, according to $wgNamespaceContentModels or the DefaultModelFor hook?
+ $isWikitext = is_null( $model ) || $model == CONTENT_MODEL_WIKITEXT;
+ $isWikitext = $isWikitext && !$isCodePage && !$isCodeSubpage;
+
+ if ( !$isWikitext ) {
+ switch ( $ext ) {
+ case 'js':
+ return CONTENT_MODEL_JAVASCRIPT;
+ case 'css':
+ return CONTENT_MODEL_CSS;
+ case 'json':
+ return CONTENT_MODEL_JSON;
+ default:
+ return is_null( $model ) ? CONTENT_MODEL_TEXT : $model;
+ }
+ }
+
+ // We established that it must be wikitext
+
+ return CONTENT_MODEL_WIKITEXT;
+ }
+
+}
'Access to the content has been suppressed for this audience'
);
} else {
+ // XXX: allow SlotRoleHandler to control the ParserOutput?
$output = $this->getSlotParserOutputUncached( $content, $withHtml );
if ( $withHtml && !$output->hasText() ) {
/** @var ILoadBalancer */
private $loadBalancer;
+ /** @var SlotRoleRegistry */
+ private $roleRegistery;
+
/** @var string|bool */
private $wikiId;
/**
* @param ILoadBalancer $loadBalancer
+ * @param SlotRoleRegistry $roleRegistry
* @param bool|string $wikiId
*/
- public function __construct( ILoadBalancer $loadBalancer, $wikiId = false ) {
+ public function __construct(
+ ILoadBalancer $loadBalancer,
+ SlotRoleRegistry $roleRegistry,
+ $wikiId = false
+ ) {
$this->loadBalancer = $loadBalancer;
+ $this->roleRegistery = $roleRegistry;
$this->wikiId = $wikiId;
$this->saveParseLogger = new NullLogger();
return $rrev->getSlotParserOutput( SlotRecord::MAIN );
}
- // TODO: put fancy layout logic here, see T200915.
-
// move main slot to front
if ( isset( $slots[SlotRecord::MAIN] ) ) {
$slots = [ SlotRecord::MAIN => $slots[SlotRecord::MAIN] ] + $slots;
$out = $rrev->getSlotParserOutput( $role, $hints );
$slotOutput[$role] = $out;
+ // XXX: should the SlotRoleHandler be able to intervene here?
$combinedOutput->mergeInternalMetaDataFrom( $out, $role );
$combinedOutput->mergeTrackingMetaDataFrom( $out );
}
$first = true;
/** @var ParserOutput $out */
foreach ( $slotOutput as $role => $out ) {
+ $roleHandler = $this->roleRegistery->getRoleHandler( $role );
+
+ // TODO: put more fancy layout logic here, see T200915.
+ $layout = $roleHandler->getOutputLayoutHints();
+ $display = $layout['display'] ?? 'section';
+
+ if ( $display === 'none' ) {
+ continue;
+ }
+
if ( $first ) {
// skip header for the first slot
$first = false;
$html .= Html::rawElement( 'h1', [ 'class' => 'mw-slot-header' ], $headText );
}
+ // XXX: do we want to put a wrapper div around the output?
+ // Do we want to let $roleHandler do that?
$html .= $out->getRawText();
$combinedOutput->mergeHtmlMetaDataFrom( $out );
}
/** @var int An appropriate combination of SCHEMA_COMPAT_XXX flags. */
private $mcrMigrationStage;
+ /** @var SlotRoleRegistry */
+ private $slotRoleRegistry;
+
/**
* @todo $blobStore should be allowed to be any BlobStore!
*
* @param CommentStore $commentStore
* @param NameTableStore $contentModelStore
* @param NameTableStore $slotRoleStore
+ * @param SlotRoleRegistry $slotRoleRegistry
* @param int $mcrMigrationStage An appropriate combination of SCHEMA_COMPAT_XXX flags
* @param ActorMigration $actorMigration
* @param bool|string $wikiId
*
- * @throws MWException if $mcrMigrationStage or $wikiId is invalid.
*/
public function __construct(
ILoadBalancer $loadBalancer,
CommentStore $commentStore,
NameTableStore $contentModelStore,
NameTableStore $slotRoleStore,
+ SlotRoleRegistry $slotRoleRegistry,
$mcrMigrationStage,
ActorMigration $actorMigration,
$wikiId = false
$this->commentStore = $commentStore;
$this->contentModelStore = $contentModelStore;
$this->slotRoleStore = $slotRoleStore;
+ $this->slotRoleRegistry = $slotRoleRegistry;
$this->mcrMigrationStage = $mcrMigrationStage;
$this->actorMigration = $actorMigration;
$this->wikiId = $wikiId;
$format = $content->getDefaultFormat();
$model = $content->getModel();
- $this->checkContent( $content, $title );
+ $this->checkContent( $content, $title, $slot->getRole() );
return $this->blobStore->storeBlob(
$content->serialize( $format ),
*
* @param Content $content
* @param Title $title
+ * @param string $role
*
* @throws MWException
* @throws MWUnknownContentModelException
*/
- private function checkContent( Content $content, Title $title ) {
+ private function checkContent( Content $content, Title $title, $role ) {
// Note: may return null for revisions that have not yet been inserted
$model = $content->getModel();
$this->assertCrossWikiContentLoadingIsSafe();
- $defaultModel = ContentHandler::getDefaultModelFor( $title );
+ $roleHandler = $this->slotRoleRegistry->getRoleHandler( $role );
+ $defaultModel = $roleHandler->getDefaultModel( $title );
$defaultHandler = ContentHandler::getForModelID( $defaultModel );
$defaultFormat = $defaultHandler->getDefaultFormat();
$mainSlotRow->model_name = function ( SlotRecord $slot ) use ( $title ) {
$this->assertCrossWikiContentLoadingIsSafe();
- // TODO: MCR: consider slot role in getDefaultModelFor()! Use LinkTarget!
- // TODO: MCR: deprecate $title->getModel().
- return ContentHandler::getDefaultModelFor( $title );
+ return $this->slotRoleRegistry->getRoleHandler( $slot->getRole() )
+ ->getDefaultModel( $title );
};
}
/** @var NameTableStoreFactory */
private $nameTables;
+ /** @var SlotRoleRegistry */
+ private $slotRoleRegistry;
+
/**
* @param ILBFactory $dbLoadBalancerFactory
* @param BlobStoreFactory $blobStoreFactory
* @param NameTableStoreFactory $nameTables
+ * @param SlotRoleRegistry $slotRoleRegistry
* @param WANObjectCache $cache
* @param CommentStore $commentStore
* @param ActorMigration $actorMigration
ILBFactory $dbLoadBalancerFactory,
BlobStoreFactory $blobStoreFactory,
NameTableStoreFactory $nameTables,
+ SlotRoleRegistry $slotRoleRegistry,
WANObjectCache $cache,
CommentStore $commentStore,
ActorMigration $actorMigration,
Assert::parameterType( 'integer', $migrationStage, '$migrationStage' );
$this->dbLoadBalancerFactory = $dbLoadBalancerFactory;
$this->blobStoreFactory = $blobStoreFactory;
+ $this->slotRoleRegistry = $slotRoleRegistry;
$this->nameTables = $nameTables;
$this->cache = $cache;
$this->commentStore = $commentStore;
$this->commentStore,
$this->nameTables->getContentModels( $wikiId ),
$this->nameTables->getSlotRoles( $wikiId ),
+ $this->slotRoleRegistry,
$this->mcrMigrationStage,
$this->actorMigration,
$wikiId
--- /dev/null
+<?php
+/**
+ * This file is part of MediaWiki.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+namespace MediaWiki\Revision;
+
+use MediaWiki\Linker\LinkTarget;
+
+/**
+ * SlotRoleHandler instances are used to declare the existence and behavior of slot roles.
+ * Most importantly, they control which content model can be used for the slot, and how it is
+ * represented in the rendered verswion of page content.
+ *
+ * @since 1.33
+ */
+class SlotRoleHandler {
+
+ /**
+ * @var string
+ */
+ private $role;
+
+ /**
+ * @var array
+ * @see getOutputLayoutHints
+ */
+ private $layout = [
+ 'display' => 'section', // use 'none' to suppress
+ 'region' => 'center',
+ 'placement' => 'append'
+ ];
+
+ /**
+ * @var string
+ */
+ private $contentModel;
+
+ /**
+ * @param string $role The name of the slot role defined by this SlotRoleHandler. See
+ * SlotRoleRegistry::defineRole for more information.
+ * @param string $contentModel The default content model for this slot. As per the default
+ * implementation of isAllowedModel(), also the only content model allowed for the
+ * slot. Subclasses may however handle default and allowed models differently.
+ * @param array $layout Layout hints, for use by RevisionRenderer. See getOutputLayoutHints.
+ */
+ public function __construct( $role, $contentModel, $layout = [] ) {
+ $this->role = $role;
+ $this->contentModel = $contentModel;
+ $this->layout = array_merge( $this->layout, $layout );
+ }
+
+ /**
+ * @return string The role this SlotRoleHandler applies to
+ */
+ public function getRole() {
+ return $this->role;
+ }
+
+ /**
+ * Layout hints for use while laying out the combined output of all slots, typically by
+ * RevisionRenderer. The layout hints are given as an associative array. Well-known keys
+ * to use:
+ *
+ * * "display": how the output of this slot should be represented. Supported values:
+ * - "section": show as a top level section of the region.
+ * - "none": do not show at all
+ * Further values that may be supported in the future include "box" and "banner".
+ * * "region": in which region of the page the output should be placed. Supported values:
+ * - "center": the central content area.
+ * Further values that may be supported in the future include "top" and "bottom", "left"
+ * and "right", "header" and "footer".
+ * * "placement": placement relative to other content of the same area.
+ * - "append": place at the end, after any output processed previously.
+ * Further values that may be supported in the future include "prepend". A "weight" key
+ * may be introduced for more fine grained control.
+ *
+ * @return array an associative array of hints
+ */
+ public function getOutputLayoutHints() {
+ return $this->layout;
+ }
+
+ /**
+ * The message key for the translation of the slot name.
+ *
+ * @return string
+ */
+ public function getNameMessageKey() {
+ return 'slot-name-' . $this->role;
+ }
+
+ /**
+ * Determines the content model to use per default for this slot on the given page.
+ *
+ * The default implementation always returns the content model provided to the constructor.
+ * Subclasses may base the choice on default model on the page title or namespace.
+ * The choice should not depend on external state, such as the page content.
+ *
+ * @param LinkTarget $page
+ *
+ * @return string
+ */
+ public function getDefaultModel( LinkTarget $page ) {
+ return $this->contentModel;
+ }
+
+ /**
+ * Determines whether the given model can be used on this slot on the given page.
+ *
+ * The default implementation checks whether $model is the content model provided to the
+ * constructor. Subclasses may allow other models and may base the decision on the page title
+ * or namespace. The choice should not depend on external state, such as the page content.
+ *
+ * @note This should be checked when creating new revisions. Existing revisions
+ * are not guaranteed to comply with the return value.
+ *
+ * @param string $model
+ * @param LinkTarget $page
+ *
+ * @return bool
+ */
+ public function isAllowedModel( $model, LinkTarget $page ) {
+ return ( $model === $this->contentModel );
+ }
+
+ /**
+ * Whether this slot should be considered when determining whether a page should be counted
+ * as an "article" in the site statistics.
+ *
+ * For a page to be considered countable, one of the page's slots must return true from this
+ * method, and Content::isCountable() must return true for the content of that slot.
+ *
+ * The default implementation always returns false.
+ *
+ * @return string
+ */
+ public function supportsArticleCount() {
+ return false;
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * This file is part of MediaWiki.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+namespace MediaWiki\Revision;
+
+use InvalidArgumentException;
+use LogicException;
+use MediaWiki\Linker\LinkTarget;
+use MediaWiki\Storage\NameTableStore;
+use Wikimedia\Assert\Assert;
+
+/**
+ * A registry service for SlotRoleHandlers, used to define which slot roles are available on
+ * which page.
+ *
+ * Extensions may use the SlotRoleRegistry to register the slots they define.
+ *
+ * In the context of the SlotRoleRegistry, it is useful to distinguish between "defined" and "known"
+ * slot roles: A slot role is "defined" if defineRole() or defineRoleWithModel() was called for
+ * that role. A slot role is "known" if the NameTableStore provided to the constructor as the
+ * $roleNamesStore parameter has an ID associated with that role, which essentially means that
+ * the role at some point has been used on the wiki. Roles that are not "defined" but are
+ * "known" typically belong to extensions that used to be installed on the wiki, but no longer are.
+ * Such slots should be considered ok for display and administrative operations, but only "defined"
+ * slots should be supported for editing.
+ *
+ * @since 1.33
+ */
+class SlotRoleRegistry {
+
+ /**
+ * @var NameTableStore
+ */
+ private $roleNamesStore;
+
+ /**
+ * @var callable[]
+ */
+ private $instantiators = [];
+
+ /**
+ * @var SlotRoleHandler[]
+ */
+ private $handlers;
+
+ /**
+ * SlotRoleRegistry constructor.
+ *
+ * @param NameTableStore $roleNamesStore
+ */
+ public function __construct( NameTableStore $roleNamesStore ) {
+ $this->roleNamesStore = $roleNamesStore;
+ }
+
+ /**
+ * Defines a slot role.
+ *
+ * For use by extensions that wish to define roles beyond the main slot role.
+ *
+ * @see defineRoleWithModel()
+ *
+ * @param string $role The role name of the slot to define. This should follow the
+ * same convention as message keys:
+ * @param callable $instantiator called with $role as a parameter;
+ * Signature: function ( string $role ): SlotRoleHandler
+ */
+ public function defineRole( $role, callable $instantiator ) {
+ if ( $this->isDefinedRole( $role ) ) {
+ throw new LogicException( "Role $role is already defined" );
+ }
+
+ $this->instantiators[$role] = $instantiator;
+ }
+
+ /**
+ * Defines a slot role that allows only the given content model, and has no special
+ * behavior.
+ *
+ * For use by extensions that wish to define roles beyond the main slot role, but have
+ * no need to implement any special behavior for that slot.
+ *
+ * @see defineRole()
+ *
+ * @param string $role The role name of the slot to define, see defineRole()
+ * for more information.
+ * @param string $model A content model name, see ContentHandler
+ * @param array $layout See SlotRoleHandler getOutputLayoutHints
+ */
+ public function defineRoleWithModel( $role, $model, $layout = [] ) {
+ $this->defineRole(
+ $role,
+ function ( $role ) use ( $model, $layout ) {
+ return new SlotRoleHandler( $role, $model, $layout );
+ }
+ );
+ }
+
+ /**
+ * Gets the SlotRoleHandler that should be used when processing content of the given role.
+ *
+ * @param string $role
+ *
+ * @throws InvalidArgumentException If $role is not a known slot role.
+ * @return SlotRoleHandler The handler to be used for $role. This may be a
+ * FallbackSlotRoleHandler if the slot is "known" but not "defined".
+ */
+ public function getRoleHandler( $role ) {
+ if ( !isset( $this->handlers[$role] ) ) {
+ if ( !$this->isDefinedRole( $role ) ) {
+ if ( $this->isKnownRole( $role ) ) {
+ // The role has no handler defined, but is represented in the database.
+ // This may happen e.g. when the extension that defined the role was uninstalled.
+ wfWarn( __METHOD__ . ": known but undefined slot role $role" );
+ $this->handlers[$role] = new FallbackSlotRoleHandler( $role );
+ } else {
+ // The role doesn't have a handler defined, and is not represented in
+ // the database. Something must be quite wrong.
+ throw new InvalidArgumentException( "Unknown role $role" );
+ }
+ } else {
+ $handler = call_user_func( $this->instantiators[$role], $role );
+
+ Assert::postcondition(
+ $handler instanceof SlotRoleHandler,
+ "Instantiator for $role role must return a SlotRoleHandler"
+ );
+
+ $this->handlers[$role] = $handler;
+ }
+ }
+
+ return $this->handlers[$role];
+ }
+
+ /**
+ * Returns the list of roles allowed when creating a new revision on the given page.
+ * The choice should not depend on external state, such as the page content.
+ * Note that existing revisions of that page are not guaranteed to comply with this list.
+ *
+ * All implementations of this method are required to return at least all "required" roles.
+ *
+ * @param LinkTarget $title
+ *
+ * @return string[]
+ */
+ public function getAllowedRoles( LinkTarget $title ) {
+ // TODO: allow this to be overwritten per namespace (or page type)
+ // TODO: decide how to control which slots are offered for editing per default (T209927)
+ return $this->getDefinedRoles();
+ }
+
+ /**
+ * Returns the list of roles required when creating a new revision on the given page.
+ * The should not depend on external state, such as the page content.
+ * Note that existing revisions of that page are not guaranteed to comply with this list.
+ *
+ * All required roles are implicitly considered "allowed", so any roles
+ * returned by this method will also be returned by getAllowedRoles().
+ *
+ * @param LinkTarget $title
+ *
+ * @return string[]
+ */
+ public function getRequiredRoles( LinkTarget $title ) {
+ // TODO: allow this to be overwritten per namespace (or page type)
+ return [ 'main' ];
+ }
+
+ /**
+ * Returns the list of roles defined by calling defineRole().
+ *
+ * This list should be used when enumerating slot roles that can be used for editing.
+ *
+ * @return string[]
+ */
+ public function getDefinedRoles() {
+ return array_keys( $this->instantiators );
+ }
+
+ /**
+ * Returns the list of known roles, including the ones returned by getDefinedRoles(),
+ * and roles that exist according to the NameTableStore provided to the constructor.
+ *
+ * This list should be used when enumerating slot roles that can be used in queries or
+ * for display.
+ *
+ * @return string[]
+ */
+ public function getKnownRoles() {
+ return array_unique( array_merge(
+ $this->getDefinedRoles(),
+ $this->roleNamesStore->getMap()
+ ) );
+ }
+
+ /**
+ * Whether the given role is defined, that is, it was defined by calling defineRole().
+ *
+ * @param string $role
+ * @return bool
+ */
+ public function isDefinedRole( $role ) {
+ return in_array( $role, $this->getDefinedRoles(), true );
+ }
+
+ /**
+ * Whether the given role is known, that is, it's either defined or exist according to
+ * the NameTableStore provided to the constructor.
+ *
+ * @param string $role
+ * @return bool
+ */
+ public function isKnownRole( $role ) {
+ return in_array( $role, $this->getKnownRoles(), true );
+ }
+
+}
use MediaWiki\MediaWikiServices;
use MediaWiki\Preferences\PreferencesFactory;
use MediaWiki\Preferences\DefaultPreferencesFactory;
+use MediaWiki\Revision\MainSlotRoleHandler;
use MediaWiki\Revision\RevisionFactory;
use MediaWiki\Revision\RevisionLookup;
+use MediaWiki\Revision\SlotRoleRegistry;
use MediaWiki\Revision\RevisionRenderer;
use MediaWiki\Revision\RevisionStore;
use MediaWiki\Revision\RevisionStoreFactory;
},
'RevisionRenderer' => function ( MediaWikiServices $services ) : RevisionRenderer {
- $renderer = new RevisionRenderer( $services->getDBLoadBalancer() );
- $renderer->setLogger( LoggerFactory::getInstance( 'SaveParse' ) );
+ $renderer = new RevisionRenderer(
+ $services->getDBLoadBalancer(),
+ $services->getSlotRoleRegistry()
+ );
+ $renderer->setLogger( LoggerFactory::getInstance( 'SaveParse' ) );
return $renderer;
},
$services->getDBLoadBalancerFactory(),
$services->getBlobStoreFactory(),
$services->getNameTableStoreFactory(),
+ $services->getSlotRoleRegistry(),
$services->getMainWANObjectCache(),
$services->getCommentStore(),
$services->getActorMigration(),
return $factory;
},
+ 'SlotRoleRegistry' => function ( MediaWikiServices $services ) : SlotRoleRegistry {
+ $config = $services->getMainConfig();
+
+ $registry = new SlotRoleRegistry(
+ $services->getNameTableStoreFactory()->getSlotRoles()
+ );
+
+ $registry->defineRole( 'main', function () use ( $config ) {
+ return new MainSlotRoleHandler(
+ $config->get( 'NamespaceContentModels' )
+ );
+ } );
+
+ return $registry;
+ },
+
'SpecialPageFactory' => function ( MediaWikiServices $services ) : SpecialPageFactory {
return new SpecialPageFactory(
$services->getMainConfig(),
use MediaWiki\Revision\RevisionRenderer;
use MediaWiki\Revision\RevisionSlots;
use MediaWiki\Revision\RevisionStore;
+use MediaWiki\Revision\SlotRoleRegistry;
use MediaWiki\Revision\SlotRecord;
use MediaWiki\User\UserIdentity;
use MessageCache;
*/
private $revisionRenderer;
+ /** @var SlotRoleRegistry */
+ private $slotRoleRegistry;
+
/**
* A stage identifier for managing the life cycle of this instance.
* Possible stages are 'new', 'knows-current', 'has-content', 'has-revision', and 'done'.
* @param WikiPage $wikiPage ,
* @param RevisionStore $revisionStore
* @param RevisionRenderer $revisionRenderer
+ * @param SlotRoleRegistry $slotRoleRegistry
* @param ParserCache $parserCache
* @param JobQueueGroup $jobQueueGroup
* @param MessageCache $messageCache
WikiPage $wikiPage,
RevisionStore $revisionStore,
RevisionRenderer $revisionRenderer,
+ SlotRoleRegistry $slotRoleRegistry,
ParserCache $parserCache,
JobQueueGroup $jobQueueGroup,
MessageCache $messageCache,
$this->parserCache = $parserCache;
$this->revisionStore = $revisionStore;
$this->revisionRenderer = $revisionRenderer;
+ $this->slotRoleRegistry = $slotRoleRegistry;
$this->jobQueueGroup = $jobQueueGroup;
$this->messageCache = $messageCache;
$this->contLang = $contLang;
$hasLinks = null;
if ( $this->articleCountMethod === 'link' ) {
+ // NOTE: it would be more appropriate to determine for each slot separately
+ // whether it has links, and use that information with that slot's
+ // isCountable() method. However, that would break parity with
+ // WikiPage::isCountable, which uses the pagelinks table to determine
+ // whether the current revision has links.
$hasLinks = (bool)count( $this->getCanonicalParserOutput()->getLinks() );
}
- // TODO: MCR: ask all slots if they have links [SlotHandler/PageTypeHandler]
- $mainContent = $this->getRawContent( SlotRecord::MAIN );
- return $mainContent->isCountable( $hasLinks );
+ foreach ( $this->getModifiedSlotRoles() as $role ) {
+ $roleHandler = $this->slotRoleRegistry->getRoleHandler( $role );
+ if ( $roleHandler->supportsArticleCount() ) {
+ $content = $this->getRawContent( $role );
+
+ if ( $content->isCountable( $hasLinks ) ) {
+ return true;
+ }
+ }
+ }
+
+ return false;
}
/**
*/
public function isRedirect() {
// NOTE: main slot determines redirect status
+ // TODO: MCR: this should be controlled by a PageTypeHandler
$mainContent = $this->getRawContent( SlotRecord::MAIN );
return $mainContent->isRedirect();
use ContentHandler;
use DeferredUpdates;
use Hooks;
-use InvalidArgumentException;
use LogicException;
use ManualLogEntry;
use MediaWiki\Linker\LinkTarget;
use MediaWiki\Revision\RevisionAccessException;
use MediaWiki\Revision\RevisionRecord;
use MediaWiki\Revision\RevisionStore;
+use MediaWiki\Revision\SlotRoleRegistry;
use MediaWiki\Revision\SlotRecord;
use MWException;
use RecentChange;
*/
private $revisionStore;
+ /**
+ * @var SlotRoleRegistry
+ */
+ private $slotRoleRegistry;
+
/**
* @var boolean see $wgUseAutomaticEditSummaries
* @see $wgUseAutomaticEditSummaries
* @param DerivedPageDataUpdater $derivedDataUpdater
* @param LoadBalancer $loadBalancer
* @param RevisionStore $revisionStore
+ * @param SlotRoleRegistry $slotRoleRegistry
*/
public function __construct(
User $user,
WikiPage $wikiPage,
DerivedPageDataUpdater $derivedDataUpdater,
LoadBalancer $loadBalancer,
- RevisionStore $revisionStore
+ RevisionStore $revisionStore,
+ SlotRoleRegistry $slotRoleRegistry
) {
$this->user = $user;
$this->wikiPage = $wikiPage;
$this->loadBalancer = $loadBalancer;
$this->revisionStore = $revisionStore;
+ $this->slotRoleRegistry = $slotRoleRegistry;
$this->slotsUpdate = new RevisionSlotsUpdate();
}
return $this->derivedDataUpdater->grabCurrentRevision();
}
- /**
- * @return string
- */
- private function getTimestampNow() {
- // TODO: allow an override to be injected for testing
- return wfTimestampNow();
- }
-
/**
* Check flags and add EDIT_NEW or EDIT_UPDATE to them as needed.
*
* @param Content $content
*/
public function setContent( $role, Content $content ) {
- // TODO: MCR: check the role and the content's model against the list of supported
- // roles, see T194046.
+ $this->ensureRoleAllowed( $role );
$this->slotsUpdate->modifyContent( $role, $content );
}
* @param SlotRecord $slot
*/
public function setSlot( SlotRecord $slot ) {
+ $this->ensureRoleAllowed( $slot->getRole() );
+
$this->slotsUpdate->modifySlot( $slot );
}
* by the new revision.
*/
public function inheritSlot( SlotRecord $originalSlot ) {
+ // NOTE: slots can be inherited even if the role is not "allowed" on the title.
// NOTE: this slot is inherited from some other revision, but it's
// a "modified" slot for the RevisionSlotsUpdate and DerivedPageDataUpdater,
// since it's not implicitly inherited from the parent revision.
* @param string $role A slot role name (but not "main")
*/
public function removeSlot( $role ) {
- if ( $role === SlotRecord::MAIN ) {
- throw new InvalidArgumentException( 'Cannot remove the main slot!' );
- }
+ $this->ensureRoleNotRequired( $role );
$this->slotsUpdate->removeSlot( $role );
}
throw new RuntimeException( 'Something is trying to edit an article with an empty title' );
}
- // TODO: MCR: check the role and the content's model against the list of supported
- // and required roles, see T194046.
+ // NOTE: slots can be inherited even if the role is not "allowed" on the title.
+ $status = Status::newGood();
+ $this->checkAllRolesAllowed(
+ $this->slotsUpdate->getModifiedRoles(),
+ $status
+ );
+ $this->checkNoRolesRequired(
+ $this->slotsUpdate->getRemovedRoles(),
+ $status
+ );
- // Make sure the given content type is allowed for this page
- // TODO: decide: Extend check to other slots? Consider the role in check? [PageType]
- $mainContentHandler = $this->getContentHandler( SlotRecord::MAIN );
- if ( !$mainContentHandler->canBeUsedOn( $this->getTitle() ) ) {
- $this->status = Status::newFatal( 'content-not-allowed-here',
- ContentHandler::getLocalizedName( $mainContentHandler->getModelID() ),
- $this->getTitle()->getPrefixedText()
- );
+ if ( !$status->isOK() ) {
return null;
}
+ // Make sure the given content is allowed in the respective slots of this page
+ foreach ( $this->slotsUpdate->getModifiedRoles() as $role ) {
+ $slot = $this->slotsUpdate->getModifiedSlot( $role );
+ $roleHandler = $this->slotRoleRegistry->getRoleHandler( $role );
+
+ if ( !$roleHandler->isAllowedModel( $slot->getModel(), $this->getTitle() ) ) {
+ $contentHandler = ContentHandler::getForModelID( $slot->getModel() );
+ $this->status = Status::newFatal( 'content-not-allowed-here',
+ ContentHandler::getLocalizedName( $contentHandler->getModelID() ),
+ $this->getTitle()->getPrefixedText(),
+ wfMessage( $roleHandler->getNameMessageKey() )
+ // TODO: defer message lookup to caller
+ );
+ return null;
+ }
+ }
+
// Load the data from the master database if needed. Needed to check flags.
// NOTE: This grabs the parent revision as the CAS token, if grabParentRevision
// wasn't called yet. If the page is modified by another process before we are done with
$content = $slot->getContent();
// XXX: We may push this up to the "edit controller" level, see T192777.
- // TODO: change the signature of PrepareSave to not take a WikiPage!
+ // XXX: prepareSave() and isValid() could live in SlotRoleHandler
+ // XXX: PrepareSave should not take a WikiPage!
$prepStatus = $content->prepareSave( $wikiPage, $flags, $oldid, $user );
// TODO: MCR: record which problem arose in which slot.
$status->merge( $prepStatus );
}
+ $this->checkAllRequiredRoles(
+ $rev->getSlotRoles(),
+ $status
+ );
+
return $rev;
}
);
}
+ /**
+ * @return string[] Slots required for this page update, as a list of role names.
+ */
+ private function getRequiredSlotRoles() {
+ return $this->slotRoleRegistry->getRequiredRoles( $this->getTitle() );
+ }
+
+ /**
+ * @return string[] Slots allowed for this page update, as a list of role names.
+ */
+ private function getAllowedSlotRoles() {
+ return $this->slotRoleRegistry->getAllowedRoles( $this->getTitle() );
+ }
+
+ private function ensureRoleAllowed( $role ) {
+ $allowedRoles = $this->getAllowedSlotRoles();
+ if ( !in_array( $role, $allowedRoles ) ) {
+ throw new PageUpdateException( "Slot role `$role` is not allowed." );
+ }
+ }
+
+ private function ensureRoleNotRequired( $role ) {
+ $requiredRoles = $this->getRequiredSlotRoles();
+ if ( in_array( $role, $requiredRoles ) ) {
+ throw new PageUpdateException( "Slot role `$role` is required." );
+ }
+ }
+
+ private function checkAllRolesAllowed( array $roles, Status $status ) {
+ $allowedRoles = $this->getAllowedSlotRoles();
+
+ $forbidden = array_diff( $roles, $allowedRoles );
+ if ( !empty( $forbidden ) ) {
+ $status->error(
+ 'edit-slots-cannot-add',
+ count( $forbidden ),
+ implode( ', ', $forbidden )
+ );
+ }
+ }
+
+ private function checkNoRolesRequired( array $roles, Status $status ) {
+ $requiredRoles = $this->getRequiredSlotRoles();
+
+ $needed = array_diff( $roles, $requiredRoles );
+ if ( !empty( $needed ) ) {
+ $status->error(
+ 'edit-slots-cannot-remove',
+ count( $needed ),
+ implode( ', ', $needed )
+ );
+ }
+ }
+
+ private function checkAllRequiredRoles( array $roles, Status $status ) {
+ $requiredRoles = $this->getRequiredSlotRoles();
+
+ $missing = array_diff( $requiredRoles, $roles );
+ if ( !empty( $missing ) ) {
+ $status->error(
+ 'edit-slots-missing',
+ count( $missing ),
+ implode( ', ', $missing )
+ );
+ }
+ }
+
}
/**
* Get the page's content model id, see the CONTENT_MODEL_XXX constants.
*
+ * @todo Deprecate this in favor of SlotRecord::getModel()
+ *
* @param int $flags A bit field; may be Title::GAID_FOR_UPDATE to select for update
* @return string Content model id
*/
}
$useReplica = ( $rigor !== 'secure' );
- $block = $user->getBlock( $useReplica );
-
- // The block may explicitly allow an action (like "read" or "upload").
- if ( $block && $block->prevents( $action ) === false ) {
- return $errors;
- }
-
- // Determine if the user is blocked from this action on this page.
- try {
+ if ( ( $action == 'edit' || $action == 'create' )
+ && !$user->isBlockedFrom( $this, $useReplica )
+ ) {
+ // Don't block the user from editing their own talk page unless they've been
+ // explicitly blocked from that too.
+ } elseif ( $user->isBlocked() && $user->getBlock()->prevents( $action ) !== false ) {
// @todo FIXME: Pass the relevant context into this function.
- $action = Action::factory( $action, WikiPage::factory( $this ), RequestContext::getMain() );
- } catch ( Exception $e ) {
- $action = null;
- }
-
- // If no action object is returned, assume that the action requires unblock
- // which is the default.
- if ( !$action || $action->requiresUnblock() ) {
- if ( $user->isBlockedFrom( $this, $useReplica ) ) {
- // @todo FIXME: Pass the relevant context into this function.
- $errors[] = $block
- ? $block->getPermissionsError( RequestContext::getMain() )
- : [ 'badaccess-group0' ];
- }
+ $errors[] = $user->getBlock()->getPermissionsError( RequestContext::getMain() );
}
return $errors;
/** @var RevisionStore */
private $revisionStore;
+ /** @var \MediaWiki\Revision\SlotRoleRegistry */
+ private $slotRoleRegistry;
+
private $guessedTitle = false, $props;
public function __construct( ApiMain $mainModule, $moduleName, $modulePrefix = '' ) {
parent::__construct( $mainModule, $moduleName, $modulePrefix );
$this->revisionStore = MediaWikiServices::getInstance()->getRevisionStore();
+ $this->slotRoleRegistry = MediaWikiServices::getInstance()->getSlotRoleRegistry();
}
public function execute() {
}
$guessedTitle = $this->guessTitle();
- if ( $guessedTitle && $role === SlotRecord::MAIN ) {
- // @todo: Use SlotRoleRegistry and do this for all slots
- return $guessedTitle->getContentModel();
+ if ( $guessedTitle ) {
+ return $this->slotRoleRegistry->getRoleHandler( $role )->getDefaultModel( $guessedTitle );
}
if ( isset( $params["fromcontentmodel-$role"] ) ) {
}
public function getAllowedParams() {
- $slotRoles = MediaWikiServices::getInstance()->getSlotRoleStore()->getMap();
- if ( !in_array( SlotRecord::MAIN, $slotRoles, true ) ) {
- $slotRoles[] = SlotRecord::MAIN;
- }
+ $slotRoles = $this->slotRoleRegistry->getKnownRoles();
sort( $slotRoles, SORT_STRING );
// Parameters for the 'from' and 'to' content
* @return string
*/
protected function feedItemDesc( RevisionRecord $revision ) {
- if ( $revision ) {
- $msg = wfMessage( 'colon-separator' )->inContentLanguage()->text();
- try {
- $content = $revision->getContent( SlotRecord::MAIN );
- } catch ( RevisionAccessException $e ) {
- $content = null;
- }
-
- if ( $content instanceof TextContent ) {
- // only textual content has a "source view".
- $html = nl2br( htmlspecialchars( $content->getNativeData() ) );
- } else {
- // XXX: we could get an HTML representation of the content via getParserOutput, but that may
- // contain JS magic and generally may not be suitable for inclusion in a feed.
- // Perhaps Content should have a getDescriptiveHtml method and/or a getSourceText method.
- // Compare also FeedUtils::formatDiffRow.
- $html = '';
- }
-
- $comment = $revision->getComment();
+ $msg = wfMessage( 'colon-separator' )->inContentLanguage()->text();
+ try {
+ $content = $revision->getContent( SlotRecord::MAIN );
+ } catch ( RevisionAccessException $e ) {
+ $content = null;
+ }
- return '<p>' . htmlspecialchars( $this->feedItemAuthor( $revision ) ) . $msg .
- htmlspecialchars( FeedItem::stripComment( $comment ? $comment->text : '' ) ) .
- "</p>\n<hr />\n<div>" . $html . '</div>';
+ if ( $content instanceof TextContent ) {
+ // only textual content has a "source view".
+ $html = nl2br( htmlspecialchars( $content->getNativeData() ) );
+ } else {
+ // XXX: we could get an HTML representation of the content via getParserOutput, but that may
+ // contain JS magic and generally may not be suitable for inclusion in a feed.
+ // Perhaps Content should have a getDescriptiveHtml method and/or a getSourceText method.
+ // Compare also FeedUtils::formatDiffRow.
+ $html = '';
}
- return '';
+ $comment = $revision->getComment();
+
+ return '<p>' . htmlspecialchars( $this->feedItemAuthor( $revision ) ) . $msg .
+ htmlspecialchars( FeedItem::stripComment( $comment ? $comment->text : '' ) ) .
+ "</p>\n<hr />\n<div>" . $html . '</div>';
}
public function getAllowedParams() {
}
public function getAllowedParams() {
- $slotRoles = MediaWikiServices::getInstance()->getSlotRoleStore()->getMap();
- if ( !in_array( SlotRecord::MAIN, $slotRoles, true ) ) {
- $slotRoles[] = SlotRecord::MAIN;
- }
+ $slotRoles = MediaWikiServices::getInstance()->getSlotRoleRegistry()->getKnownRoles();
sort( $slotRoles, SORT_STRING );
return [
"apihelp-block-param-allowusertalk": "A felhasználó szerkeszthesse a saját vitalapját (a <var>[[mw:Special:MyLanguage/Manual:$wgBlockAllowsUTEdit|$wgBlockAllowsUTEdit]]</var> beállítástól függ).",
"apihelp-block-param-reblock": "Jelenlegi blokk felülírása, ha a felhasználó már blokkolva van.",
"apihelp-block-param-watchuser": "A szerkesztő vagy IP-cím szerkesztői- és vitalapjának figyelése.",
+ "apihelp-block-param-tags": "A blokknapló naplóbejegyzésére érvényesítendő változtatáscímkék.",
+ "apihelp-block-param-partial": "Teljes blokk helyett a felhasználó eltiltása bizonyos lapok vagy névterek szerkesztésétől.",
+ "apihelp-block-param-pagerestrictions": "A felhasználó számára blokkolandó címek listája. Csak akkor van hatása, ha a <var>partial</var> igaz.",
"apihelp-block-example-ip-simple": "A <kbd>192.0.2.5</kbd> IP-cím blokkolása három napra <kbd>First strike</kbd> indoklással.",
"apihelp-block-example-user-complex": "<kbd>Vandal</kbd> blokkolása határozatlan időre <kbd>Vandalism</kbd> indoklással, új fiók létrehozásának és e-mail küldésének megakadályozása.",
"apihelp-checktoken-summary": "Egy <kbd>[[Special:ApiHelp/query+tokens|action=query&meta=tokens]]</kbd> kéréssel szerzett token érvényességének vizsgálata.",
*
* @param int|array $blockId
* @param IDatabase|null $db
- * @param array $options Options to pass to the select query.
* @return Restriction[]
*/
public static function loadByBlockId( $blockId, IDatabase $db = null ) {
/**
* Creates a new Restriction from a database row.
*
+ * @param \stdClass $row
* @return self
*/
public static function newFromRow( \stdClass $row );
* that it's also in a countable location (e.g. a current revision in the
* main namespace).
*
+ * @see SlotRoleHandler::supportsArticleCount
+ *
* @since 1.21
*
* @param bool|null $hasLinks If it is known whether this content contains
* Returns whether this Content represents a redirect.
* Shorthand for getRedirectTarget() !== null.
*
+ * @see SlotRoleHandler::supportsRedirects
+ *
* @since 1.21
*
* @return bool
* Note: this is used by, and may thus not use, Title::getContentModel()
*
* @since 1.21
+ * @deprecated since 1.33, use SlotRoleHandler::getDefaultModel() together with
+ * SlotRoleRegistry::getRoleHandler().
*
* @param Title $title
*
* @return string Default model name for the page given by $title
*/
public static function getDefaultModelFor( Title $title ) {
- // NOTE: this method must not rely on $title->getContentModel() directly or indirectly,
- // because it is used to initialize the mContentModel member.
-
- $ns = $title->getNamespace();
-
- $ext = false;
- $m = null;
- $model = MWNamespace::getNamespaceContentModel( $ns );
-
- // Hook can determine default model
- if ( !Hooks::run( 'ContentHandlerDefaultModelFor', [ $title, &$model ] ) ) {
- if ( !is_null( $model ) ) {
- return $model;
- }
- }
-
- // Could this page contain code based on the title?
- $isCodePage = NS_MEDIAWIKI == $ns && preg_match( '!\.(css|js|json)$!u', $title->getText(), $m );
- if ( $isCodePage ) {
- $ext = $m[1];
- }
-
- // Is this a user subpage containing code?
- $isCodeSubpage = NS_USER == $ns
- && !$isCodePage
- && preg_match( "/\\/.*\\.(js|css|json)$/", $title->getText(), $m );
- if ( $isCodeSubpage ) {
- $ext = $m[1];
- }
-
- // Is this wikitext, according to $wgNamespaceContentModels or the DefaultModelFor hook?
- $isWikitext = is_null( $model ) || $model == CONTENT_MODEL_WIKITEXT;
- $isWikitext = $isWikitext && !$isCodePage && !$isCodeSubpage;
-
- if ( !$isWikitext ) {
- switch ( $ext ) {
- case 'js':
- return CONTENT_MODEL_JAVASCRIPT;
- case 'css':
- return CONTENT_MODEL_CSS;
- case 'json':
- return CONTENT_MODEL_JSON;
- default:
- return is_null( $model ) ? CONTENT_MODEL_TEXT : $model;
- }
- }
-
- // We established that it must be wikitext
-
- return CONTENT_MODEL_WIKITEXT;
+ $slotRoleregistry = MediaWikiServices::getInstance()->getSlotRoleRegistry();
+ $mainSlotHandler = $slotRoleregistry->getRoleHandler( 'main' );
+ return $mainSlotHandler->getDefaultModel( $title );
}
/**
/**
* Determines whether the content type handled by this ContentHandler
- * can be used on the given page.
+ * can be used for the main slot of the given page.
*
* This default implementation always returns true.
* Subclasses may override this to restrict the use of this content model to specific locations,
* @note this calls the ContentHandlerCanBeUsedOn hook which may be used to override which
* content model can be used where.
*
+ * @see SlotRoleHandler::isAllowedModel
+ *
* @param Title $title The page's title.
*
* @return bool True if content of this kind can be used on the given page, false otherwise.
* @param string $host Syslog host
* @param int $port Syslog port
* @param int $facility Syslog message facility
- * @param string $level The minimum logging level at which this handler
+ * @param int $level The minimum logging level at which this handler
* will be triggered
* @param bool $bubble Whether the messages that are handled can bubble up
* the stack or not
$slotDiff = $slotDiffRenderer->getDiff( $slotContents[$role]['old'],
$slotContents[$role]['new'] );
if ( $slotDiff && $role !== SlotRecord::MAIN ) {
- // TODO use human-readable role name at least
+ // FIXME: ask SlotRoleHandler::getSlotNameMessage
$slotTitle = $role;
$difftext .= $this->getSlotHeader( $slotTitle );
}
/**
* Override to write to a different stream type.
* @param string $string
- * @return bool
*/
function write( $string ) {
print $string;
/**
* Relative Date Time Field.
+ * @param array $params
*/
public function __construct( array $params = [] ) {
parent::__construct( $params );
/**
* Get options and make them into arrays suitable for OOUI.
- * @return array Options for inclusion in a select or whatever.
+ * @throws MWException
*/
public function getOptionsOOUI() {
// Sections make this difficult. See getInputOOUI().
* @return bool Success
*/
public function streamFile( $headers = [] ) {
- $this->streamFileWithStatus( $headers )->isOK();
+ return $this->streamFileWithStatus( $headers )->isOK();
}
/**
use MediaWiki\Revision\RevisionRecord;
use MediaWiki\Revision\RevisionRenderer;
use MediaWiki\Revision\RevisionStore;
+use MediaWiki\Revision\SlotRoleRegistry;
use MediaWiki\Revision\SlotRecord;
use MediaWiki\Storage\DerivedPageDataUpdater;
use MediaWiki\Storage\PageUpdater;
return MediaWikiServices::getInstance()->getRevisionRenderer();
}
+ /**
+ * @return SlotRoleRegistry
+ */
+ private function getSlotRoleRegistry() {
+ return MediaWikiServices::getInstance()->getSlotRoleRegistry();
+ }
+
/**
* @return ParserCache
*/
// links.
$hasLinks = (bool)count( $editInfo->output->getLinks() );
} else {
- // NOTE: keep in sync with revisionRenderer::getLinkCount
+ // NOTE: keep in sync with RevisionRenderer::getLinkCount
+ // NOTE: keep in sync with DerivedPageDataUpdater::isCountable
$hasLinks = (bool)wfGetDB( DB_REPLICA )->selectField( 'pagelinks', 1,
[ 'pl_from' => $this->getId() ], __METHOD__ );
}
}
+ // TODO: MCR: determine $hasLinks for each slot, and use that info
+ // with that slot's Content's isCountable method. That requires per-
+ // slot ParserOutput in the ParserCache, or per-slot info in the
+ // pagelinks table.
return $content->isCountable( $hasLinks );
}
$this, // NOTE: eventually, PageUpdater should not know about WikiPage
$this->getRevisionStore(),
$this->getRevisionRenderer(),
+ $this->getSlotRoleRegistry(),
$this->getParserCache(),
JobQueueGroup::singleton(),
MessageCache::singleton(),
$this, // NOTE: eventually, PageUpdater should not know about WikiPage
$this->getDerivedDataUpdater( $user, null, $forUpdate, true ),
$this->getDBLoadBalancer(),
- $this->getRevisionStore()
+ $this->getRevisionStore(),
+ $this->getSlotRoleRegistry()
);
$pageUpdater->setUsePageCreationLog( $wgPageCreationLog );
if ( $stash ) {
$this->stash = $stash;
} else {
- if ( $user ) {
- wfDebug( __METHOD__ . " creating new UploadFromChunks instance for " . $user->getId() . "\n" );
- } else {
- wfDebug( __METHOD__ . " creating new UploadFromChunks instance with no user\n" );
- }
+ wfDebug( __METHOD__ . " creating new UploadFromChunks instance for " . $user->getId() . "\n" );
$this->stash = new UploadStash( $this->repo, $this->user );
}
}
while ( $hebrewMonth <= 12 ) {
# Calculate days in this month
if ( $isLeap && $hebrewMonth == 6 ) {
- # Adar in a leap year
- if ( $isLeap ) {
- # Leap year - has Adar I, with 30 days, and Adar II, with 29 days
- $days = 30;
+ # Leap year - has Adar I, with 30 days, and Adar II, with 29 days
+ $days = 30;
+ if ( $hebrewDay <= $days ) {
+ # Day in Adar I
+ $hebrewMonth = 13;
+ } else {
+ # Subtract the days of Adar I
+ $hebrewDay -= $days;
+ # Try Adar II
+ $days = 29;
if ( $hebrewDay <= $days ) {
- # Day in Adar I
- $hebrewMonth = 13;
- } else {
- # Subtract the days of Adar I
- $hebrewDay -= $days;
- # Try Adar II
- $days = 29;
- if ( $hebrewDay <= $days ) {
- # Day in Adar II
- $hebrewMonth = 14;
- }
+ # Day in Adar II
+ $hebrewMonth = 14;
}
}
} elseif ( $hebrewMonth == 2 && $yearPattern == 2 ) {
"welcomecreation-msg": "Akun-neuh ka geupeugöt. \nDroeneuh jeuet neugantoe {{SITENAME}} [[Special:Preferences|peuatô]] meunyö neumeuh'eut.",
"yourname": "Ureuëng ngui:",
"userlogin-yourname": "Ureuëng ngui",
- "userlogin-yourname-ph": "Peutamöng nan ureuëng ngui droëneuh",
+ "userlogin-yourname-ph": "Peutamöng nan ureueng ngui",
"createacct-another-username-ph": "Pasoë nan ureuëng ngui droëneuh",
"yourpassword": "Lageuem tamöng:",
"userlogin-yourpassword": "Lageuem tamöng",
- "userlogin-yourpassword-ph": "Pasoe lageuem tamöng droeneuh",
+ "userlogin-yourpassword-ph": "Pasoe lageuem tamöng",
"createacct-yourpassword-ph": "Pasoe lageuem tamöng",
"yourpasswordagain": "Pasoe lom lageuem tamöng:",
"createacct-yourpasswordagain": "Peunyo lageuem tamöng",
"sp-contributions-logs": "log",
"sp-contributions-talk": "marit",
"sp-contributions-search": "Mita soë nyang tuléh",
- "sp-contributions-username": "Alamat IP atawa nan ureuëng ngui:",
+ "sp-contributions-username": "Alamat IP atawa nan ureueng ngui:",
"sp-contributions-toponly": "Peuleumah geunantoe nyang baro mantong",
"sp-contributions-newonly": "Peuleumah pumeugöt laman mantöng",
"sp-contributions-hideminor": "Peusom peusaneut bacut",
"rcfilters-filterlist-feedbacklink": "بیزه بو فیلترلره گؤره دوشوندوگونوزو بیلیندیرین!",
"rcfilters-highlightbutton-title": "نتیجهلری هایلایتلا",
"rcfilters-filtergroup-authorship": "دییشدیرن",
+ "rcfilters-filtergroup-userExpLevel": "ایشلدن آدیازدیرما و تجروبهسی",
+ "rcfilters-filter-user-experience-level-registered-label": "آدیازدیریلمیش",
+ "rcfilters-filter-user-experience-level-unregistered-label": "آدیازدیریلمامیش",
+ "rcfilters-filter-user-experience-level-learner-label": "اؤیرننلر",
+ "rcfilters-filter-user-experience-level-experienced-label": "تجروبهلی ایشلدنلر",
"rcfilters-filtergroup-automated": "اوْتوماتیک دییشدیرمهلر",
"rcfilters-filter-humans-label": "اینسان (غئیر روْبات)",
"rcfilters-filter-humans-description": "اینسان اَلی ایله دییشدیرمهلر",
"rcfilters-filtergroup-reviewstatus": "یوخلاما وضعیتی",
"rcfilters-filter-minor-label": "کیچیک دَییشدیرمهلر",
+ "rcfilters-filter-major-label": "کیچیک اوْلمایان دییشدیرمهلر",
"rcfilters-filtergroup-watchlist": "ایزلنمیش صفحهلر",
"rcfilters-filter-watchlist-watched-label": "ایزلنمیش",
"rcfilters-filtergroup-changetype": "دَییشیکلیک نوعو",
"localtime": "Мясцовы час:",
"timezoneuseserverdefault": "Выкарыстоўваць стандартныя налады {{GRAMMAR:родны|{{SITENAME}}}} ($1)",
"timezoneuseoffset": "Іншы (пазначце ніжэй розьніцу ў часе)",
+ "timezone-useoffset-placeholder": "Напрыклад: «-07:00» ці «01:00»",
"servertime": "Час на сэрвэры:",
"guesstimezone": "Запоўніць з браўзэра",
"timezoneregion-africa": "Афрыка",
"view": "Паказ",
"view-foreign": "Глядзець на $1",
"edit": "Правіць",
- "edit-local": "Правіць тутэйшае апісанне",
+ "edit-local": "Правіць лакальнае апісанне",
"create": "Стварыць",
- "create-local": "Дадаць тутэйшае апісанне",
+ "create-local": "Дадаць лакальнае апісанне",
"delete": "Выдаліць",
"undelete_short": "Аднавіць {{PLURAL:$1|адну праўку|$1 правак}}",
"viewdeleted_short": "Паказаць {{PLURAL:$1|адну сцёртую праўку|$1 сцёртыя праўкі}}",
"timezonelegend": "Časové pásmo:",
"localtime": "Místní čas:",
"timezoneuseserverdefault": "Použít časové pásmo wiki ($1)",
- "timezoneuseoffset": "Jiné (zadejte posun)",
+ "timezoneuseoffset": "Jiné (níže zadejte posun)",
+ "timezone-useoffset-placeholder": "Příklady hodnot: „-07:00“ nebo „01:00“",
"servertime": "Čas na serveru:",
"guesstimezone": "Načíst z prohlížeče",
"timezoneregion-africa": "Afrika",
"tog-watchlisthideminor": "Skjul mindre ændringer i overvågningslisten",
"tog-watchlisthideliu": "Skjul indloggede brugeres redigeringer i overvågningslisten",
"tog-watchlistreloadautomatically": "Opdater overvågningslisten automatisk, når et filter ændres (kræver JavaScript)",
- "tog-watchlistunwatchlinks": "Tilføj direkte henvisninger for at overvåge/fjerne overvågning til overvågningsposter (JavaScript krævet for at skifte funktionalitet)",
+ "tog-watchlistunwatchlinks": "Føj mærker ({{int:Watchlist-unwatch}}/{{int:Watchlist-unwatch-undo}}) til at slå overvågning til og fra for overvågede sider med ændringer (JavaScript kræves for at kunne slå til og fra)",
"tog-watchlisthideanons": "Skjul anonyme brugeres redigeringer i overvågningslisten",
"tog-watchlisthidepatrolled": "Skjul patruljerede ændringer i overvågningslisten",
"tog-watchlisthidecategorization": "Skjul kategorisering af sider",
"localtime": "Τοπική ώρα:",
"timezoneuseserverdefault": "Χρήση της προεπιλογής του wiki ($1)",
"timezoneuseoffset": "Ἀλλη (καθορισμός της διαφοράς)",
+ "timezone-useoffset-placeholder": "Τιμές ως παράδειγμα: \"-07:00\" or \"01:00\"",
"servertime": "Η ώρα του διακομιστή:",
"guesstimezone": "Συμπλήρωση μέσω του browser",
"timezoneregion-africa": "Αφρική",
"edit-gone-missing": "Could not update the page.\nIt appears to have been deleted.",
"edit-conflict": "Edit conflict.",
"edit-no-change": "Your edit was ignored because no change was made to the text.",
+ "edit-slots-cannot-add": "The following {{PLURAL:$1|slot is|slots are}} not supported here: $2.",
+ "edit-slots-cannot-remove": "The following {{PLURAL:$1|slot is|slots are}} required and cannot be removed: $2.",
+ "edit-slots-missing": "The following {{PLURAL:$1|slot is|slots are}} missing: $2.",
"postedit-confirmation-created": "The page has been created.",
"postedit-confirmation-restored": "The page has been restored.",
"postedit-confirmation-saved": "Your edit was saved.",
"defaultmessagetext": "Default message text",
"content-failed-to-parse": "Failed to parse $2 content for $1 model: $3",
"invalid-content-data": "Invalid content data",
- "content-not-allowed-here": "\"$1\" content is not allowed on page [[$2]]",
+ "content-not-allowed-here": "\"$1\" content is not allowed on page [[$2]] in slot \"$3\"",
"editwarning-warning": "Leaving this page may cause you to lose any changes you have made.\nIf you are logged in, you can disable this warning in the \"{{int:prefs-editing}}\" section of your preferences.",
"editpage-invalidcontentmodel-title": "Content model not supported",
"editpage-invalidcontentmodel-text": "The content model \"$1\" is not supported.",
"filedesc": "Resumo",
"fileuploadsummary": "Resumo:",
"filereuploadsummary": "Dosieraj ŝanĝoj:",
- "filestatus": "Aŭtorrajta statuso:",
+ "filestatus": "Aŭtorrajta stato:",
"filesource": "Fonto:",
"ignorewarning": "Ignori averton kaj konservi dosieron ĉiukaze",
"ignorewarnings": "Ignori ĉiajn avertojn",
"exif-nickname": "Malformala nomo de bildo",
"exif-rating": "Taksado (el 5)",
"exif-rightscertificate": "Atestilo de rajtoj-administrado",
- "exif-copyrighted": "Aŭtorrajta statuso:",
+ "exif-copyrighted": "Aŭtorrajta stato:",
"exif-copyrightowner": "Posedanto de la aŭtorrajto",
"exif-usageterms": "Regularo pri uzado",
"exif-webstatement": "Interreta deklarado pri aŭtorrajtoj",
"version-license-not-found": "Por tiu ĉi etendilo ne estis trovitaj pli detalaj permesilaj informoj.",
"version-credits-title": "Agnosko por $1",
"version-credits-not-found": "Por tiu ĉi etendilo ne estis trovitaj pli detalaj informoj pri aŭtoroj.",
- "version-poweredby-credits": "Ĉi tiu vikio funkcias per '''[https://www.mediawiki.org/ MediaWiki]''', aŭtorrajto © 2001–$1 $2.",
+ "version-poweredby-credits": "Ĉi tiu vikio funkcias per <strong>[https://www.mediawiki.org/ MediaWiki]</strong>, aŭtorrajto © 2001–$1 $2.",
"version-poweredby-others": "aliaj",
"version-poweredby-translators": "tradukantoj de translatewiki.net",
"version-credits-summary": "Ni ŝatus agnoski la sekvajn personojn pro siaj kontribuoj al [[Special:Version|MediaWiki]].",
"prefs-displaywatchlist": "Näyttöasetukset",
"prefs-changesrc": "Näytettävät muutokset",
"prefs-changeswatchlist": "Näytettävät muutokset",
- "prefs-pageswatchlist": "Tarkkailtavat sivut",
+ "prefs-pageswatchlist": "Tarkkaillut sivut",
"prefs-tokenwatchlist": "Avain",
"prefs-diffs": "Eroavaisuudet",
"prefs-help-prefershttps": "Tämä asetus tulee voimaan seuraavan sisäänkirjautumisesi yhteydessä.",
"subject-preview": "Tárgy előnézete:",
"previewerrortext": "Hiba történt a változások előnézetének megjelenítése során.",
"blockedtitle": "A szerkesztő blokkolva van",
+ "blocked-email-user": "<strong>Szerkesztőneved számára az e-mail küldési lehetőséget blokkoltuk. Továbbra is szerkeszthetsz egyéb lapokat.</strong> A blokkolás további részleteit a [[Special:MyContributions|fiók közreműködéseinél]] találod.\n\nA blokkolást $1 hajtotta végre.\n\nAz általa megadott indok: <em>$2.</em>\n\n* A blokk kezdete: $8\n* A blokk lejárata: $6\n* Blokkolt szerkesztő: $7\n* A blokkolás azonosítószáma: $5",
+ "blockedtext-partial": "<strong>Szerkesztőneved vagy IP-címed számára az oldal szerkesztését blokkoltuk. Továbbra is szerkeszthetsz egyéb lapokat.</strong> A blokkolás további részleteit a [[Special:MyContributions|fiók közreműködéseinél]] találod.\n\nA blokkolást $1 hajtotta végre.\n\nAz általa megadott indok: <em>$2.</em>\n\n* A blokk kezdete: $8\n* A blokk lejárata: $6\n* Blokkolt szerkesztő: $7\n* A blokkolás azonosítószáma: $5",
"blockedtext": "<strong>A szerkesztőnevedet vagy az IP-címedet blokkoltuk.</strong>\n\nA blokkolást $1 végezte el.\nAz általa felhozott indok: <em>$2.</em>\n\n* A blokk kezdete: $8\n* A blokk lejárata: $6\n* Blokkolt szerkesztő: $7\n\nKapcsolatba léphetsz $1 szerkesztőnkkel vagy egy másik [[{{MediaWiki:Grouppage-sysop}}|adminisztrátorral]], és megbeszélheted vele a blokkolást.\nAz „{{int:emailuser}}” funkciót csak akkor használhatod, ha érvényes e-mail-címet adtál meg [[Special:Preferences|fiókbeállításaidban]], és nem blokkolták a használatát.\nJelenlegi IP-címed: $3, a blokkolás azonosítószáma: #$5.\nKérjük, hogy érdeklődés esetén minden fenti részletet adj meg.",
"autoblockedtext": "Az IP-címed automatikusan blokkolva lett, mert korábban egy olyan szerkesztő használta, akit $1 blokkolt, az alábbi indoklással:\n\n:''$2''\n\n*A blokk kezdete: '''$8'''\n*A blokk lejárata: '''$6'''\n*Blokkolt szerkesztő: '''$7'''\n\nKapcsolatba léphetsz $1 szerkesztőnkkel, vagy egy másik [[{{MediaWiki:Grouppage-sysop}}|adminisztrátorral]], és megbeszélheted vele a blokkolást.\n\nAz „{{int:emailuser}}” funkciót csak akkor használhatod, ha érvényes e-mail címet adtál meg\n[[Special:Preferences|fiókbeállításaidban]], és nem blokkolták a használatát.\n\nJelenlegi IP-címed: $3, a blokkolás azonosítószáma: #$5.\nKérjük, hogy érdeklődés esetén mindkettőt add meg.",
"systemblockedtext": "A felhasználónevedet vagy IP-címedet automatikusan blokkolta a MediaWiki.\nA blokkolás indoka:\n\n:<em>$2</em>\n\n* A blokk kezdete: $8\n* A blokk lejárata: $6\n* Blokkolt szerkesztő: $7\n\nA jelenlegi IP-címed: $3.\nKérjük, hogy érdeklődés esetén minden fenti részletet adj meg.",
"converter-manual-rule-error": "Hiba van a kézi nyelvi konverziós szabályban",
"undo-success": "A szerkesztés visszavonható. Kérlek ellenőrizd alább a változásokat, hogy valóban ezt szeretnéd-e tenni, majd kattints a lap mentése gombra a visszavonás véglegesítéséhez.",
"undo-failure": "A szerkesztést nem lehet automatikusan visszavonni vele ütköző későbbi szerkesztések miatt.",
+ "undo-main-slot-only": "A szerkesztést nem lehet automatikusan visszavonni, mert érinti a tartalom más részét is.",
"undo-norev": "A szerkesztés nem állítható vissza, mert nem létezik vagy törölve lett.",
"undo-nochange": "A szerkesztés már vissza lett állítva.",
"undo-summary": "Visszavontam [[Special:Contributions/$2|$2]] ([[User talk:$2|vita]]) szerkesztését (oldid: $1)",
"localtime": "Helyi idő:",
"timezoneuseserverdefault": "Az alapértelmezett beállítás használata ($1)",
"timezoneuseoffset": "Egyéb (eltérés megadása)",
+ "timezone-useoffset-placeholder": "Példaértékek: „-07:00” vagy „01:00”",
"servertime": "A kiszolgáló ideje:",
"guesstimezone": "Töltse ki a böngésző",
"timezoneregion-africa": "Afrika",
"ipb-disableusertalk": "Megakadályozza, hogy a felhasználó szerkeszthesse a saját vitalapját, miközben blokkolva van",
"ipb-change-block": "Blokk beállításainak megváltoztatása",
"ipb-confirm": "Blokk megerősítése",
+ "ipb-sitewide": "Teljes körű",
+ "ipb-partial": "Részleges",
"ipb-type-label": "Típus",
"ipb-pages-label": "Lapok",
"badipaddress": "Érvénytelen IP-cím",
"createaccountblock": "új felhasználó létrehozása blokkolva",
"emailblock": "e-mail-cím blokkolva",
"blocklist-nousertalk": "nem szerkesztheti a vitalapját",
+ "blocklist-editing": "szerkesztés",
+ "blocklist-editing-sitewide": "szerkesztés (teljes körű)",
"ipblocklist-empty": "A blokkoltak listája üres.",
"ipblocklist-no-results": "A kért IP-cím vagy felhasználónév nem blokkolt.",
"blocklink": "blokkolás",
"move-watch": "Figyeld a lapot",
"movepagebtn": "Lap átnevezése",
"pagemovedsub": "Az átnevezés sikerült",
+ "cannotmove": "A lapot nem sikerült átnevezni a következő {{PLURAL:$1|ok|okok}} miatt:",
"movepage-moved": "'''„$1” átnevezve „$2” névre'''",
"movepage-moved-redirect": "Átirányítás létrehozva.",
"movepage-moved-noredirect": "A régi címről nem készült átirányítás.",
+ "movepage-delete-first": "A céloldal túl sok változattal rendelkezik ahhoz, ezért az átnevezés részeként nem törölhető. Kérlek, előbb töröld a lapot kézzel, aztán próbáld újra.",
"articleexists": "Ilyen névvel már létezik lap, vagy az általad választott név érvénytelen.\nKérlek, válassz egy másik nevet.",
"cantmove-titleprotected": "Nem nevezheted át a lapot, mert az új cím le van védve a létrehozás ellen.",
"movetalk": "Nevezd át a vitalapot is, ha lehetséges",
"confirm-mcrundo-title": "Egy változtatás visszavonva",
"mcrundofailed": "A visszavonás nem sikerült",
"mcrundo-missingparam": "Kötelező paraméterek hiányoznak a kérésből.",
+ "mcrundo-changed": "A változtatások megtekintése óta az oldal megváltozott. Kérlek, tekintsd meg az új változtatásokat.",
"ellipsis": "…",
"quotation-marks": "„$1”",
"imgmultipageprev": "← előző oldal",
"logentry-block-block": "$1 {{GENDER:$2|blokkolta}} „{{GENDER:$4|$3}}”-t $5 időtartamra $6",
"logentry-block-unblock": "$1 {{GENDER:$2|feloldotta}} {{GENDER:$4|$3}} blokkolását",
"logentry-block-reblock": "$1 {{GENDER:$2|módosította}} a blokk beállításokat „{{GENDER:$4|$3}}” szerkesztőnél $5 időtartamra $6",
+ "logentry-partialblock-block": "$1 {{GENDER:$2|blokkolta}} „{{GENDER:$4|$3}}”-t $5 időtartamra $6 a következő {{PLURAL:$8|lap|lapok}} szerkesztésétől: $7",
+ "logentry-partialblock-reblock": "$1 {{GENDER:$2|módosította}} a(z) $7 {{PLURAL:$8|lap|lapok}} szerkesztését megakadályozó blokk beállítását „{{GENDER:$4|$3}}” szerkesztőnél $5 időtartamra $6",
+ "logentry-non-editing-block-block": "$1 {{GENDER:$2|blokkolta}} „{{GENDER:$4|$3}}”-t nem-szerkesztési műveletektől $5 időtartamra $6",
+ "logentry-non-editing-block-reblock": "$1 {{GENDER:$2|módosította}} a nem-szerkesztési műveletekre vonatkozó blokk beállításait „{{GENDER:$4|$3}}” szerkesztőnél $5 időtartamra $6",
"logentry-suppress-block": "$1 {{GENDER:$2|blokkolta}} „{{GENDER:$4|$3}}”-t $5 időtartamra $6",
"logentry-suppress-reblock": "$1 {{GENDER:$2|módosította}} a blokk beállításokat „{{GENDER:$4|$3}}” szerkesztőnél $5 időtartamra $6",
"logentry-import-upload": "$1 {{GENDER:$2|importálta}} $3 lapot fájl feltöltéssel",
"timezonelegend": "Часовен појас:",
"localtime": "Месно време:",
"timezoneuseserverdefault": "Од викито ($1)",
- "timezoneuseoffset": "Друго (посочете отстапување)",
+ "timezoneuseoffset": "Друго (подолу посочете отстапување)",
+ "timezone-useoffset-placeholder": "Примерни вредности: „-07:00“ или „01:00“",
"servertime": "Време на опслужувачот:",
"guesstimezone": "Пополни од прелистувачот",
"timezoneregion-africa": "Африка",
"timezonelegend": "Tijdzone:",
"localtime": "Plaatselijke tijd:",
"timezoneuseserverdefault": "Wikistandaard gebruiken ($1)",
- "timezoneuseoffset": "Anders (tijdverschil opgeven)",
+ "timezoneuseoffset": "Anders (vul tijdverschil hier beneden in)",
+ "timezone-useoffset-placeholder": "Voorbeeldinvoer: \"-07:00\" of \"01:00\"",
"servertime": "Servertijd:",
"guesstimezone": "Vanuit de browser toevoegen",
"timezoneregion-africa": "Afrika",
"protect-expiry-options": "1 time:1 hour,1 dag:1 day,1 veke:1 week,2 veker:2 weeks,1 månad:1 month,3 månader:3 months,6 månader:6 months,1 år:1 year,endelaus:infinite",
"restriction-type": "Tilgang:",
"restriction-level": "Avgrensingsnivå:",
- "minimum-size": "Minimumstorleik",
- "maximum-size": "Maksimumstorleik:",
+ "minimum-size": "Minstestorleik",
+ "maximum-size": "Størstestorleik:",
"pagesize": "(byte)",
"restriction-edit": "Endring",
"restriction-move": "Flytting",
"edit-gone-missing": "Used as error message.\n\nSee also:\n* {{msg-mw|edit-hook-aborted}}\n* {{msg-mw|edit-conflict}}\n* {{msg-mw|edit-no-change}}\n* {{msg-mw|edit-already-exists}}",
"edit-conflict": "An 'Edit conflict' happens when more than one edit is being made to a page at the same time. This would usually be caused by separate individuals working on the same page. However, if the system is slow, several edits from one individual could back up and attempt to apply simultaneously - causing the conflict.\n\nSee also:\n* {{msg-mw|edit-hook-aborted}}\n* {{msg-mw|edit-gone-missing}}\n* {{msg-mw|edit-no-change}}\n* {{msg-mw|edit-already-exists}}",
"edit-no-change": "Used as error message.\n\nSee also:\n* {{msg-mw|edit-hook-aborted}}\n* {{msg-mw|edit-gone-missing}}\n* {{msg-mw|edit-conflict}}\n* {{msg-mw|edit-already-exists}}",
+ "edit-slots-cannot-add": "An error message shown when trying to save an edit, if the edit tries to add a {{Identical|slot}} that is not allowed on the page.\n* $1 - the number of slots\n* $2 - the slots that were attempted to be added but are not allowed",
+ "edit-slots-cannot-remove": "An error message shown when trying to save an edit, if the edit tries to remove a {{Identical|slot}} that is required on the page.\n* $1 - the number of slots\n* $2 - the slots that were attempted to be removed but are required",
+ "edit-slots-missing": "An error message shown when trying to save an edit, if the edit is missing some required {{Identical|slot}}, which could not be inherited from a parent revision.\n* $1 - the number of slots\n* $2 - the slots that are required but missing from the new revision",
"postedit-confirmation-created": "{{gender}}\nShown after a user creates a new page. Parameters:\n* $1 - the current user, for GENDER support",
"postedit-confirmation-restored": "{{gender}}\nShown after a user restores a page to a previous revision. Parameters:\n* $1 - the current user, for GENDER support",
"postedit-confirmation-saved": "{{gender}}\nShown after a user saves a page. Parameters:\n* $1 - the current user, for GENDER support",
"defaultmessagetext": "Caption above the default message text shown on the left-hand side of a diff displayed after clicking \"Show changes\" when creating a new page in the MediaWiki: namespace",
"content-failed-to-parse": "Error message indicating that the page's content can not be saved because it is syntactically invalid. This may occurr for content types using serialization or a strict markup syntax.\n\nParameters:\n* $1 – content model, any one of the following messages:\n** {{msg-mw|Content-model-wikitext}}\n** {{msg-mw|Content-model-javascript}}\n** {{msg-mw|Content-model-css}}\n** {{msg-mw|Content-model-json}}\n** {{msg-mw|Content-model-text}}\n* $2 – content format as MIME type (e.g. <code>text/css</code>)\n* $3 – specific error message",
"invalid-content-data": "Error message indicating that the page's content can not be saved because it is invalid. This may occurr for content types with internal consistency constraints.",
- "content-not-allowed-here": "Error message indicating that the desired content model is not supported in given localtion.\n* $1 - the human readable name of the content model: {{msg-mw|Content-model-wikitext}}, {{msg-mw|Content-model-javascript}}, {{msg-mw|Content-model-json}}, {{msg-mw|Content-model-css}} or {{msg-mw|Content-model-text}}\n* $2 - the title of the page in question",
+ "content-not-allowed-here": "Error message indicating that the desired content model is not supported in given localtion.\n* $1 - the human readable name of the content model: {{msg-mw|Content-model-wikitext}}, {{msg-mw|Content-model-javascript}}, {{msg-mw|Content-model-json}}, {{msg-mw|Content-model-css}} or {{msg-mw|Content-model-text}}\n* $2 - the title of the page in question\n* $3 - the role name of the slot the content is not allowed in",
"editwarning-warning": "Uses {{msg-mw|Prefs-editing}}",
"editpage-invalidcontentmodel-title": "Title of error page shown when using an unrecognized content model on EditPage",
"editpage-invalidcontentmodel-text": "Error message shown when using an unrecognized content model on EditPage. $1 is the user's invalid input",
"timezonelegend": "Часовой пояс:",
"localtime": "Местное время:",
"timezoneuseserverdefault": "Использовать настройки сервера ($1)",
- "timezoneuseoffset": "Иное (укажите смещение)",
+ "timezoneuseoffset": "Иное (ниже укажите смещение)",
+ "timezone-useoffset-placeholder": "Например: «-07:00» или «01:00»",
"servertime": "Время сервера:",
"guesstimezone": "Заполнить из браузера",
"timezoneregion-africa": "Африка",
"timezonelegend": "Časovni pas",
"localtime": "Krajevni čas:",
"timezoneuseserverdefault": "Uporabi privzeti wiki čas ($1)",
- "timezoneuseoffset": "Drugo (navedite izravnavo)",
+ "timezoneuseoffset": "Drugo (spodaj navedite odmik)",
+ "timezone-useoffset-placeholder": "Primera vrednosti: »-07:00« ali »01:00«",
"servertime": "Strežniški čas:",
"guesstimezone": "Izpolni iz brskalnika",
"timezoneregion-africa": "Afrika",
"lastmodifiedat": "Ова страница је последњи пут уређена на датум $1 у $2 ч.",
"viewcount": "Овој страници је приступљено {{PLURAL:$1|једанпут|$1 пута}}.",
"protectedpage": "Заштићена страница",
- "jumpto": "Ð\98ди на:",
+ "jumpto": "Ð\9fÑ\80еÑ\92и на:",
"jumptonavigation": "навигацију",
"jumptosearch": "претрагу",
"view-pool-error": "Сервери су тренутно преоптерећени.\nПревише корисника покушава да види ову страницу.\nСачекајте неко време пре него што поново покушате да јој приступите.\n\n$1",
"updated": "(ажурирано)",
"note": "<strong>Напомена:</strong>",
"previewnote": "<strong>Не заборавите да је ово само претпреглед.</strong>\nВаше промене још нису сачуване!",
- "continue-editing": "Ð\98ди на Ñ\83Ñ\80еÑ\92иваÑ\87ки оквиÑ\80",
+ "continue-editing": "Ð\9fÑ\80еÑ\92и на обаÑ\81Ñ\82 Ñ\83Ñ\80еÑ\92иваÑ\9aа",
"previewconflict": "Овај преглед осликава како ће изгледати текст у текстуалном оквиру.",
"session_fail_preview": "Извињавамо се! Нисмо могли да обрадимо вашу измену због губитка података сесије.\n\nМожда сте одјављени. <strong>Проверите да ли сте пријављени и покушајте поново</strong>.\nАко и даље не ради, покушајте да се [[Special:UserLogout|одјавите]] и поново пријавите, те проверите да ли су на вашем претраживачу дозвољени колачићи са овог сајта.",
"session_fail_preview_html": "Нисмо могли да обрадимо вашу измену због губитка података сесије.\n\n<em>Будући да је на овом викију омогућен унос HTML ознака, преглед је сакривен као мера предострожности против напада преко јаваскрипта.</em>\n\n<strong>Ако сте покушали да направите праву измену, покушајте поново.<strong>\nАко и даље не ради, покушајте да се [[Special:UserLogout|одјавите]] и поново пријавите и проверите да ли ваш прегледач дозвољава колачиће са овог сајта.",
"imgmultipageprev": "← претходна страница",
"imgmultipagenext": "следећа страница →",
"imgmultigo": "Иди!",
- "imgmultigoto": "Ð\98ди на страницу $1",
+ "imgmultigoto": "Ð\9fÑ\80еÑ\92и на страницу $1",
"img-lang-opt": "$2 ($1)",
"img-lang-default": "(подразумевани језик)",
"img-lang-info": "Рендеруј ову слику у $1. $2",
"diff-form-submit": "Прикажи разлике",
"permanentlink": "Трајна веза",
"permanentlink-revid": "ID измене",
- "permanentlink-submit": "Ð\98ди на измену",
+ "permanentlink-submit": "Ð\9fÑ\80еÑ\92и на измену",
"dberr-problems": "Дошло је до техничких проблема.",
"dberr-again": "Сачекајте неколико минута и поново учитајте страницу.",
"dberr-info": "(Не могу приступити бази података: $1)",
"timezonelegend": "Tidszon:",
"localtime": "Lokal tid:",
"timezoneuseserverdefault": "Använd wikins standard ($1)",
- "timezoneuseoffset": "Annan (specificera skillnad)",
+ "timezoneuseoffset": "Annan (specificera skillnad nedan)",
+ "timezone-useoffset-placeholder": "Exempelvärden: \"-07:00\" eller \"01:00\"",
"servertime": "Serverns tid:",
"guesstimezone": "Fyll i från webbläsare",
"timezoneregion-africa": "Afrika",
"timezonelegend": "Часовий пояс:",
"localtime": "Місцевий час:",
"timezoneuseserverdefault": "Використовувати стандартне налаштування вікі ($1)",
- "timezoneuseoffset": "Інше (зазначте зміщення)",
+ "timezoneuseoffset": "Інше (нижче зазначте зміщення)",
+ "timezone-useoffset-placeholder": "Наприклад: «-07:00» або «01:00»",
"servertime": "Час сервера:",
"guesstimezone": "Заповнити з браузера",
"timezoneregion-africa": "Африка",
"move-watch": "Спостерігати за цією сторінкою",
"movepagebtn": "Перейменувати сторінку",
"pagemovedsub": "Перейменування виконано",
- "cannotmove": "СÑ\82оÑ\80Ñ\96нка не може бÑ\83Ñ\82и пеÑ\80ейменована з {{PLURAL:$1|1=Ñ\82акоÑ\8a причини|таких причин}}:",
+ "cannotmove": "СÑ\82оÑ\80Ñ\96нка не може бÑ\83Ñ\82и пеÑ\80ейменована з {{PLURAL:$1|1=Ñ\82акоÑ\97 причини|таких причин}}:",
"movepage-moved": "'''Сторінка «$1» перейменована на «$2»'''",
"movepage-moved-redirect": "Створено перенаправлення.",
"movepage-moved-noredirect": "Створення перенаправлення було заборонене.",
"prefs-watchlist-edits": "زیر نظر فہرست میں نظر آنے والی تبدیلیوں کی زیادہ سے زیادہ تعداد:",
"prefs-watchlist-edits-max": "زیادہ سے زیادہ تعداد: 1000",
"prefs-watchlist-token": "زیر نظر فہرست کی کلید:",
+ "prefs-watchlist-managetokens": "انتظام ٹوکن",
"prefs-misc": "دیگر",
"prefs-resetpass": "پاس ورڈ تبدیل کریں",
"prefs-changeemail": "برقی ڈاک پتا تبدیل یا حذف کریں",
"recentchangescount": "حالیہ تبدیلیوں، تاریخچوں اور نوشتوں میں دکھائی جانے والی ترامیم کی تعداد:",
"prefs-help-recentchangescount": "زیادہ سے زیادہ تعداد: 1000",
"prefs-help-watchlist-token2": "یہ آپ کی زیر نظر فہرست کے ویب فیڈ کی خفیہ کلید ہے۔\nاسے خفیہ رکھیں، تاکہ کوئی دوسرا شخص آپ کی زیر نظر فہرست نہ دیکھ سکے۔\nاگر آپ کو کلید تبدیل کرنی ہو تو [[Special:ResetTokens|یہاں کلک کریں]]۔",
+ "prefs-help-tokenmanagement": "آپ کی زیر نظر فہرست کی ویب فیڈ تک رسائی کے لیے اپنے کھاتے کی خفیہ کلید آپ یہاں دیکھ اور بدل سکتے ہیں۔ جس کے پاس یہ کلید ہوگی وہ آپ کی زیر نظر فہرست کو دیکھ سکتا ہے، لہذا اس کلید کو خفیہ رکھیں۔",
"savedprefs": "آپ کی ترجیحات محفوظ ہوگئیں۔",
"savedrights": "{{GENDER:$1|$1}} کے اختیارات محفوظ ہو گئے۔",
"timezonelegend": "منطقۂ وقت:",
"prefs-advancedwatchlist": "اضافی اختیارات",
"prefs-displayrc": "نمائش کے اختیارات",
"prefs-displaywatchlist": "نمائش کے اختیارات",
+ "prefs-changesrc": "دکھائی جانے والی تبدیلیاں",
+ "prefs-changeswatchlist": "دکھائی جانے والی تبدیلیاں",
+ "prefs-pageswatchlist": "زیر نظر صفحات",
"prefs-tokenwatchlist": "ٹوکن",
"prefs-diffs": "فرق",
"prefs-help-prefershttps": "یہ ترجیح آپ کے اگلے لاگ ان پر اثر انداز ہوگی۔",
"json-error-syntax": "語法錯咗",
"json-error-utf8": "字符引導失敗,因為有非法UTF-8代碼。",
"headline-anchor-title": "連結到呢一節",
- "special-characters-group-latin": "拉丁文",
- "special-characters-group-latinextended": "Latin擴展左",
+ "special-characters-group-latin": "拉丁字母擴展",
+ "special-characters-group-latinextended": "拉丁字母擴展",
"special-characters-group-ipa": "IPA",
"special-characters-group-symbols": "符號",
"special-characters-group-greek": "希臘文",
"timezonelegend": "时区:",
"localtime": "当地时间:",
"timezoneuseserverdefault": "使用wiki默认值($1)",
- "timezoneuseoffset": "其它(指定时差)",
+ "timezoneuseoffset": "其他(指定时差)",
"servertime": "服务器时间:",
"guesstimezone": "使用浏览器设置",
"timezoneregion-africa": "非洲",
'dependencies' => [
'jquery.accessKeyLabel',
'mediawiki.RegExp',
- 'mediawiki.notify',
],
'targets' => [ 'desktop', 'mobile' ],
],
}
};
- /**
- * Add a little box at the top of the screen to inform the user of
- * something, replacing any previous message.
- * Calling with no arguments, with an empty string or null will hide the message
- *
- * @method jsMessage
- * @deprecated since 1.20 Use mw#notify
- * @param {Mixed} message The DOM-element, jQuery object or HTML-string to be put inside the message box.
- * to allow CSS/JS to hide different boxes. null = no class used.
- */
- mw.log.deprecate( util, 'jsMessage', function ( message ) {
- if ( !arguments.length || message === '' || message === null ) {
- return true;
- }
- if ( typeof message !== 'object' ) {
- message = $.parseHTML( message );
- }
- mw.notify( message, { autoHide: true, tag: 'legacy' } );
- return true;
- }, 'Use mw.notify instead.', 'mw.util.jsMessage' );
-
/**
* Initialisation of mw.util.$content
*/
--- /dev/null
+<?php
+
+namespace MediaWiki\Tests\Revision;
+
+use MediaWiki\Revision\FallbackSlotRoleHandler;
+use MediaWikiTestCase;
+use Title;
+
+/**
+ * @covers \MediaWiki\Revision\FallbackSlotRoleHandler
+ */
+class FallbackSlotRoleHandlerTest extends MediaWikiTestCase {
+
+ private function makeBlankTitleObject() {
+ /** @var Title $title */
+ $title = $this->getMockBuilder( Title::class )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ return $title;
+ }
+
+ /**
+ * @covers \MediaWiki\Revision\FallbackSlotRoleHandler::__construct
+ * @covers \MediaWiki\Revision\FallbackSlotRoleHandler::getRole()
+ * @covers \MediaWiki\Revision\FallbackSlotRoleHandler::getNameMessageKey()
+ * @covers \MediaWiki\Revision\FallbackSlotRoleHandler::getDefaultModel()
+ * @covers \MediaWiki\Revision\FallbackSlotRoleHandler::getOutputLayoutHints()
+ */
+ public function testConstruction() {
+ $handler = new FallbackSlotRoleHandler( 'foo' );
+ $this->assertSame( 'foo', $handler->getRole() );
+ $this->assertSame( 'slot-name-foo', $handler->getNameMessageKey() );
+
+ $title = $this->makeBlankTitleObject();
+ $this->assertSame( CONTENT_MODEL_TEXT, $handler->getDefaultModel( $title ) );
+
+ $hints = $handler->getOutputLayoutHints();
+ $this->assertArrayHasKey( 'display', $hints );
+ $this->assertArrayHasKey( 'region', $hints );
+ $this->assertArrayHasKey( 'placement', $hints );
+ }
+
+ /**
+ * @covers \MediaWiki\Revision\FallbackSlotRoleHandler::isAllowedModel()
+ */
+ public function testIsAllowedModel() {
+ $handler = new FallbackSlotRoleHandler( 'foo', 'FooModel' );
+
+ // For the fallback handler, no models are allowed
+ $title = $this->makeBlankTitleObject();
+ $this->assertFalse( $handler->isAllowedModel( 'FooModel', $title ) );
+ $this->assertFalse( $handler->isAllowedModel( 'QuaxModel', $title ) );
+ }
+
+ /**
+ * @covers \MediaWiki\Revision\SlotRoleHandler::isAllowedModel()
+ */
+ public function testIsAllowedOn() {
+ $handler = new FallbackSlotRoleHandler( 'foo', 'FooModel' );
+
+ $title = $this->makeBlankTitleObject();
+ $this->assertFalse( $handler->isAllowedOn( $title ) );
+ }
+
+ /**
+ * @covers \MediaWiki\Revision\FallbackSlotRoleHandler::supportsArticleCount()
+ */
+ public function testSupportsArticleCount() {
+ $handler = new FallbackSlotRoleHandler( 'foo', 'FooModel' );
+
+ $this->assertFalse( $handler->supportsArticleCount() );
+ }
+
+}
--- /dev/null
+<?php
+
+namespace MediaWiki\Tests\Revision;
+
+use MediaWiki\Revision\MainSlotRoleHandler;
+use MediaWikiTestCase;
+use PHPUnit\Framework\MockObject\MockObject;
+use Title;
+
+/**
+ * @covers \MediaWiki\Revision\MainSlotRoleHandler
+ */
+class MainSlotRoleHandlerTest extends MediaWikiTestCase {
+
+ private function makeTitleObject( $ns ) {
+ /** @var Title|MockObject $title */
+ $title = $this->getMockBuilder( Title::class )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $title->method( 'getNamespace' )
+ ->willReturn( $ns );
+
+ return $title;
+ }
+
+ /**
+ * @covers \MediaWiki\Revision\MainSlotRoleHandler::__construct
+ * @covers \MediaWiki\Revision\MainSlotRoleHandler::getRole()
+ * @covers \MediaWiki\Revision\MainSlotRoleHandler::getNameMessageKey()
+ * @covers \MediaWiki\Revision\MainSlotRoleHandler::getOutputLayoutHints()
+ */
+ public function testConstruction() {
+ $handler = new MainSlotRoleHandler( [] );
+ $this->assertSame( 'main', $handler->getRole() );
+ $this->assertSame( 'slot-name-main', $handler->getNameMessageKey() );
+
+ $hints = $handler->getOutputLayoutHints();
+ $this->assertArrayHasKey( 'display', $hints );
+ $this->assertArrayHasKey( 'region', $hints );
+ $this->assertArrayHasKey( 'placement', $hints );
+ }
+
+ /**
+ * @covers \MediaWiki\Revision\MainSlotRoleHandler::getDefaultModel()
+ */
+ public function testFetDefaultModel() {
+ $handler = new MainSlotRoleHandler( [ 100 => CONTENT_MODEL_TEXT ] );
+
+ // For the main handler, the namespace determins the defualt model
+ $titleMain = $this->makeTitleObject( NS_MAIN );
+ $this->assertSame( CONTENT_MODEL_WIKITEXT, $handler->getDefaultModel( $titleMain ) );
+
+ $title100 = $this->makeTitleObject( 100 );
+ $this->assertSame( CONTENT_MODEL_TEXT, $handler->getDefaultModel( $title100 ) );
+ }
+
+ /**
+ * @covers \MediaWiki\Revision\MainSlotRoleHandler::isAllowedModel()
+ */
+ public function testIsAllowedModel() {
+ $handler = new MainSlotRoleHandler( [] );
+
+ // For the main handler, (nearly) all models are allowed
+ $title = $this->makeTitleObject( NS_MAIN );
+ $this->assertTrue( $handler->isAllowedModel( CONTENT_MODEL_WIKITEXT, $title ) );
+ $this->assertTrue( $handler->isAllowedModel( CONTENT_MODEL_TEXT, $title ) );
+ }
+
+ /**
+ * @covers \MediaWiki\Revision\MainSlotRoleHandler::supportsArticleCount()
+ */
+ public function testSupportsArticleCount() {
+ $handler = new MainSlotRoleHandler( [] );
+
+ $this->assertTrue( $handler->supportsArticleCount() );
+ }
+
+}
use Language;
use LogicException;
use MediaWiki\Revision\MutableRevisionRecord;
+use MediaWiki\Revision\MainSlotRoleHandler;
use MediaWiki\Revision\RevisionRecord;
use MediaWiki\Revision\RevisionRenderer;
use MediaWiki\Revision\SlotRecord;
+use MediaWiki\Revision\SlotRoleRegistry;
+use MediaWiki\Storage\NameTableStore;
use MediaWikiTestCase;
use MediaWiki\User\UserIdentityValue;
use ParserOptions;
->with( $dbIndex )
->willReturn( $db );
- return new RevisionRenderer( $lb );
+ /** @var NameTableStore|MockObject $slotRoles */
+ $slotRoles = $this->getMockBuilder( NameTableStore::class )
+ ->disableOriginalConstructor()
+ ->getMock();
+ $slotRoles->method( 'getMap' )
+ ->willReturn( [] );
+
+ $roleReg = new SlotRoleRegistry( $slotRoles );
+ $roleReg->defineRole( 'main', function () {
+ return new MainSlotRoleHandler( [] );
+ } );
+ $roleReg->defineRoleWithModel( 'aux', CONTENT_MODEL_WIKITEXT );
+
+ return new RevisionRenderer( $lb, $roleReg );
}
private function selectFieldCallback( $table, $fields, $cond, $maxRev ) {
MediaWikiServices::getInstance()->getCommentStore(),
MediaWikiServices::getInstance()->getContentModelStore(),
MediaWikiServices::getInstance()->getSlotRoleStore(),
+ MediaWikiServices::getInstance()->getSlotRoleRegistry(),
$this->getMcrMigrationStage(),
MediaWikiServices::getInstance()->getActorMigration(),
$wikiId
use MediaWiki\Logger\Spi as LoggerSpi;
use MediaWiki\Revision\RevisionStore;
use MediaWiki\Revision\RevisionStoreFactory;
+use MediaWiki\Revision\SlotRoleRegistry;
use MediaWiki\Storage\BlobStore;
use MediaWiki\Storage\BlobStoreFactory;
use MediaWiki\Storage\NameTableStore;
$this->getMockLoadBalancerFactory(),
$this->getMockBlobStoreFactory(),
$this->getNameTableStoreFactory(),
+ $this->getMockSlotRoleRegistry(),
$this->getHashWANObjectCache(),
$this->getMockCommentStore(),
ActorMigration::newMigration(),
$lbFactory = $this->getMockLoadBalancerFactory();
$blobStoreFactory = $this->getMockBlobStoreFactory();
$nameTableStoreFactory = $this->getNameTableStoreFactory();
+ $slotRoleRegistry = $this->getMockSlotRoleRegistry();
$cache = $this->getHashWANObjectCache();
$commentStore = $this->getMockCommentStore();
$actorMigration = ActorMigration::newMigration();
$lbFactory,
$blobStoreFactory,
$nameTableStoreFactory,
+ $slotRoleRegistry,
$cache,
$commentStore,
$actorMigration,
return $mock;
}
+ /**
+ * @return \PHPUnit_Framework_MockObject_MockObject|SlotRoleRegistry
+ */
+ private function getMockSlotRoleRegistry() {
+ $mock = $this->getMockBuilder( SlotRoleRegistry::class )
+ ->disableOriginalConstructor()->getMock();
+
+ return $mock;
+ }
+
/**
* @return NameTableStoreFactory
*/
use MediaWiki\MediaWikiServices;
use MediaWiki\Revision\RevisionAccessException;
use MediaWiki\Revision\RevisionStore;
+use MediaWiki\Revision\SlotRoleRegistry;
use MediaWiki\Revision\SlotRecord;
use MediaWiki\Storage\SqlBlobStore;
use MediaWikiTestCase;
MediaWikiServices::getInstance()->getCommentStore(),
MediaWikiServices::getInstance()->getContentModelStore(),
MediaWikiServices::getInstance()->getSlotRoleStore(),
+ MediaWikiServices::getInstance()->getSlotRoleRegistry(),
$wgMultiContentRevisionSchemaMigrationStage,
MediaWikiServices::getInstance()->getActorMigration()
);
->disableOriginalConstructor()->getMock();
}
+ /**
+ * @return \PHPUnit_Framework_MockObject_MockObject|SlotRoleRegistry
+ */
+ private function getMockSlotRoleRegistry() {
+ return $this->getMockBuilder( SlotRoleRegistry::class )
+ ->disableOriginalConstructor()->getMock();
+ }
+
private function getHashWANObjectCache() {
return new WANObjectCache( [ 'cache' => new \HashBagOStuff() ] );
}
$this->getMockCommentStore(),
$nameTables->getContentModels(),
$nameTables->getSlotRoles(),
+ $this->getMockSlotRoleRegistry(),
$migrationMode,
MediaWikiServices::getInstance()->getActorMigration()
);
$nameTables = $services->getNameTableStoreFactory();
$contentModelStore = $nameTables->getContentModels();
$slotRoleStore = $nameTables->getSlotRoles();
+ $slotRoleRegistry = $services->getSlotRoleRegistry();
$store = new RevisionStore(
$loadBalancer,
$blobStore,
$commentStore,
$nameTables->getContentModels(),
$nameTables->getSlotRoles(),
+ $slotRoleRegistry,
$migration,
$services->getActorMigration()
);
--- /dev/null
+<?php
+
+namespace MediaWiki\Tests\Revision;
+
+use MediaWiki\Revision\SlotRoleHandler;
+use MediaWikiTestCase;
+use Title;
+
+/**
+ * @covers \MediaWiki\Revision\SlotRoleHandler
+ */
+class SlotRoleHandlerTest extends MediaWikiTestCase {
+
+ private function makeBlankTitleObject() {
+ /** @var Title $title */
+ $title = $this->getMockBuilder( Title::class )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ return $title;
+ }
+
+ /**
+ * @covers \MediaWiki\Revision\SlotRoleHandler::__construct
+ * @covers \MediaWiki\Revision\SlotRoleHandler::getRole()
+ * @covers \MediaWiki\Revision\SlotRoleHandler::getNameMessageKey()
+ * @covers \MediaWiki\Revision\SlotRoleHandler::getDefaultModel()
+ * @covers \MediaWiki\Revision\SlotRoleHandler::getOutputLayoutHints()
+ */
+ public function testConstruction() {
+ $handler = new SlotRoleHandler( 'foo', 'FooModel', [ 'frob' => 'niz' ] );
+ $this->assertSame( 'foo', $handler->getRole() );
+ $this->assertSame( 'slot-name-foo', $handler->getNameMessageKey() );
+
+ $title = $this->makeBlankTitleObject();
+ $this->assertSame( 'FooModel', $handler->getDefaultModel( $title ) );
+
+ $hints = $handler->getOutputLayoutHints();
+ $this->assertArrayHasKey( 'frob', $hints );
+ $this->assertSame( 'niz', $hints['frob'] );
+
+ $this->assertArrayHasKey( 'display', $hints );
+ $this->assertArrayHasKey( 'region', $hints );
+ $this->assertArrayHasKey( 'placement', $hints );
+ }
+
+ /**
+ * @covers \MediaWiki\Revision\SlotRoleHandler::isAllowedModel()
+ */
+ public function testIsAllowedModel() {
+ $handler = new SlotRoleHandler( 'foo', 'FooModel' );
+
+ $title = $this->makeBlankTitleObject();
+ $this->assertTrue( $handler->isAllowedModel( 'FooModel', $title ) );
+ $this->assertFalse( $handler->isAllowedModel( 'QuaxModel', $title ) );
+ }
+
+ /**
+ * @covers \MediaWiki\Revision\SlotRoleHandler::supportsArticleCount()
+ */
+ public function testSupportsArticleCount() {
+ $handler = new SlotRoleHandler( 'foo', 'FooModel' );
+
+ $this->assertFalse( $handler->supportsArticleCount() );
+ }
+
+}
--- /dev/null
+<?php
+
+namespace MediaWiki\Tests\Revision;
+
+use InvalidArgumentException;
+use LogicException;
+use MediaWiki\Revision\MainSlotRoleHandler;
+use MediaWiki\Revision\SlotRoleHandler;
+use MediaWiki\Revision\SlotRoleRegistry;
+use MediaWiki\Storage\NameTableStore;
+use MediaWikiTestCase;
+use Title;
+use Wikimedia\Assert\PostconditionException;
+
+/**
+ * @covers \MediaWiki\Revision\SlotRoleRegistry
+ */
+class SlotRoleRegistryTest extends MediaWikiTestCase {
+
+ private function makeBlankTitleObject() {
+ /** @var Title $title */
+ $title = $this->getMockBuilder( Title::class )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ return $title;
+ }
+
+ private function makeNameTableStore( array $names = [] ) {
+ $mock = $this->getMockBuilder( NameTableStore::class )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $mock->method( 'getMap' )
+ ->willReturn( $names );
+
+ return $mock;
+ }
+
+ private function newSlotRoleRegistry( NameTableStore $roleNameStore = null ) {
+ if ( !$roleNameStore ) {
+ $roleNameStore = $this->makeNameTableStore();
+ }
+
+ return new SlotRoleRegistry( $roleNameStore );
+ }
+
+ /**
+ * @covers \MediaWiki\Revision\SlotRoleRegistry::defineRole()
+ * @covers \MediaWiki\Revision\SlotRoleRegistry::getDefinedRoles()
+ * @covers \MediaWiki\Revision\SlotRoleRegistry::getKnownRoles()
+ * @covers \MediaWiki\Revision\SlotRoleRegistry::getRoleHandler()
+ */
+ public function testDefineRole() {
+ $registry = $this->newSlotRoleRegistry();
+ $registry->defineRole( 'foo', function ( $role ) {
+ return new SlotRoleHandler( $role, 'FooModel' );
+ } );
+
+ $this->assertTrue( $registry->isDefinedRole( 'foo' ) );
+ $this->assertContains( 'foo', $registry->getDefinedRoles() );
+ $this->assertContains( 'foo', $registry->getKnownRoles() );
+
+ $handler = $registry->getRoleHandler( 'foo' );
+ $this->assertSame( 'foo', $handler->getRole() );
+
+ $title = $this->makeBlankTitleObject();
+ $this->assertSame( 'FooModel', $handler->getDefaultModel( $title ) );
+ }
+
+ /**
+ * @covers \MediaWiki\Revision\SlotRoleRegistry::defineRole()
+ */
+ public function testDefineRoleFailsForDupe() {
+ $registry = $this->newSlotRoleRegistry();
+ $registry->defineRole( 'foo', function ( $role ) {
+ return new SlotRoleHandler( $role, 'FooModel' );
+ } );
+
+ $this->setExpectedException( LogicException::class );
+ $registry->defineRole( 'foo', function ( $role ) {
+ return new SlotRoleHandler( $role, 'FooModel' );
+ } );
+ }
+
+ /**
+ * @covers \MediaWiki\Revision\SlotRoleRegistry::defineRoleWithModel()
+ * @covers \MediaWiki\Revision\SlotRoleRegistry::getDefinedRoles()
+ * @covers \MediaWiki\Revision\SlotRoleRegistry::getKnownRoles()
+ * @covers \MediaWiki\Revision\SlotRoleRegistry::getRoleHandler()
+ */
+ public function testDefineRoleWithContentModel() {
+ $registry = $this->newSlotRoleRegistry();
+ $registry->defineRoleWithModel( 'foo', 'FooModel' );
+
+ $this->assertTrue( $registry->isDefinedRole( 'foo' ) );
+ $this->assertContains( 'foo', $registry->getDefinedRoles() );
+ $this->assertContains( 'foo', $registry->getKnownRoles() );
+
+ $handler = $registry->getRoleHandler( 'foo' );
+ $this->assertSame( 'foo', $handler->getRole() );
+
+ /** @var Title $title */
+ $title = $this->getMockBuilder( Title::class )
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->assertSame( 'FooModel', $handler->getDefaultModel( $title ) );
+ }
+
+ /**
+ * @covers \MediaWiki\Revision\SlotRoleRegistry::getRoleHandler()
+ */
+ public function testGetRoleHandlerForUnknownModel() {
+ $registry = $this->newSlotRoleRegistry();
+
+ $this->setExpectedException( InvalidArgumentException::class );
+
+ $registry->getRoleHandler( 'foo' );
+ }
+
+ /**
+ * @covers \MediaWiki\Revision\SlotRoleRegistry::getRoleHandler()
+ */
+ public function testGetRoleHandlerFallbackHandler() {
+ $registry = $this->newSlotRoleRegistry(
+ $this->makeNameTableStore( [ 1 => 'foo' ] )
+ );
+
+ \Wikimedia\suppressWarnings();
+ $handler = $registry->getRoleHandler( 'foo' );
+ $this->assertSame( 'foo', $handler->getRole() );
+
+ \Wikimedia\restoreWarnings();
+ }
+
+ /**
+ * @covers \MediaWiki\Revision\SlotRoleRegistry::getRoleHandler()
+ */
+ public function testGetRoleHandlerWithBadInstantiator() {
+ $registry = $this->newSlotRoleRegistry();
+ $registry->defineRole( 'foo', function ( $role ) {
+ return 'Not a SlotRoleHandler instance';
+ } );
+
+ $this->setExpectedException( PostconditionException::class );
+ $registry->getRoleHandler( 'foo' );
+ }
+
+ /**
+ * @covers \MediaWiki\Revision\SlotRoleRegistry::getRequiredRoles()
+ */
+ public function testGetRequiredRoles() {
+ $registry = $this->newSlotRoleRegistry();
+ $registry->defineRole( 'main', function ( $role ) {
+ return new MainSlotRoleHandler( [] );
+ } );
+
+ $title = $this->makeBlankTitleObject();
+ $this->assertEquals( [ 'main' ], $registry->getRequiredRoles( $title ) );
+ }
+
+ /**
+ * @covers \MediaWiki\Revision\SlotRoleRegistry::getAllowedRoles()
+ */
+ public function testGetAllowedRoles() {
+ $registry = $this->newSlotRoleRegistry();
+ $registry->defineRole( 'main', function ( $role ) {
+ return new MainSlotRoleHandler( [] );
+ } );
+ $registry->defineRoleWithModel( 'foo', CONTENT_MODEL_TEXT );
+
+ $title = $this->makeBlankTitleObject();
+ $this->assertEquals( [ 'main', 'foo' ], $registry->getAllowedRoles( $title ) );
+ }
+
+ /**
+ * @covers \MediaWiki\Revision\SlotRoleRegistry::getKnownRoles()
+ * @covers \MediaWiki\Revision\SlotRoleRegistry::isKnownRole()
+ */
+ public function testGetKnownRoles() {
+ $registry = $this->newSlotRoleRegistry(
+ $this->makeNameTableStore( [ 1 => 'foo' ] )
+ );
+ $registry->defineRoleWithModel( 'bar', CONTENT_MODEL_TEXT );
+
+ $this->assertTrue( $registry->isKnownRole( 'foo' ) );
+ $this->assertTrue( $registry->isKnownRole( 'bar' ) );
+ $this->assertFalse( $registry->isKnownRole( 'xyzzy' ) );
+
+ $title = $this->makeBlankTitleObject();
+ $this->assertArrayEquals( [ 'foo', 'bar' ], $registry->getKnownRoles( $title ) );
+ }
+
+}
$services->getCommentStore(),
$services->getContentModelStore(),
$services->getSlotRoleStore(),
+ $services->getSlotRoleRegistry(),
$this->getMcrMigrationStage(),
$services->getActorMigration()
);
MediaWikiServices::getInstance()->getCommentStore(),
MediaWikiServices::getInstance()->getContentModelStore(),
MediaWikiServices::getInstance()->getSlotRoleStore(),
+ MediaWikiServices::getInstance()->getSlotRoleRegistry(),
MIGRATION_OLD,
MediaWikiServices::getInstance()->getActorMigration()
);
}
$rev = $updater->saveRevision( $comment );
+ if ( !$updater->wasSuccessful() ) {
+ $this->fail( $updater->getStatus()->getWikiText() );
+ }
$this->getDerivedPageDataUpdater( $page ); // flush cached instance after.
return $rev;
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::getCanonicalParserOutput()
*/
public function testPrepareContent() {
+ MediaWikiServices::getInstance()->getSlotRoleRegistry()->defineRoleWithModel(
+ 'aux',
+ CONTENT_MODEL_WIKITEXT
+ );
+
$sysop = $this->getTestUser( [ 'sysop' ] )->getUser();
$updater = $this->getDerivedPageDataUpdater( __METHOD__ );
}
public function testGetSecondaryDataUpdatesWithSlotRemoval() {
- global $wgMultiContentRevisionSchemaMigrationStage;
-
- if ( ! ( $wgMultiContentRevisionSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) ) {
+ if ( !$this->hasMultiSlotSupport() ) {
$this->markTestSkipped( 'Slot removal cannot happen with MCR being enabled' );
}
$a1 = $this->defineMockContentModelForUpdateTesting( 'A1' );
$m2 = $this->defineMockContentModelForUpdateTesting( 'M2' );
+ MediaWikiServices::getInstance()->getSlotRoleRegistry()->defineRoleWithModel(
+ 'aux',
+ $a1->getModelID()
+ );
+
$mainContent1 = $this->createMockContent( $m1, 'main 1' );
$auxContent1 = $this->createMockContent( $a1, 'aux 1' );
$mainContent2 = $this->createMockContent( $m2, 'main 2' );
if ( $this->hasMultiSlotSupport() ) {
$content['aux'] = new WikitextContent( 'Aux [[Nix]]' );
+
+ MediaWikiServices::getInstance()->getSlotRoleRegistry()->defineRoleWithModel(
+ 'aux',
+ CONTENT_MODEL_WIKITEXT
+ );
}
$rev = $this->createRevision( $page, 'first', $content );
*/
class PageUpdaterTest extends MediaWikiTestCase {
+ public function setUp() {
+ parent::setUp();
+
+ MediaWikiServices::getInstance()->getSlotRoleRegistry()->defineRoleWithModel(
+ 'aux',
+ CONTENT_MODEL_WIKITEXT
+ );
+ }
+
private function getDummyTitle( $method ) {
return Title::newFromText( $method, $this->getDefaultWikitextNS() );
}
$this->assertTrue( $status->hasMessage( 'edit-already-exists' ), 'edit-already-exists' );
}
+ /**
+ * @covers \MediaWiki\Storage\PageUpdater::saveRevision()
+ */
+ public function testFailureOnBadContentModel() {
+ $user = $this->getTestUser()->getUser();
+ $title = $this->getDummyTitle( __METHOD__ );
+
+ // start editing non-existing page
+ $page = WikiPage::factory( $title );
+ $updater = $page->newPageUpdater( $user );
+
+ // plain text content should fail in aux slot (the main slot doesn't care)
+ $updater->setContent( 'main', new TextContent( 'Main Content' ) );
+ $updater->setContent( 'aux', new TextContent( 'Aux Content' ) );
+
+ $summary = CommentStoreComment::newUnsavedComment( 'udpate?!' );
+ $updater->saveRevision( $summary, EDIT_UPDATE );
+ $status = $updater->getStatus();
+
+ $this->assertFalse( $updater->wasSuccessful(), 'wasSuccessful()' );
+ $this->assertNull( $updater->getNewRevision(), 'getNewRevision()' );
+ $this->assertFalse( $status->isOK(), 'getStatus()->isOK()' );
+ $this->assertTrue(
+ $status->hasMessage( 'content-not-allowed-here' ),
+ 'content-not-allowed-here'
+ );
+ }
+
public function provideSetRcPatrolStatus( $patrolled ) {
yield [ RecentChange::PRC_UNPATROLLED ];
yield [ RecentChange::PRC_AUTOPATROLLED ];
<?php
-use MediaWiki\Block\Restriction\PageRestriction;
use MediaWiki\MediaWikiServices;
/**
'wgEmailAuthentication' => true,
] );
- $this->setUserPerm( [ 'createpage', 'edit', 'move', 'rollback', 'patrol', 'upload', 'purge' ] );
+ $this->setUserPerm( [ "createpage", "move" ] );
$this->setTitle( NS_HELP, "test page" );
# $wgEmailConfirmToEdit only applies to 'edit' action
'expiry' => 10,
'systemBlock' => 'test',
] );
-
- $errors = [ [ 'systemblockedtext',
+ $this->assertEquals( [ [ 'systemblockedtext',
'[[User:Useruser|Useruser]]', 'no reason given', '127.0.0.1',
'Useruser', 'test', '23:00, 31 December 1969', '127.0.8.1',
- $wgLang->timeanddate( wfTimestamp( TS_MW, $now ), true ) ] ];
-
- $this->assertEquals( $errors,
- $this->title->getUserPermissionsErrors( 'edit', $this->user ) );
- $this->assertEquals( $errors,
+ $wgLang->timeanddate( wfTimestamp( TS_MW, $now ), true ) ] ],
$this->title->getUserPermissionsErrors( 'move-target', $this->user ) );
- $this->assertEquals( $errors,
- $this->title->getUserPermissionsErrors( 'rollback', $this->user ) );
- $this->assertEquals( $errors,
- $this->title->getUserPermissionsErrors( 'patrol', $this->user ) );
- $this->assertEquals( $errors,
- $this->title->getUserPermissionsErrors( 'upload', $this->user ) );
- $this->assertEquals( [],
- $this->title->getUserPermissionsErrors( 'purge', $this->user ) );
// partial block message test
$this->user->mBlockedby = $this->user->getName();
'expiry' => 10,
] );
- $this->assertEquals( [],
- $this->title->getUserPermissionsErrors( 'edit', $this->user ) );
- $this->assertEquals( [],
- $this->title->getUserPermissionsErrors( 'move-target', $this->user ) );
- $this->assertEquals( [],
- $this->title->getUserPermissionsErrors( 'rollback', $this->user ) );
- $this->assertEquals( [],
- $this->title->getUserPermissionsErrors( 'patrol', $this->user ) );
- $this->assertEquals( [],
- $this->title->getUserPermissionsErrors( 'upload', $this->user ) );
- $this->assertEquals( [],
- $this->title->getUserPermissionsErrors( 'purge', $this->user ) );
-
- $this->user->mBlock->setRestrictions( [
- ( new PageRestriction( 0, $this->title->getArticleID() ) )->setTitle( $this->title ),
- ] );
-
- $errors = [ [ 'blockedtext-partial',
+ $this->assertEquals( [ [ 'blockedtext-partial',
'[[User:Useruser|Useruser]]', 'no reason given', '127.0.0.1',
'Useruser', null, '23:00, 31 December 1969', '127.0.8.1',
- $wgLang->timeanddate( wfTimestamp( TS_MW, $now ), true ) ] ];
-
- $this->assertEquals( $errors,
- $this->title->getUserPermissionsErrors( 'edit', $this->user ) );
- $this->assertEquals( $errors,
+ $wgLang->timeanddate( wfTimestamp( TS_MW, $now ), true ) ] ],
$this->title->getUserPermissionsErrors( 'move-target', $this->user ) );
- $this->assertEquals( $errors,
- $this->title->getUserPermissionsErrors( 'rollback', $this->user ) );
- $this->assertEquals( $errors,
- $this->title->getUserPermissionsErrors( 'patrol', $this->user ) );
- $this->assertEquals( [],
- $this->title->getUserPermissionsErrors( 'upload', $this->user ) );
- $this->assertEquals( [],
- $this->title->getUserPermissionsErrors( 'purge', $this->user ) );
}
}
// TODO: test partition
public function testRunForSinglePage() {
+ MediaWikiServices::getInstance()->getSlotRoleRegistry()->defineRoleWithModel(
+ 'aux',
+ CONTENT_MODEL_WIKITEXT
+ );
+
$mainContent = new WikitextContent( 'MAIN [[Kittens]]' );
$auxContent = new WikitextContent( 'AUX [[Category:Goats]]' );
$page = $this->createPage( __METHOD__, [ 'main' => $mainContent, 'aux' => $auxContent ] );
}
$updater->saveRevision( CommentStoreComment::newUnsavedComment( "testing" ) );
+ if ( !$updater->wasSuccessful() ) {
+ $this->fail( $updater->getStatus()->getWikiText() );
+ }
return $page;
}
<?php
+
+use MediaWiki\MediaWikiServices;
use MediaWiki\Tests\Revision\McrReadNewSchemaOverride;
/**
$m1 = $this->defineMockContentModelForUpdateTesting( 'M1' );
$a1 = $this->defineMockContentModelForUpdateTesting( 'A1' );
+ MediaWikiServices::getInstance()->getSlotRoleRegistry()->defineRoleWithModel(
+ 'aux',
+ $a1->getModelID()
+ );
+
$mainContent1 = $this->createMockContent( $m1, 'main 1' );
$auxContent1 = $this->createMockContent( $a1, 'aux 1' );