class PoolForm in CMS Content Sync 2.0.x
Same name and namespace in other branches
- 8 src/Form/PoolForm.php \Drupal\cms_content_sync\Form\PoolForm
- 2.1.x src/Form/PoolForm.php \Drupal\cms_content_sync\Form\PoolForm
Form handler for the Pool add and edit forms.
Hierarchy
- class \Drupal\Core\Form\FormBase implements ContainerInjectionInterface, FormInterface uses DependencySerializationTrait, LoggerChannelTrait, MessengerTrait, RedirectDestinationTrait, StringTranslationTrait
- class \Drupal\Core\Entity\EntityForm implements EntityFormInterface
- class \Drupal\cms_content_sync\Form\PoolForm
- class \Drupal\Core\Entity\EntityForm implements EntityFormInterface
Expanded class hierarchy of PoolForm
1 file declares its use of PoolForm
- MigrationBase.php in modules/
cms_content_sync_migrate_acquia_content_hub/ src/ Form/ MigrationBase.php
File
- src/
Form/ PoolForm.php, line 21
Namespace
Drupal\cms_content_sync\FormView source
class PoolForm extends EntityForm {
/**
* @var int Defines the max length for the siteID. This must be limited due to the maximum characters allowed for table names within mongo db.
*/
public const siteIdMaxLength = 20;
/**
* @var string STEP_SYNC_CORE Select a Sync Core another pool on this site already uses or enter a new one
*/
public const STEP_SYNC_CORE = 'sync-core';
/**
* @var string STEP_POOL Select pool from existing pools in the Sync Core or enter a new one
*/
public const STEP_POOL = 'pool';
/**
* @var string CONTAINER_ID The element ID of the container that's replaced with every AJAX request
*/
public const CONTAINER_ID = 'pool-form-container';
/**
* @var null|string
*/
protected $backendUrl;
/**
* @var null|string
*/
protected $configMachineName;
/**
* @var null|string
*/
protected $overwrittenSiteId;
/**
* Constructs an PoolForm object.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
* The entityTypeManager
* @param \Drupal\Core\Extension\ModuleHandler $moduleHandler
* The moduleHandler
*/
public function __construct(EntityTypeManagerInterface $entityTypeManager, ModuleHandler $moduleHandler) {
$this->entityTypeManager = $entityTypeManager;
$this->moduleHandler = $moduleHandler;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static($container
->get('entity_type.manager'), $container
->get('module_handler'));
}
/**
* Return next form.
*
* @param array $form
*
* @return array the bundle settings
*/
public function ajaxReturn($form, FormStateInterface $form_state) {
return $form['elements'];
}
/**
* Rebuild form for next step.
*
* @param array $form
*/
public function connectSyncCore($form, FormStateInterface $form_state) {
$form_state
->setRebuild();
}
/**
* Rebuild form for next step.
*
* @param array $form
*/
public function checkSiteId($form, FormStateInterface $form_state) {
$form_state
->setRebuild();
}
/**
* Rebuild form for next step.
*
* @param array $form
*/
public function createNew($form, FormStateInterface $form_state) {
$form_state
->setValue('id', 'new');
// If we only use ->setValue() the default value in the input element will still be whatever was selected at the radios.
$data = $form_state
->getUserInput();
$data['id'] = 'new';
$form_state
->setUserInput($data);
$form_state
->setRebuild();
}
/**
* {@inheritdoc}
*/
public function form(array $form, FormStateInterface $form_state) {
$form = parent::form($form, $form_state);
$form['#tree'] = false;
$defaults = $this
->getDefaults($form_state);
if (!empty($defaults['backend_url'])) {
$form['default_backend_url'] = [
'#type' => 'hidden',
'#value' => $defaults['backend_url'],
];
}
if (!empty($defaults['name'])) {
$form['default_name'] = [
'#type' => 'hidden',
'#value' => $defaults['name'],
];
}
if (!empty($defaults['id'])) {
$form['default_id'] = [
'#type' => 'hidden',
'#value' => $defaults['id'],
];
}
/**
* @var \Drupal\cms_content_sync\Entity\Pool $pool
*/
$pool = $this->entity;
// Check if the site id or backend_url got set within the settings*.php.
if (!is_null($pool->id)) {
$this->configMachineName = $pool->id;
$cms_content_sync_settings = Settings::get('cms_content_sync');
if (!is_null($cms_content_sync_settings) && isset($cms_content_sync_settings['pools'][$pool->id]['backend_url'])) {
$this->backendUrl = $cms_content_sync_settings['pools'][$pool->id]['backend_url'];
}
}
if (!isset($this->configMachineName)) {
$this->configMachineName = '<machine_name_of_the_configuration>';
}
$step = $this
->getCurrentFormStep($form_state);
if (self::STEP_SYNC_CORE === $step) {
$elements = $this
->syncCoreForm($form, $form_state);
}
else {
$elements = $this
->poolForm($form, $form_state);
}
$form['elements'] = array_merge([
'#prefix' => '<div id="' . self::CONTAINER_ID . '">',
'#suffix' => '</div>',
'step' => [
'#type' => 'hidden',
'#value' => $step,
],
], $elements);
return $form;
}
/**
* Validate format of input fields and make sure the Sync Core backend is
* accessible to actually update it.
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
parent::validateForm($form, $form_state);
$step = $this
->getLastFormStep($form_state);
/**
* @var Pool $entity
*/
$entity = $this->entity;
$entity->backend_url = $form_state
->getValue('backend_url');
$allowed_length = 0;
if (!$entity
->useV2()) {
try {
$client = $entity
->getClient();
$client
->getReportingService()
->getStatus();
// In MongoDB (used by the Sync Core, the name of the database + collection name mustn't be longer than 120 characters)
$longest_entity_name = 0;
$bundleInfoService = \Drupal::service('entity_type.bundle.info');
$entity_types = $bundleInfoService
->getAllBundleInfo();
foreach ($entity_types as $type_key => $entity_type) {
foreach ($entity_type as $entity_bundle_name => $entity_bundle) {
$length = strlen($type_key) + strlen($entity_bundle_name) + 1;
if ($length > $longest_entity_name) {
$longest_entity_name = $length;
}
}
}
$allowed_length = 120 - 7;
// Full name is formatted like this: [database].drupal-[pool]-[site]-[entity]-[bundle].
$allowed_length = $allowed_length - 6 - 1 - 1 - 1 - $longest_entity_name - 1;
} catch (SyncCoreException $e) {
$form_state
->setErrorByName('backend_url', $this
->t('The Sync Core is not accessible (did not respond with 200 OK). Please configure your outbound traffic to allow traffic to the Sync Core. The error message is @message', [
'@message' => $e
->getMessage(),
]));
}
}
if (self::STEP_POOL === $step) {
$api = $form_state
->getValue('id');
if (!preg_match('@^([a-z0-9\\-_]+)$@', $api)) {
$form_state
->setErrorByName('id', $this
->t('Please only use letters, numbers and dashes.'));
}
if ('drupal' == $api || 'api-unify' == $api) {
$form_state
->setErrorByName('api', $this
->t('This name is reserved.'));
}
$site_id = $form_state
->getValue('site_id');
$length = strlen($api) + strlen($site_id);
if ($allowed_length && $length > $allowed_length) {
$form_state
->setErrorByName('id', $this
->t('The Pool machine name + site id combined mustn\'t be longer than @allowed characters.', [
'@allowed' => $allowed_length,
]));
}
}
}
/**
* {@inheritdoc}
*/
public function save(array $form, FormStateInterface $form_state) {
/**
* @var \Drupal\cms_content_sync\Entity\Pool $pool
*/
$pool = $this->entity;
if (!$pool
->label()) {
$pool->label = $this
->getRemotePools($form_state)[$pool
->id()];
}
$status = $pool
->save();
if ($status) {
if (empty(Flow::getAll())) {
$link = Link::createFromRoute($this
->t('Create a Flow'), 'entity.cms_content_sync_flow.add_form')
->toString();
\Drupal::messenger()
->addStatus($this
->t('Saved Pool %label. Well done! @create to continue the setup.', [
'%label' => $pool
->label(),
'@create' => $link,
]));
\Drupal::messenger()
->addStatus($this
->t('If you have connected another site already, @copy. Mirroring means you can simply swap the push and pull settings.', [
'@copy' => Link::createFromRoute('copy or mirror the configuration from another site', 'entity.cms_content_sync_flow.copy_remote')
->toString(),
]));
}
else {
\Drupal::messenger()
->addStatus($this
->t('Saved Pool %label.', [
'%label' => $pool
->label(),
]));
}
}
else {
\Drupal::messenger()
->addStatus($this
->t('The %label Pool could not be saved.', [
'%label' => $pool
->label(),
]));
}
// Make sure that the export is executed.
$destination = \Drupal::request()->query
->get('destination');
\Drupal::request()->query
->remove('destination');
// Keep destination for after the export.
$options = [];
if (!empty($destination)) {
$options['query']['destination'] = $destination;
}
$form_state
->setRedirect('entity.cms_content_sync_pool.export', [
'cms_content_sync_pool' => $this->entity
->id(),
], $options);
}
/**
* Helper function to check whether an Pool configuration entity exists.
*
* @param mixed $id
*/
public function exist($id) {
$entity = $this->entityTypeManager
->getStorage('cms_content_sync_pool')
->getQuery()
->condition('id', $id)
->execute();
return (bool) $entity;
}
/**
* Get default values that were provided in the URL.
*
* @return array
*/
protected function getDefaults(FormStateInterface $form_state) {
return [
'backend_url' => empty($_GET['backend_url']) ? $form_state
->getValue('default_backend_url') : $_GET['backend_url'],
'name' => empty($_GET['name']) ? $form_state
->getValue('default_name') : $_GET['name'],
'id' => empty($_GET['id']) ? $form_state
->getValue('default_id') : $_GET['id'],
];
}
/**
* Return the current step of our multi-step form.
*
* @return string
*/
protected function getCurrentFormStep(FormStateInterface $form_state) {
/**
* @var \Drupal\cms_content_sync\Entity\Pool $pool
*/
$pool = $this->entity;
if (!$pool
->getSyncCoreUrl() && !$form_state
->getValue('backend_url')) {
return self::STEP_SYNC_CORE;
}
return self::STEP_POOL;
}
/**
* Step 2: Enter or select site ID.
*
* @param bool $collapsed
*
* @return array the form
*/
protected function syncCoreForm(array $form, FormStateInterface $form_state, $collapsed = false) {
/**
* @var \Drupal\cms_content_sync\Entity\Pool $pool
*/
$pool = $this->entity;
if (Migration::alwaysUseV2()) {
return [];
}
if ($collapsed) {
$elements['backend_url'] = [
'#type' => 'hidden',
'#value' => $form_state
->getValue('backend_url') ? $form_state
->getValue('backend_url') : $pool
->getSyncCoreUrl(),
];
return $elements;
}
$elements['headline'] = [
'#markup' => '<br><br><h1>Step 1: Pick Sync Core</h1>' . '<div class="messages messages--status">' . $this
->t("The Sync Core is the central distribution service used to syndicate content between sites. If you don't have one yet, just <a href='mailto:info@cms-content-sync.io?subject=Evaluation%20Sync%20Core&body=Please%20provide%20me%20an%20evaluation%20Sync%20Core'>contact us</a>. <br>\n<br>\n<strong>Want to learn more?</strong><br>\nUse <a href='https://www.cms-content-sync.io/content-staging/'>Content Staging</a> to publish content from a private site to a public site. Pricing for Content Staging is documented <a href='https://www.cms-content-sync.io/content-sync-for-drupal/content-staging-for-drupal/'>here</a>.<br>\nUse <a href='https://www.cms-content-sync.io/content-syndication/'>Content Syndication</a> to connect many public sites to share content between them. Pricing for Content Syndication is documented <a href='https://www.cms-content-sync.io/content-sync-for-drupal/content-syndication-for-drupal/'>here</a>.\n") . '</div><br><br>',
];
$default_backend_url = $this
->getDefaults($form_state)['backend_url'];
$pools = Pool::getAll();
$urls = [];
$default_auth = null;
foreach ($pools as $existing_pool) {
$url = $existing_pool
->getSyncCoreUrl();
$urls[$url] = Helper::obfuscateCredentials($url);
}
if (count($urls) && !isset($_GET['add-sync-core']) && empty($default_backend_url)) {
$url = $pool
->getSyncCoreUrl();
if ($url) {
$urls[$url] = Helper::obfuscateCredentials($url);
}
$elements['backend_url'] = [
'#type' => 'radios',
'#title' => $this
->t('Sync Core URL'),
'#default_value' => $url ? $url : array_slice(array_keys($urls), 0, 1)[0],
'#description' => $this
->t('The backend url can be overwritten within your environment specific settings.php file by using <i>@settings</i>.', [
'@settings' => '$settings["cms_content_sync"]["pools"]["' . $this->configMachineName . '"]["backend_url"] = "http://cms-content-sync-example:8691/rest"',
'@config_machine_name' => $this->configMachineName,
]) . '<br><a href="?add-sync-core">Add new</a>',
'#options' => $urls,
'#required' => true,
];
}
else {
$elements['backend_url'] = [
'#type' => 'url',
'#title' => $this
->t('Sync Core URL'),
'#default_value' => empty($default_backend_url) ? $pool
->getSyncCoreUrl() : $default_backend_url,
'#description' => $this
->t('The backend url can be overwritten within your environment specific settings.php file by using <i>@settings</i>.', [
'@settings' => '$settings["cms_content_sync"]["pools"]["' . $this->configMachineName . '"]["backend_url"] = "http://cms-content-sync-example.de:8691/rest"',
'@config_machine_name' => $this->configMachineName,
]),
'#required' => true,
];
}
// If the backend_url is set within the settings.php,
// the form field is disabled.
if (isset($this->backendUrl)) {
$elements['backend_url']['#disabled'] = true;
$elements['backend_url']['#default_value'] = $this->backendUrl;
$elements['backend_url']['#description'] = $this
->t('The backend url is set within the environment specific settings.php file.');
}
$elements['continue'] = [
'#prefix' => '<br><br>',
'#type' => 'submit',
'#submit' => [
'::connectSyncCore',
],
'#value' => $this
->t('Connect'),
'#name' => 'connect',
// '#limit_validation_errors' => [],
'#attributes' => [
'class' => [
'button--primary',
],
],
'#ajax' => [
'callback' => '::ajaxReturn',
'wrapper' => self::CONTAINER_ID,
'method' => 'replace',
'effect' => 'fade',
'progress' => [
'type' => 'throbber',
'message' => 'loading settings...',
],
],
];
return $elements;
}
/**
* Step 3: Select an existing pool or create a new one.
*
* @throws \Exception
*
* @return array the form
*/
protected function poolForm(array $form, FormStateInterface $form_state) {
$elements = $this
->syncCoreForm($form, $form_state, true);
$elements['headline'] = [
'#markup' => '<br><br><h1>Step 3: Pool properties</h1>',
];
/**
* @var \Drupal\cms_content_sync\Entity\Pool $pool
*/
$pool = $this->entity;
$default_name = $this
->getDefaults($form_state)['name'];
$default_id = $this
->getDefaults($form_state)['id'];
$options = $pool
->isNew() && empty($default_id) ? $this
->getRemotePools($form_state) : [];
if (count($options) && !$form_state
->getValue('id')) {
$elements['id'] = [
'#type' => 'radios',
'#title' => $this
->t('Existing pools'),
'#required' => true,
'#default_value' => array_slice(array_keys($options), 0, 1)[0],
'#options' => $options,
];
}
else {
$elements['label'] = [
'#type' => 'textfield',
'#title' => $this
->t('Label'),
'#maxlength' => 255,
'#default_value' => empty($default_name) ? $form_state
->getValue('label') ? $form_state
->getValue('label') : ($pool
->label() ? $pool
->label() : (empty(Pool::getAll()['content']) ? $this
->t('Content') : '')) : $default_name,
'#description' => $this
->t("The pool name. If you aren't working with multiple pools just leave it as it is."),
'#required' => true,
];
$elements['id'] = [
'#type' => 'machine_name',
'#default_value' => empty($default_id) ? $form_state
->getValue('id') ? $form_state
->getValue('id') : ($pool
->id() ? $pool
->id() : (empty(Pool::getAll()['content']) ? 'content' : '')) : $default_id,
'#machine_name' => [
'exists' => [
$this,
'exist',
],
],
'#description' => $this
->t("A unique ID that must be identical on all sites that want to connect to this pool. If you aren't working with multiple pools just leave it as it is."),
'#disabled' => !$pool
->isNew(),
];
}
// AJAX? Show submit buttons inline.
if ($form_state
->getTriggeringElement()) {
$actions = $this
->actions($form, $form_state);
$actions['submit']['#attributes']['class'][] = 'button--primary';
$elements = array_merge($elements, $actions);
}
if (!isset($elements['label'])) {
$elements['create'] = [
'#prefix' => '<br><br>',
'#type' => 'submit',
'#submit' => [
'::createNew',
],
'#value' => $this
->t('Create new'),
'#name' => 'create',
'#ajax' => [
'callback' => '::ajaxReturn',
'wrapper' => self::CONTAINER_ID,
'method' => 'replace',
'effect' => 'fade',
'progress' => [
'type' => 'throbber',
'message' => 'loading settings...',
],
],
];
}
return $elements;
}
/**
* List all remote pools that aren't used locally yet.
*
* @throws \Exception
*/
protected function getRemotePools(FormStateInterface $form_state) {
/**
* @var Pool $entity
*/
$entity = $this->entity;
$client = $entity
->getClient();
$pools = $client
->getConfigurationService()
->listRemotePools();
$local_pools = Pool::getAll();
foreach ($pools as $id => $name) {
// Already exists locally.
if (isset($local_pools[$id])) {
unset($pools[$id]);
}
}
return $pools;
}
/**
* {@inheritdoc}
*/
protected function actions(array $form, FormStateInterface $form_state) {
$step = $this
->getCurrentFormStep($form_state);
if (self::STEP_POOL !== $step) {
return [];
}
return parent::actions($form, $form_state);
}
/**
* Return the current step of our multi-step form.
*
* @return string
*/
protected function getLastFormStep(FormStateInterface $form_state) {
$step = $form_state
->getValue('step');
if (empty($step)) {
return self::STEP_SYNC_CORE;
}
return $step;
}
}
Members
Name | Modifiers | Type | Description | Overrides |
---|---|---|---|---|
DependencySerializationTrait:: |
protected | property | ||
DependencySerializationTrait:: |
protected | property | ||
DependencySerializationTrait:: |
public | function | 2 | |
DependencySerializationTrait:: |
public | function | 2 | |
EntityForm:: |
protected | property | The entity being used by this form. | 11 |
EntityForm:: |
protected | property | The entity type manager. | 3 |
EntityForm:: |
protected | property | The module handler service. | |
EntityForm:: |
protected | property | The name of the current operation. | |
EntityForm:: |
protected | function | Returns the action form element for the current entity form. | |
EntityForm:: |
public | function | Form element #after_build callback: Updates the entity with submitted data. | |
EntityForm:: |
public | function |
Builds an updated entity object based upon the submitted form values. Overrides EntityFormInterface:: |
3 |
EntityForm:: |
public | function |
Form constructor. Overrides FormInterface:: |
13 |
EntityForm:: |
protected | function | Copies top-level form values to entity properties. | 9 |
EntityForm:: |
public | function |
Returns a string identifying the base form. Overrides BaseFormIdInterface:: |
6 |
EntityForm:: |
public | function |
Gets the form entity. Overrides EntityFormInterface:: |
|
EntityForm:: |
public | function |
Determines which entity will be used by this form from a RouteMatch object. Overrides EntityFormInterface:: |
3 |
EntityForm:: |
public | function |
Returns a unique string identifying the form. Overrides FormInterface:: |
12 |
EntityForm:: |
public | function |
Gets the operation identifying the form. Overrides EntityFormInterface:: |
|
EntityForm:: |
protected | function | Initialize the form state and the entity before the first form build. | 3 |
EntityForm:: |
protected | function | Prepares the entity object before the form is built first. | 3 |
EntityForm:: |
protected | function | Invokes the specified prepare hook variant. | |
EntityForm:: |
public | function | Process callback: assigns weights and hides extra fields. | |
EntityForm:: |
public | function |
Sets the form entity. Overrides EntityFormInterface:: |
|
EntityForm:: |
public | function |
Sets the entity type manager for this form. Overrides EntityFormInterface:: |
|
EntityForm:: |
public | function |
Sets the module handler for this form. Overrides EntityFormInterface:: |
|
EntityForm:: |
public | function |
Sets the operation for this form. Overrides EntityFormInterface:: |
|
EntityForm:: |
public | function |
This is the default entity object builder function. It is called before any
other submit handler to build the new entity object to be used by the
following submit handlers. At this point of the form workflow the entity is
validated and the form state… Overrides FormInterface:: |
20 |
FormBase:: |
protected | property | The config factory. | 3 |
FormBase:: |
protected | property | The request stack. | 1 |
FormBase:: |
protected | property | The route match. | |
FormBase:: |
protected | function | Retrieves a configuration object. | |
FormBase:: |
protected | function | Gets the config factory for this form. | 3 |
FormBase:: |
private | function | Returns the service container. | |
FormBase:: |
protected | function | Gets the current user. | |
FormBase:: |
protected | function | Gets the request object. | |
FormBase:: |
protected | function | Gets the route match. | |
FormBase:: |
protected | function | Gets the logger for a specific channel. | |
FormBase:: |
protected | function | Returns a redirect response object for the specified route. | |
FormBase:: |
public | function | Resets the configuration factory. | |
FormBase:: |
public | function | Sets the config factory for this form. | |
FormBase:: |
public | function | Sets the request stack object to use. | |
LoggerChannelTrait:: |
protected | property | The logger channel factory service. | |
LoggerChannelTrait:: |
protected | function | Gets the logger for a specific channel. | |
LoggerChannelTrait:: |
public | function | Injects the logger channel factory. | |
MessengerTrait:: |
protected | property | The messenger. | 27 |
MessengerTrait:: |
public | function | Gets the messenger. | 27 |
MessengerTrait:: |
public | function | Sets the messenger. | |
PoolForm:: |
protected | property | ||
PoolForm:: |
protected | property | ||
PoolForm:: |
protected | property | ||
PoolForm:: |
protected | function |
Returns an array of supported actions for the current entity form. Overrides EntityForm:: |
|
PoolForm:: |
public | function | Return next form. | |
PoolForm:: |
public | function | Rebuild form for next step. | |
PoolForm:: |
public | function | Rebuild form for next step. | |
PoolForm:: |
public | constant | ||
PoolForm:: |
public static | function |
Instantiates a new instance of this class. Overrides FormBase:: |
|
PoolForm:: |
public | function | Rebuild form for next step. | |
PoolForm:: |
public | function | Helper function to check whether an Pool configuration entity exists. | |
PoolForm:: |
public | function |
Gets the actual form array to be built. Overrides EntityForm:: |
|
PoolForm:: |
protected | function | Return the current step of our multi-step form. | |
PoolForm:: |
protected | function | Get default values that were provided in the URL. | |
PoolForm:: |
protected | function | Return the current step of our multi-step form. | |
PoolForm:: |
protected | function | List all remote pools that aren't used locally yet. | |
PoolForm:: |
protected | function | Step 3: Select an existing pool or create a new one. | |
PoolForm:: |
public | function |
Form submission handler for the 'save' action. Overrides EntityForm:: |
|
PoolForm:: |
public | constant | ||
PoolForm:: |
public | constant | ||
PoolForm:: |
public | constant | ||
PoolForm:: |
protected | function | Step 2: Enter or select site ID. | |
PoolForm:: |
public | function |
Validate format of input fields and make sure the Sync Core backend is
accessible to actually update it. Overrides FormBase:: |
|
PoolForm:: |
public | function | Constructs an PoolForm object. | |
RedirectDestinationTrait:: |
protected | property | The redirect destination service. | 1 |
RedirectDestinationTrait:: |
protected | function | Prepares a 'destination' URL query parameter for use with \Drupal\Core\Url. | |
RedirectDestinationTrait:: |
protected | function | Returns the redirect destination service. | |
RedirectDestinationTrait:: |
public | function | Sets the redirect destination service. | |
StringTranslationTrait:: |
protected | property | The string translation service. | 4 |
StringTranslationTrait:: |
protected | function | Formats a string containing a count of items. | |
StringTranslationTrait:: |
protected | function | Returns the number of plurals supported by a given language. | |
StringTranslationTrait:: |
protected | function | Gets the string translation service. | |
StringTranslationTrait:: |
public | function | Sets the string translation service to use. | 2 |
StringTranslationTrait:: |
protected | function | Translates a string to the current language or to a given language. |