/** * Checks access to the overview based on permissions and translatability. * * @param \Symfony\Component\Routing\Route $route * The route to check against. * @param \Drupal\Core\Session\AccountInterface $account * The currently logged in account. * * @return \Drupal\Core\Access\AccessResultInterface * The access result. */ public function access(Route $route, AccountInterface $account) { /** @var \Drupal\config_translation\ConfigMapperInterface $mapper */ $mapper = $this->configMapperManager->createInstance($route->getDefault('plugin_id')); $this->sourceLanguage = $this->languageManager->getLanguage($mapper->getLangcode()); // Allow access to the translation overview if the proper permission is // granted, the configuration has translatable pieces, and the source // language is not locked if it is present. $source_language_access = is_null($this->sourceLanguage) || !$this->sourceLanguage->isLocked(); $access = $account->hasPermission('translate configuration') && $mapper->hasSchema() && $mapper->hasTranslatable() && $source_language_access; return AccessResult::allowedIf($access)->cachePerRole(); }
/** * Checks access to the overview based on permissions and translatability. * * @param \Symfony\Component\Routing\Route $route * The route to check against. * @param \Symfony\Component\HttpFoundation\Request $request * The request object. * @param \Drupal\Core\Session\AccountInterface $account * The currently logged in account. * * @return string * A \Drupal\Core\Access\AccessInterface constant value. */ public function access(Route $route, Request $request, AccountInterface $account) { /** @var \Drupal\config_translation\ConfigMapperInterface $mapper */ $mapper = $this->configMapperManager->createInstance($route->getDefault('plugin_id')); $mapper->populateFromRequest($request); $this->sourceLanguage = $mapper->getLanguageWithFallback(); // Allow access to the translation overview if the proper permission is // granted, the configuration has translatable pieces, and the source // language is not locked. $access = $account->hasPermission('translate configuration') && $mapper->hasSchema() && $mapper->hasTranslatable() && !$this->sourceLanguage->locked; return $access ? static::ALLOW : static::DENY; }
/** * Provides the listing page for any entity type. * * @param string $mapper_id * The name of the mapper. * * @return array * A render array as expected by drupal_render(). * * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException * Throws an exception if a mapper plugin could not be instantiated from the * mapper definition in the constructor. */ public function listing($mapper_id) { $mapper_definition = $this->mapperManager->getDefinition($mapper_id); $mapper = $this->mapperManager->createInstance($mapper_id, $mapper_definition); if (!$mapper) { throw new NotFoundHttpException(); } $entity_type = $mapper->getType(); // If the mapper, for example the mapper for fields, has a custom list // controller defined, use it. Other mappers, for examples the ones for // node_type and block, fallback to the generic configuration translation // list controller. $build = $this->entityManager()->getHandler($entity_type, 'config_translation_list')->setMapperDefinition($mapper_definition)->render(); $build['#title'] = $mapper->getTypeLabel(); return $build; }
/** * Language translations overview page for a configuration name. * * @param \Symfony\Component\HttpFoundation\Request $request * Page request object. * @param \Drupal\Core\Routing\RouteMatchInterface $route_match * The route match. * @param string $plugin_id * The plugin ID of the mapper. * * @return array * Page render array. */ public function itemPage(Request $request, RouteMatchInterface $route_match, $plugin_id) { /** @var \Drupal\config_translation\ConfigMapperInterface $mapper */ $mapper = $this->configMapperManager->createInstance($plugin_id); $mapper->populateFromRequest($request); $page = array(); $page['#title'] = $this->t('Translations for %label', array('%label' => $mapper->getTitle())); $languages = $this->languageManager->getLanguages(); if (count($languages) == 1) { drupal_set_message($this->t('In order to translate configuration, the website must have at least two <a href="@url">languages</a>.', array('@url' => $this->url('entity.configurable_language.collection'))), 'warning'); } $original_langcode = $mapper->getLangcode(); if (!isset($languages[$original_langcode])) { // If the language is not configured on the site, create a dummy language // object for this listing only to ensure the user gets useful info. $language_name = $this->languageManager->getLanguageName($original_langcode); $languages[$original_langcode] = new Language(array('id' => $original_langcode, 'name' => $language_name)); } // We create a fake request object to pass into // ConfigMapperInterface::populateFromRequest() for the different languages. // Creating a separate request for each language and route is neither easily // possible nor performant. $fake_request = $request->duplicate(); $page['languages'] = array('#type' => 'table', '#header' => array($this->t('Language'), $this->t('Operations'))); foreach ($languages as $language) { $langcode = $language->getId(); // This is needed because // ConfigMapperInterface::getAddRouteParameters(), for example, // needs to return the correct language code for each table row. $fake_request->attributes->set('langcode', $langcode); $mapper->populateFromRequest($fake_request); // Prepare the language name and the operations depending on whether this // is the original language or not. if ($langcode == $original_langcode) { $language_name = '<strong>' . $this->t('@language (original)', array('@language' => $language->getName())) . '</strong>'; // Check access for the path/route for editing, so we can decide to // include a link to edit or not. $edit_access = $this->accessManager->checkNamedRoute($mapper->getBaseRouteName(), $route_match->getRawParameters()->all(), $this->account); // Build list of operations. $operations = array(); if ($edit_access) { $operations['edit'] = array('title' => $this->t('Edit'), 'url' => Url::fromRoute($mapper->getBaseRouteName(), $mapper->getBaseRouteParameters(), ['query' => ['destination' => $mapper->getOverviewPath()]])); } } else { $language_name = $language->getName(); $operations = array(); // If no translation exists for this language, link to add one. if (!$mapper->hasTranslation($language)) { $operations['add'] = array('title' => $this->t('Add'), 'url' => Url::fromRoute($mapper->getAddRouteName(), $mapper->getAddRouteParameters())); } else { // Otherwise, link to edit the existing translation. $operations['edit'] = array('title' => $this->t('Edit'), 'url' => Url::fromRoute($mapper->getEditRouteName(), $mapper->getEditRouteParameters())); $operations['delete'] = array('title' => $this->t('Delete'), 'url' => Url::fromRoute($mapper->getDeleteRouteName(), $mapper->getDeleteRouteParameters())); } } $page['languages'][$langcode]['language'] = array('#markup' => $language_name); $page['languages'][$langcode]['operations'] = array('#type' => 'operations', '#links' => $operations); } return $page; }
/** * {@inheritdoc} */ public function getTranslateRoute() { // @todo There should be some way for Config Translation module to alter // this information in on its own. if ($this->mapperManager) { $entity_type = 'menu_link_config'; /** @var \Drupal\menu_link_config\MenuLinkConfigMapper $mapper */ $mapper = $this->mapperManager->createInstance($entity_type); $mapper->setEntity($this->getEntity()); return array('route_name' => $mapper->getOverviewRouteName(), 'route_parameters' => $mapper->getOverviewRouteParameters()); } }
/** * {@inheritdoc} */ public function buildForm(array $form, array &$form_state, Request $request = NULL, $plugin_id = NULL, $langcode = NULL) { /** @var \Drupal\config_translation\ConfigMapperInterface $mapper */ $mapper = $this->configMapperManager->createInstance($plugin_id); $mapper->populateFromRequest($request); $language = language_load($langcode); if (!$language) { throw new NotFoundHttpException(); } $this->mapper = $mapper; $this->language = $language; return parent::buildForm($form, $form_state); }
/** * {@inheritdoc} */ public function buildForm(array $form, FormStateInterface $form_state, RouteMatchInterface $route_match = NULL, $plugin_id = NULL, $langcode = NULL) { /** @var \Drupal\config_translation\ConfigMapperInterface $mapper */ $mapper = $this->configMapperManager->createInstance($plugin_id); $mapper->populateFromRouteMatch($route_match); $language = $this->languageManager->getLanguage($langcode); if (!$language) { throw new NotFoundHttpException(); } $this->mapper = $mapper; $this->language = $language; return parent::buildForm($form, $form_state); }
/** * Implements \Drupal\Core\Form\FormInterface::buildForm(). * * Builds configuration form with metadata and values from the source * language. * * @param array $form * An associative array containing the structure of the form. * @param \Drupal\Core\Form\FormStateInterface $form_state * The current state of the form. * @param \Drupal\Core\Routing\RouteMatchInterface $route_match * (optional) The route match. * @param string $plugin_id * (optional) The plugin ID of the mapper. * @param string $langcode * (optional) The language code of the language the form is adding or * editing. * * @return array * The form structure. * * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException * Throws an exception if the language code provided as a query parameter in * the request does not match an active language. */ public function buildForm(array $form, FormStateInterface $form_state, RouteMatchInterface $route_match = NULL, $plugin_id = NULL, $langcode = NULL) { /** @var \Drupal\config_translation\ConfigMapperInterface $mapper */ $mapper = $this->configMapperManager->createInstance($plugin_id); $mapper->populateFromRouteMatch($route_match); $language = $this->languageManager->getLanguage($langcode); if (!$language) { throw new NotFoundHttpException(); } $this->mapper = $mapper; $this->language = $language; // ConfigTranslationFormAccess will not grant access if this raises an // exception, so we can call this without a try-catch block here. $langcode = $this->mapper->getLangcode(); $this->sourceLanguage = $this->languageManager->getLanguage($langcode); // Get base language configuration to display in the form before setting the // language to use for the form. This avoids repetitively settings and // resetting the language to get original values later. $this->baseConfigData = $this->mapper->getConfigData(); // Set the translation target language on the configuration factory. $original_language = $this->languageManager->getConfigOverrideLanguage(); $this->languageManager->setConfigOverrideLanguage($this->language); // Add some information to the form state for easier form altering. $form_state->set('config_translation_mapper', $this->mapper); $form_state->set('config_translation_language', $this->language); $form_state->set('config_translation_source_language', $this->sourceLanguage); $form['#attached']['library'][] = 'config_translation/drupal.config_translation.admin'; // Even though this is a nested form, we do not set #tree to TRUE because // the form value structure is generated by using #parents for each element. // @see \Drupal\config_translation\FormElement\FormElementBase::getElements() $form['config_names'] = array('#type' => 'container'); foreach ($this->mapper->getConfigNames() as $name) { $form['config_names'][$name] = array('#type' => 'container'); $schema = $this->typedConfigManager->get($name); $source_config = $this->baseConfigData[$name]; $translation_config = $this->configFactory()->get($name)->get(); if ($form_element = $this->createFormElement($schema)) { $parents = array('config_names', $name); $form['config_names'][$name] += $form_element->getTranslationBuild($this->sourceLanguage, $this->language, $source_config, $translation_config, $parents); } } $form['actions']['#type'] = 'actions'; $form['actions']['submit'] = array('#type' => 'submit', '#value' => $this->t('Save translation'), '#button_type' => 'primary'); // Set the configuration language back. $this->languageManager->setConfigOverrideLanguage($original_language); return $form; }
/** * Implements \Drupal\Core\Form\FormInterface::buildForm(). * * Builds configuration form with metadata and values from the source * language. * * @param array $form * An associative array containing the structure of the form. * @param array $form_state * An associative array containing the current state of the form. * @param \Symfony\Component\HttpFoundation\Request $request * (optional) Page request object. * @param string $plugin_id * (optional) The plugin ID of the mapper. * @param string $langcode * (optional) The language code of the language the form is adding or * editing. * * @return array * The form structure. * * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException * Throws an exception if the language code provided as a query parameter in * the request does not match an active language. */ public function buildForm(array $form, array &$form_state, Request $request = NULL, $plugin_id = NULL, $langcode = NULL) { /** @var \Drupal\config_translation\ConfigMapperInterface $mapper */ $mapper = $this->configMapperManager->createInstance($plugin_id); $mapper->populateFromRequest($request); $language = language_load($langcode); if (!$language) { throw new NotFoundHttpException(); } $this->mapper = $mapper; $this->language = $language; $this->sourceLanguage = $this->mapper->getLanguageWithFallback(); // Get base language configuration to display in the form before setting the // language to use for the form. This avoids repetitively settings and // resetting the language to get original values later. $config_factory = $this->configFactory(); $old_state = $config_factory->getOverrideState(); $config_factory->setOverrideState(FALSE); $this->baseConfigData = $this->mapper->getConfigData(); $config_factory->setOverrideState($old_state); // Set the translation target language on the configuration factory. $original_language = $this->languageManager->getConfigOverrideLanguage(); $this->languageManager->setConfigOverrideLanguage($this->language); // Add some information to the form state for easier form altering. $form_state['config_translation_mapper'] = $this->mapper; $form_state['config_translation_language'] = $this->language; $form_state['config_translation_source_language'] = $this->sourceLanguage; $form['#attached']['library'][] = 'config_translation/drupal.config_translation.admin'; $form['config_names'] = array('#type' => 'container', '#tree' => TRUE); foreach ($this->mapper->getConfigNames() as $name) { $form['config_names'][$name] = array('#type' => 'container'); $form['config_names'][$name] += $this->buildConfigForm($this->typedConfigManager->get($name), $config_factory->get($name)->get(), $this->baseConfigData[$name]); } $form['actions']['#type'] = 'actions'; $form['actions']['submit'] = array('#type' => 'submit', '#value' => $this->t('Save translation'), '#button_type' => 'primary'); // Set the configuration language back. $this->languageManager->setConfigOverrideLanguage($original_language); return $form; }
/** * Gets the mapper. * * @param \Drupal\tmgmt\JobItemInterface $job_item * Gets a job item as a parameter. * * @return \Drupal\config_translation\ConfigMapperInterface $config_mapper * Returns the config mapper. * * @throws \Drupal\tmgmt\TMGMTException * If there is no entity, throws an exception. */ protected function getMapper(JobItemInterface $job_item) { // @todo: Inject dependencies. if ($job_item->getItemType() == static::SIMPLE_CONFIG) { $config_mapper = $this->configMapperManager->createInstance($job_item->getItemId()); } else { $config_mapper = $this->configMapperManager->createInstance($job_item->getItemType()); /** @var \Drupal\Core\Config\Entity\ConfigEntityTypeInterface $entity_type */ $entity_type = $this->entityManager->getDefinition($config_mapper->getType()); $pos = strpos($job_item->getItemId(), $entity_type->getConfigPrefix()); if ($pos !== FALSE) { $entity_id = str_replace($entity_type->getConfigPrefix() . '.', '', $job_item->getItemId()); } else { throw new TMGMTException(t('Item ID does not contain the full config object name.')); } $entity = $this->entityManager->getStorage($config_mapper->getType())->load($entity_id); if (!$entity) { throw new TMGMTException(t('Unable to load entity %type with id %id', array('%type' => $job_item->getItemType(), '%id' => $entity_id))); } $config_mapper->setEntity($entity); } return $config_mapper; }
/** * Language translations overview page for a configuration name. * * @param \Symfony\Component\HttpFoundation\Request $request * Page request object. * @param string $plugin_id * The plugin ID of the mapper. * * @return array * Page render array. */ public function itemPage(Request $request, $plugin_id) { /** @var \Drupal\config_translation\ConfigMapperInterface $mapper */ $mapper = $this->configMapperManager->createInstance($plugin_id); $mapper->populateFromRequest($request); $page = array(); $page['#title'] = $this->t('Translations for %label', array('%label' => $mapper->getTitle())); // It is possible the original language this configuration was saved with is // not on the system. For example, the configuration shipped in English but // the site has no English configured. Represent the original language in // the table even if it is not currently configured. $languages = $this->languageManager->getLanguages(); $original_langcode = $mapper->getLangcode(); if (!isset($languages[$original_langcode])) { $language_name = $this->languageManager->getLanguageName($original_langcode); if ($original_langcode == 'en') { $language_name = $this->t('Built-in English'); } // Create a dummy language object for this listing only. $languages[$original_langcode] = new Language(array('id' => $original_langcode, 'name' => $language_name)); } // We create a fake request object to pass into // ConfigMapperInterface::populateFromRequest() for the different languages. // Creating a separate request for each language and route is neither easily // possible nor performant. $fake_request = $request->duplicate(); $page['languages'] = array('#type' => 'table', '#header' => array($this->t('Language'), $this->t('Operations'))); foreach ($languages as $language) { $langcode = $language->id; // This is needed because // ConfigMapperInterface::getAddRouteParameters(), for example, // needs to return the correct language code for each table row. $fake_request->attributes->set('langcode', $langcode); $mapper->populateFromRequest($fake_request); // Prepare the language name and the operations depending on whether this // is the original language or not. if ($langcode == $original_langcode) { $language_name = '<strong>' . $this->t('@language (original)', array('@language' => $language->name)) . '</strong>'; // Check access for the path/route for editing, so we can decide to // include a link to edit or not. $route_request = $this->getRequestForPath($request, $mapper->getBasePath()); $edit_access = FALSE; if (!empty($route_request)) { $route_name = $route_request->attributes->get(RouteObjectInterface::ROUTE_NAME); // Note that the parameters don't really matter here since we're // passing in the request which already has the upcast attributes. $parameters = array(); $edit_access = $this->accessManager->checkNamedRoute($route_name, $parameters, $this->account, $route_request); } // Build list of operations. $operations = array(); if ($edit_access) { $operations['edit'] = array('title' => $this->t('Edit'), 'route_name' => $mapper->getBaseRouteName(), 'route_parameters' => $mapper->getBaseRouteParameters(), 'query' => array('destination' => $mapper->getOverviewPath())); } } else { $language_name = $language->name; $operations = array(); // If no translation exists for this language, link to add one. if (!$mapper->hasTranslation($language)) { $operations['add'] = array('title' => $this->t('Add'), 'route_name' => $mapper->getAddRouteName(), 'route_parameters' => $mapper->getAddRouteParameters()); } else { // Otherwise, link to edit the existing translation. $operations['edit'] = array('title' => $this->t('Edit'), 'route_name' => $mapper->getEditRouteName(), 'route_parameters' => $mapper->getEditRouteParameters()); $operations['delete'] = array('title' => $this->t('Delete'), 'route_name' => $mapper->getDeleteRouteName(), 'route_parameters' => $mapper->getDeleteRouteParameters()); } } $page['languages'][$langcode]['language'] = array('#markup' => $language_name); $page['languages'][$langcode]['operations'] = array('#type' => 'operations', '#links' => $operations); } return $page; }
/** * {@inheritdoc} */ public function buildForm(array $form, FormStateInterface $form_state, array $build = NULL, $plugin_id = NULL) { // Store the entity in the form state so we can easily create the job in the // submit handler. $mapper_definition = \Drupal::service('plugin.manager.config_translation.mapper')->getDefinition($plugin_id); /** @var \Drupal\config_translation\ConfigMapperInterface $mapper */ $mapper = $this->configMapperManager->createInstance($plugin_id); $mapper->populateFromRouteMatch($this->routeMatch); $form_state->set('mapper', $mapper); if (!isset($mapper_definition['entity_type'])) { $form_state->set('item_type', ConfigSource::SIMPLE_CONFIG); $form_state->set('item_id', $mapper_definition['id']); } else { $id = $mapper->getConfigNames()[0]; $form_state->set('id', $id); $form_state->set('item_type', $plugin_id); $form_state->set('item_id', $id); } $form['#title'] = $this->t('Translations of @title', array('@title' => $mapper->getTitle())); $overview = $build['languages']; $form['top_actions'] = array('#type' => 'details', '#title' => t('Operations'), '#open' => TRUE, '#attributes' => array('class' => array('tmgmt-source-operations-wrapper'))); $form['top_actions']['request'] = array('#type' => 'submit', '#button_type' => 'primary', '#value' => $this->t('Request translation'), '#submit' => array('::submitForm')); tmgmt_add_cart_form($form['top_actions'], $form_state, 'config', $form_state->get('item_type'), $form_state->get('item_id')); // Inject our additional column into the header. array_splice($overview['#header'], -1, 0, array(t('Pending Translations'))); // Make this a tableselect form. $form['languages'] = array('#type' => 'tableselect', '#header' => $overview['#header'], '#options' => array()); $languages = \Drupal::languageManager()->getLanguages(); // Check if there is a job / job item that references this translation. $items = tmgmt_job_item_load_latest('config', $form_state->get('item_type'), $form_state->get('item_id'), $mapper->getLangcode()); foreach ($languages as $langcode => $language) { if ($langcode == LanguageInterface::LANGCODE_DEFAULT) { // Never show language neutral on the overview. continue; } // Since the keys are numeric and in the same order we can shift one element // after the other from the original non-form rows. $option = $overview[$langcode]; if ($langcode == $mapper->getLangcode()) { $additional = array('data' => array('#markup' => '<strong>' . t('Source') . '</strong>')); // This is the source object so we disable the checkbox for this row. $form['languages'][$langcode] = array('#type' => 'checkbox', '#disabled' => TRUE); } elseif (isset($items[$langcode])) { $item = $items[$langcode]; $states = JobItem::getStates(); $additional = \Drupal::l($states[$item->getState()], $item->urlInfo()->setOption('query', array('destination' => Url::fromRoute('<current>')->getInternalPath()))); // Disable the checkbox for this row since there is already a translation // in progress that has not yet been finished. This way we make sure that // we don't stack multiple active translations for the same item on top // of each other. $form['languages'][$langcode] = array('#type' => 'checkbox', '#disabled' => TRUE); } else { // There is no translation job / job item for this target language. $additional = t('None'); } // Inject the additional column into the array. // The generated form structure has changed, support both an additional // 'data' key (that is not supported by tableselect) and the old version // without. if (isset($option['data'])) { array_splice($option['data'], -1, 0, array($additional)); // Append the current option array to the form. $form['languages']['#options'][$langcode] = $option['data']; } else { array_splice($option, -1, 0, array($additional)); // Append the current option array to the form. $form['languages']['#options'][$langcode] = array(drupal_render($option['language']), $additional, drupal_render($option['operations'])); } } return $form; }
/** * Gets a configuration mapper using a route match. * * @param \Drupal\Core\Routing\RouteMatchInterface $route_match * The route match to populate the mapper with. * * @return \Drupal\config_translation\ConfigMapperInterface * The configuration mapper. */ protected function getMapperFromRouteMatch(RouteMatchInterface $route_match) { $mapper = $this->configMapperManager->createInstance($route_match->getRouteObject()->getDefault('plugin_id')); $mapper->populateFromRouteMatch($route_match); return $mapper; }