Issue #1807776 by plach, Berdir, YesCT, Gábor Hojtsy, bforchhammer, Bojhan, webchick: Support both simple and editorial workflows for translating entities.

8.0.x
webchick 2013-01-29 22:52:39 -08:00
parent afeed9ed44
commit ae9f336ed3
25 changed files with 666 additions and 265 deletions

View File

@ -120,6 +120,11 @@ use Drupal\Core\Cache\CacheBackendInterface;
* entity.
* - menu_path_wildcard: (optional) A string identifying the menu loader in the
* router path.
* - permission_granularity: (optional) Specifies whether a module exposing
* permissions for the current entity type should use entity-type level
* granularity, bundle level granularity or just skip this entity. The allowed
* values are respectively "entity_type", "bundle" or FALSE. Defaults to
* "entity_type".
*
* The defaults for the plugin definition are provided in
* \Drupal\Core\Entity\EntityManager::defaults.
@ -159,6 +164,7 @@ class EntityManager extends PluginManagerBase {
'access_controller_class' => 'Drupal\Core\Entity\EntityAccessController',
'static_cache' => TRUE,
'translation' => array(),
'permission_granularity' => 'entity_type',
);
/**

View File

@ -9,12 +9,29 @@
namespace Drupal\comment;
use Drupal\Core\Entity\EntityInterface;
use Drupal\translation_entity\EntityTranslationController;
use Drupal\translation_entity\EntityTranslationControllerNG;
/**
* Defines the translation controller class for comments.
*/
class CommentTranslationController extends EntityTranslationController {
class CommentTranslationController extends EntityTranslationControllerNG {
/**
* Overrides EntityTranslationController::getAccess().
*/
public function getAccess(EntityInterface $entity, $op) {
switch ($op) {
case 'view':
return user_access('access comments');
case 'update':
return comment_access('edit', $entity);
case 'delete':
return user_access('administer comments');
case 'create':
return user_access('post comments');
}
return parent::getAccess($entity, $op);
}
/**
* Overrides EntityTranslationController::entityFormTitle().

View File

@ -25,7 +25,7 @@ use Drupal\Core\Annotation\Translation;
* form_controller_class = {
* "default" = "Drupal\comment\CommentFormController"
* },
* translation_controller_class = "Drupal\translation_entity\EntityTranslationControllerNG",
* translation_controller_class = "Drupal\comment\CommentTranslationController",
* base_table = "comment",
* uri_callback = "comment_uri",
* fieldable = TRUE,

View File

@ -34,9 +34,6 @@ class CommentTranslationUITest extends EntityTranslationUITest {
);
}
/**
* Overrides \Drupal\simpletest\WebTestBase::setUp().
*/
function setUp() {
$this->entityType = 'comment';
$this->nodeBundle = 'article';
@ -58,7 +55,7 @@ class CommentTranslationUITest extends EntityTranslationUITest {
* Overrides \Drupal\translation_entity\Tests\EntityTranslationUITest::getTranslatorPermission().
*/
function getTranslatorPermissions() {
return array('post comments', 'administer comments', "translate $this->entityType entities", 'edit original values');
return array_merge(parent::getTranslatorPermissions(), array('post comments', 'administer comments'));
}
/**
@ -100,7 +97,7 @@ class CommentTranslationUITest extends EntityTranslationUITest {
*/
function testTranslateLinkCommentAdminPage() {
$this->drupalCreateContentType(array('type' => 'page', 'name' => 'page'));
$this->admin_user = $this->drupalCreateUser(array('access administration pages', 'administer comments', 'translate any entity'));
$this->admin_user = $this->drupalCreateUser(array_merge(parent::getTranslatorPermissions(), array('access administration pages', 'administer comments')));
$this->drupalLogin($this->admin_user);
$cid_translatable = $this->createEntity(array(), $this->langcodes[0], $this->nodeBundle);

View File

@ -336,7 +336,7 @@ class NodeFormController extends EntityFormController {
}
$element['preview'] = array(
'#access' => $preview_mode != DRUPAL_DISABLED,
'#access' => $preview_mode != DRUPAL_DISABLED && (node_access('create', $node) || node_access('update', $node)),
'#value' => t('Preview'),
'#weight' => 20,
'#validate' => array(
@ -429,6 +429,9 @@ class NodeFormController extends EntityFormController {
* A reference to a keyed array containing the current state of the form.
*/
public function preview(array $form, array &$form_state) {
// @todo Remove this: we should not have explicit includes in autoloaded
// classes.
module_load_include('inc', 'node', 'node.pages');
drupal_set_title(t('Preview'), PASS_THROUGH);
$form_state['node_preview'] = node_preview($this->getEntity($form_state));
$form_state['rebuild'] = TRUE;

View File

@ -39,7 +39,8 @@ use Drupal\Core\Annotation\Translation;
* },
* bundle_keys = {
* "bundle" = "type"
* }
* },
* permission_granularity = "bundle"
* )
*/
class Node extends Entity implements ContentEntityInterface {

View File

@ -34,9 +34,6 @@ class NodeTranslationUITest extends EntityTranslationUITest {
);
}
/**
* Overrides \Drupal\simpletest\WebTestBase::setUp().
*/
function setUp() {
$this->entityType = 'node';
$this->bundle = 'article';
@ -56,7 +53,7 @@ class NodeTranslationUITest extends EntityTranslationUITest {
* Overrides \Drupal\translation_entity\Tests\EntityTranslationUITest::getTranslatorPermission().
*/
function getTranslatorPermissions() {
return array("edit any $this->bundle content", "translate $this->entityType entities", 'edit original values');
return array_merge(parent::getTranslatorPermissions(), array("edit any $this->bundle content"));
}
/**
@ -80,7 +77,7 @@ class NodeTranslationUITest extends EntityTranslationUITest {
* Tests field translation form.
*/
function testFieldTranslationForm() {
$admin_user = $this->drupalCreateUser(array('translate any entity', 'access administration pages', 'bypass node access', 'administer node fields'));
$admin_user = $this->drupalCreateUser(array_merge($this->getTranslatorPermissions(), array('access administration pages', 'bypass node access', 'administer node fields')));
$this->drupalLogin($admin_user);
$article = $this->drupalCreateNode(array('type' => 'article', 'langcode' => 'en'));

View File

@ -462,12 +462,14 @@ abstract class WebTestBase extends TestBase {
* @param array $permissions
* Array of permission names to assign to user. Note that the user always
* has the default permissions derived from the "authenticated users" role.
* @param $name
* The user name.
*
* @return object|false
* A fully loaded user object with pass_raw property, or FALSE if account
* creation fails.
*/
protected function drupalCreateUser(array $permissions = array()) {
protected function drupalCreateUser(array $permissions = array(), $name = NULL) {
// Create a role with the given permission set, if any.
$rid = FALSE;
if ($permissions) {
@ -479,7 +481,7 @@ abstract class WebTestBase extends TestBase {
// Create a user assigned to that role.
$edit = array();
$edit['name'] = $this->randomName();
$edit['name'] = !empty($name) ? $name : $this->randomName();
$edit['mail'] = $edit['name'] . '@example.com';
$edit['pass'] = user_password();
$edit['status'] = 1;

View File

@ -68,12 +68,12 @@ class EntityAccessTest extends WebTestBase {
'view' => TRUE,
), $entity);
// The custom user is not allowed to view test entities.
// The custom user is not allowed to perform any operation on test entities.
$custom_user = $this->drupalCreateUser();
$this->assertEntityAccess(array(
'create' => TRUE,
'update' => TRUE,
'delete' => TRUE,
'create' => FALSE,
'update' => FALSE,
'delete' => FALSE,
'view' => FALSE,
), $entity, $custom_user);
}

View File

@ -30,21 +30,21 @@ class EntityTestAccessController implements EntityAccessControllerInterface {
* Implements \Drupal\Core\Entity\EntityAccessControllerInterface::createAccess().
*/
public function createAccess(EntityInterface $entity, $langcode = LANGUAGE_DEFAULT, User $account = NULL) {
return TRUE;
return user_access('administer entity_test content', $account);
}
/**
* Implements \Drupal\Core\Entity\EntityAccessControllerInterface::updateAccess().
*/
public function updateAccess(EntityInterface $entity, $langcode = LANGUAGE_DEFAULT, User $account = NULL) {
return TRUE;
return user_access('administer entity_test content', $account);
}
/**
* Implements \Drupal\Core\Entity\EntityAccessControllerInterface::deleteAccess().
*/
public function deleteAccess(EntityInterface $entity, $langcode = LANGUAGE_DEFAULT, User $account = NULL) {
return TRUE;
return user_access('administer entity_test content', $account);
}
}

View File

@ -38,7 +38,8 @@ use Drupal\Core\Annotation\Translation;
* bundle_keys = {
* "bundle" = "vid"
* },
* menu_base_path = "taxonomy/term/%taxonomy_term"
* menu_base_path = "taxonomy/term/%taxonomy_term",
* permission_granularity = "bundle"
* )
*/
class Term extends Entity implements ContentEntityInterface {

View File

@ -15,6 +15,20 @@ use Drupal\translation_entity\EntityTranslationController;
*/
class TermTranslationController extends EntityTranslationController {
/**
* Overrides EntityTranslationController::getAccess().
*/
public function getAccess(EntityInterface $entity, $op) {
switch ($op) {
case 'create':
case 'update':
return taxonomy_term_access('edit', $entity);
case 'delete':
return taxonomy_term_access('delete', $entity);
}
return parent::getAccess($entity, $op);
}
/**
* Overrides EntityTranslationController::entityFormAlter().
*/

View File

@ -41,9 +41,6 @@ class TermTranslationUITest extends EntityTranslationUITest {
);
}
/**
* Overrides \Drupal\simpletest\WebTestBase::setUp().
*/
function setUp() {
$this->entityType = 'taxonomy_term';
$this->bundle = 'tags';
@ -73,7 +70,7 @@ class TermTranslationUITest extends EntityTranslationUITest {
* Overrides \Drupal\translation_entity\Tests\EntityTranslationUITest::getTranslatorPermission().
*/
function getTranslatorPermissions() {
return array('administer taxonomy', "translate $this->entityType entities", 'edit original values');
return array_merge(parent::getTranslatorPermissions(), array('administer taxonomy'));
}
/**
@ -102,7 +99,7 @@ class TermTranslationUITest extends EntityTranslationUITest {
* Tests translate link on vocabulary term list.
*/
function testTranslateLinkVocabularyAdminPage() {
$this->admin_user = $this->drupalCreateUser(array('access administration pages', 'administer taxonomy', 'translate any entity'));
$this->admin_user = $this->drupalCreateUser(array_merge(parent::getTranslatorPermissions(), array('access administration pages', 'administer taxonomy')));
$this->drupalLogin($this->admin_user);
$translatable_tid = $this->createEntity(array(), $this->langcodes[0], $this->vocabulary->id());

View File

@ -99,9 +99,17 @@ class EntityTranslationController implements EntityTranslationControllerInterfac
/**
* Implements EntityTranslationControllerInterface::getTranslationAccess().
*/
public function getTranslationAccess(EntityInterface $entity, $langcode) {
$entity_type = $entity->entityType();
return (user_access('translate any entity') || user_access("translate $entity_type entities")) && ($langcode != $entity->language()->langcode || user_access('edit original values'));
public function getTranslationAccess(EntityInterface $entity, $op) {
// @todo Move this logic into a translation access controller checking also
// the translation language and the given account.
$info = $entity->entityInfo();
$translate_permission = TRUE;
// If no permission granularity is defined this entity type does not need an
// explicit translate permission.
if (!user_access('translate any entity') && !empty($info['permission_granularity'])) {
$translate_permission = user_access($info['permission_granularity'] == 'bundle' ? "translate {$entity->bundle()} {$entity->entityType()}" : "translate {$entity->entityType()}");
}
return $translate_permission && user_access("$op entity translations");
}
/**
@ -203,6 +211,7 @@ class EntityTranslationController implements EntityTranslationControllerInterfac
'#value' => t('Delete translation'),
'#weight' => $weight,
'#submit' => array(array($this, 'entityFormDeleteTranslation')),
'#access' => $this->getTranslationAccess($entity, 'delete'),
);
}
@ -220,7 +229,7 @@ class EntityTranslationController implements EntityTranslationControllerInterfac
'#collapsed' => TRUE,
'#tree' => TRUE,
'#weight' => 10,
'#access' => $this->getTranslationAccess($entity, $form_langcode),
'#access' => $this->getTranslationAccess($entity, $source_langcode ? 'create' : 'update'),
'#multilingual' => TRUE,
);
@ -259,17 +268,11 @@ class EntityTranslationController implements EntityTranslationControllerInterfac
}
/**
* Process callback: Determines which elements get clue in the form.
*
* @param array $element
* Form API element.
*
* @return array
* A processed element with the shared elements marked with a clue.
* Process callback: determines which elements get clue in the form.
*
* @see \Drupal\translation_entity\EntityTranslationController::entityFormAlter()
*/
public function entityFormSharedElements($element) {
public function entityFormSharedElements($element, $form_state, $form) {
static $ignored_types;
// @todo Find a more reliable way to determine if a form element concerns a
@ -280,7 +283,7 @@ class EntityTranslationController implements EntityTranslationControllerInterfac
foreach (element_children($element) as $key) {
if (!isset($element[$key]['#type'])) {
$this->entityFormSharedElements($element[$key]);
$this->entityFormSharedElements($element[$key], $form_state, $form);
}
else {
// Ignore non-widget form elements.
@ -289,8 +292,16 @@ class EntityTranslationController implements EntityTranslationControllerInterfac
}
// Elements are considered to be non multilingual by default.
if (empty($element[$key]['#multilingual'])) {
// If we are displaying a multilingual entity form we need to provide
// translatability clues, otherwise the shared form elements should be
// hidden.
if (empty($form_state['translation_entity']['translation_form'])) {
$this->addTranslatabilityClue($element[$key]);
}
else {
$element[$key]['#access'] = FALSE;
}
}
}
}

View File

@ -132,17 +132,21 @@ interface EntityTranslationControllerInterface {
public function getAccess(EntityInterface $entity, $op);
/**
* Checks if a user is allowed to edit the given translation.
* Checks if the user can perform the given operation on translations of the
* wrapped entity.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity whose translation has to be accessed.
* @param string $langcode
* The language code identifying the translation to be accessed.
* @param $op
* The operation to be performed on the translation. Possible values are:
* - "create"
* - "update"
* - "delete"
*
* @return boolean
* TRUE if the operation may be performed, FALSE otherwise.
*/
public function getTranslationAccess(EntityInterface $entity, $langcode);
public function getTranslationAccess(EntityInterface $entity, $op);
/**
* Retrieves the source language for the translation being created.

View File

@ -15,7 +15,14 @@ use Drupal\Core\Entity\EntityInterface;
class EntityTranslationControllerNG extends EntityTranslationController {
/**
* Overrides EntityTranslationController::removeTranslation().
* Overrides \Drupal\translation_entity\EntityTranslationController::getAccess().
*/
public function getAccess(EntityInterface $entity, $op) {
return $entity->access($op);
}
/**
* Overrides \Drupal\translation_entity\EntityTranslationControllerInterface::removeTranslation().
*/
public function removeTranslation(EntityInterface $entity, $langcode) {
$translation = $entity->getTranslation($langcode);
@ -23,4 +30,5 @@ class EntityTranslationControllerNG extends EntityTranslationController {
$translation->$property_name = array();
}
}
}

View File

@ -29,21 +29,11 @@ class ConfigTestTranslationUITest extends EntityTranslationUITest {
);
}
/**
* Overrides \Drupal\simpletest\WebTestBase::setUp().
*/
function setUp() {
$this->entityType = 'config_test';
parent::setUp();
}
/**
* Overrides \Drupal\translation_entity\Tests\EntityTranslationUITest::getTranslatorPermission().
*/
function getTranslatorPermissions() {
return array("translate $this->entityType entities", 'edit original values');
}
/**
* Overrides \Drupal\translation_entity\Tests\EntityTranslationUITest::getNewEntityValues().
*/

View File

@ -21,7 +21,7 @@ class EntityTestTranslationUITest extends EntityTranslationUITest {
public static function getInfo() {
return array(
'name' => 'Entity Test Translation UI',
'name' => 'Entity Test translation UI',
'description' => 'Tests the test entity translation UI with the test entity.',
'group' => 'Entity Translation UI',
);
@ -40,7 +40,7 @@ class EntityTestTranslationUITest extends EntityTranslationUITest {
* Overrides \Drupal\translation_entity\Tests\EntityTranslationUITest::getTranslatorPermission().
*/
function getTranslatorPermissions() {
return array('administer entity_test content', "translate $this->entityType entities", 'edit original values');
return array_merge(parent::getTranslatorPermissions(), array('administer entity_test content'));
}
/**

View File

@ -0,0 +1,215 @@
<?php
/**
* @file
* Contains \Drupal\entity\Tests\EntityTranslationTestBase.
*/
namespace Drupal\translation_entity\Tests;
use Drupal\Core\Entity\DatabaseStorageControllerNG;
use Drupal\Core\Language\Language;
use Drupal\simpletest\WebTestBase;
/**
* Tests entity translation workflows.
*/
abstract class EntityTranslationTestBase extends WebTestBase {
/**
* The entity type being tested.
*
* @var string
*/
protected $entityType = 'entity_test_mul';
/**
* The bundle being tested.
*
* @var string
*/
protected $bundle;
/**
* The enabled languages.
*
* @var array
*/
protected $langcodes;
/**
* The account to be used to test translation operations.
*
* @var \Drupal\user\Plugin\Core\Entity\User
*/
protected $translator;
/**
* The account to be used to test multilingual entity editing.
*
* @var \Drupal\user\Plugin\Core\Entity\User
*/
protected $editor;
/**
* The account to be used to test access to both workflows.
*
* @var \Drupal\user\Plugin\Core\Entity\User
*/
protected $administrator;
/**
* The name of the field used to test translation.
*
* @var string
*/
protected $fieldName;
/**
* The translation controller for the current entity type.
*
* @var \Drupal\translation_entity\EntityTranslationControllerInterface
*/
protected $controller;
function setUp() {
parent::setUp();
$this->setupLanguages();
$this->setupBundle();
$this->enableTranslation();
$this->setupUsers();
$this->setupTestFields();
$this->controller = translation_entity_controller($this->entityType);
}
/**
* Enables additional languages.
*/
protected function setupLanguages() {
$this->langcodes = array('it', 'fr');
foreach ($this->langcodes as $langcode) {
language_save(new Language(array('langcode' => $langcode)));
}
array_unshift($this->langcodes, language_default()->langcode);
}
/**
* Returns an array of permissions needed for the translator.
*/
protected function getTranslatorPermissions() {
return array_filter(array($this->getTranslatePermission(), 'create entity translations', 'update entity translations', 'delete entity translations'));
}
/**
* Returns the translate permissions for the current entity and bundle.
*/
protected function getTranslatePermission() {
$info = entity_get_info($this->entityType);
if (!empty($info['permission_granularity'])) {
return $info['permission_granularity'] == 'bundle' ? "translate {$this->bundle} {$this->entityType}" : "translate {$this->entityType}";
}
}
/**
* Returns an array of permissions needed for the editor.
*/
protected function getEditorPermissions() {
// Every entity-type-specific test needs to define these.
return array();
}
/**
* Creates and activates translator, editor and admin users.
*/
protected function setupUsers() {
$this->translator = $this->drupalCreateUser($this->getTranslatorPermissions(), 'translator');
$this->editor = $this->drupalCreateUser($this->getEditorPermissions(), 'editor');
$this->administrator = $this->drupalCreateUser(array_merge($this->getEditorPermissions(), $this->getTranslatorPermissions()), 'administrator');
$this->drupalLogin($this->translator);
}
/**
* Creates or initializes the bundle date if needed.
*/
protected function setupBundle() {
if (empty($this->bundle)) {
$this->bundle = $this->entityType;
}
}
/**
* Enables translation for the current entity type and bundle.
*/
protected function enableTranslation() {
// Enable translation for the current entity type and ensure the change is
// picked up.
translation_entity_set_config($this->entityType, $this->bundle, 'enabled', TRUE);
drupal_static_reset();
entity_info_cache_clear();
menu_router_rebuild();
}
/**
* Creates the test fields.
*/
protected function setupTestFields() {
$this->fieldName = 'field_test_et_ui_test';
$field = array(
'field_name' => $this->fieldName,
'type' => 'text',
'cardinality' => 1,
'translatable' => TRUE,
);
field_create_field($field);
$instance = array(
'entity_type' => $this->entityType,
'field_name' => $this->fieldName,
'bundle' => $this->bundle,
'label' => 'Test translatable text-field',
'widget' => array(
'type' => 'text_textfield',
'weight' => 0,
),
);
field_create_instance($instance);
}
/**
* Creates the entity to be translated.
*
* @param array $values
* An array of initial values for the entity.
* @param string $langcode
* The initial language code of the entity.
* @param string $bundle_name
* (optional) The entity bundle, if the entity uses bundles. Defaults to
* NULL. If left NULL, $this->bundle will be used.
*
* @return
* The entity id.
*/
protected function createEntity($values, $langcode, $bundle_name = NULL) {
$entity_values = $values;
$entity_values['langcode'] = $langcode;
$info = entity_get_info($this->entityType);
if (!empty($info['entity_keys']['bundle'])) {
$entity_values[$info['entity_keys']['bundle']] = $bundle_name ?: $this->bundle;
}
$controller = $this->container->get('plugin.manager.entity')->getStorageController($this->entityType);
if (!($controller instanceof DatabaseStorageControllerNG)) {
foreach ($values as $property => $value) {
if (is_array($value)) {
$entity_values[$property] = array($langcode => $value);
}
}
}
$entity = entity_create($this->entityType, $entity_values);
$entity->save();
return $entity->id();
}
}

View File

@ -7,45 +7,14 @@
namespace Drupal\translation_entity\Tests;
use Drupal\Core\Entity\DatabaseStorageControllerNG;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityNG;
use Drupal\Core\Language\Language;
use Drupal\Core\TypedData\ComplexDataInterface;
use Drupal\simpletest\WebTestBase;
/**
* Tests the Entity Translation UI.
*/
abstract class EntityTranslationUITest extends WebTestBase {
/**
* The enabled languages.
*
* @var array
*/
protected $langcodes;
/**
* The entity type being tested.
*
* @var string
*/
protected $entityType;
/**
* The bundle being tested.
*
* @var string
*/
protected $bundle;
/**
* The name of the field used to test translation.
*
* @var string
*/
protected $fieldName;
abstract class EntityTranslationUITest extends EntityTranslationTestBase {
/**
* Whether the behavior of the language selector should be tested.
@ -54,92 +23,6 @@ abstract class EntityTranslationUITest extends WebTestBase {
*/
protected $testLanguageSelector = TRUE;
/**
* Overrides \Drupal\simpletest\WebTestBase::setUp().
*/
function setUp() {
parent::setUp();
$this->setupLanguages();
$this->setupBundle();
$this->enableTranslation();
$this->setupTranslator();
$this->setupTestFields();
}
/**
* Enables additional languages.
*/
protected function setupLanguages() {
$this->langcodes = array('it', 'fr');
foreach ($this->langcodes as $langcode) {
language_save(new Language(array('langcode' => $langcode)));
}
array_unshift($this->langcodes, language_default()->langcode);
}
/**
* Creates or initializes the bundle date if needed.
*/
protected function setupBundle() {
if (empty($this->bundle)) {
$this->bundle = $this->entityType;
}
}
/**
* Enables translation for the current entity type and bundle.
*/
protected function enableTranslation() {
// Enable translation for the current entity type and ensure the change is
// picked up.
translation_entity_set_config($this->entityType, $this->bundle, 'enabled', TRUE);
drupal_static_reset();
entity_info_cache_clear();
menu_router_rebuild();
}
/**
* Returns an array of permissions needed for the translator.
*/
abstract function getTranslatorPermissions();
/**
* Creates and activates a translator user.
*/
protected function setupTranslator() {
$translator = $this->drupalCreateUser($this->getTranslatorPermissions());
$this->drupalLogin($translator);
}
/**
* Creates the test fields.
*/
protected function setupTestFields() {
$this->fieldName = 'field_test_et_ui_test';
$field = array(
'field_name' => $this->fieldName,
'type' => 'text',
'cardinality' => 1,
'translatable' => TRUE,
);
field_create_field($field);
$instance = array(
'entity_type' => $this->entityType,
'field_name' => $this->fieldName,
'bundle' => $this->bundle,
'label' => 'Test translatable text-field',
'widget' => array(
'type' => 'text_textfield',
'weight' => 0,
),
);
field_create_instance($instance);
}
/**
* Tests the basic translation UI.
*/
@ -163,8 +46,7 @@ abstract class EntityTranslationUITest extends WebTestBase {
$langcode = 'it';
$values[$langcode] = $this->getNewEntityValues($langcode);
$controller = translation_entity_controller($this->entityType);
$base_path = $controller->getBasePath($entity);
$base_path = $this->controller->getBasePath($entity);
$path = $langcode . '/' . $base_path . '/translations/add/' . $default_langcode . '/' . $langcode;
$this->drupalPost($path, $this->getEditValues($values, $langcode), t('Save'));
if ($this->testLanguageSelector) {
@ -200,7 +82,7 @@ abstract class EntityTranslationUITest extends WebTestBase {
// Check that every translation has the correct "outdated" status.
foreach ($this->langcodes as $enabled_langcode) {
$prefix = $enabled_langcode != $default_langcode ? $enabled_langcode . '/' : '';
$path = $prefix . $controller->getEditPath($entity);
$path = $prefix . $this->controller->getEditPath($entity);
$this->drupalGet($path);
if ($enabled_langcode == $langcode) {
$this->assertFieldByXPath('//input[@name="translation[retranslate]"]', FALSE, 'The retranslate flag is not checked by default.');
@ -226,40 +108,6 @@ abstract class EntityTranslationUITest extends WebTestBase {
}
}
/**
* Creates the entity to be translated.
*
* @param array $values
* An array of initial values for the entity.
* @param string $langcode
* The initial language code of the entity.
* @param string $bundle_name
* (optional) The entity bundle, if the entity uses bundles. Defaults to
* NULL. If left NULL, $this->bundle will be used.
*
* @return
* The entity id.
*/
protected function createEntity($values, $langcode, $bundle_name = NULL) {
$entity_values = $values;
$entity_values['langcode'] = $langcode;
$info = entity_get_info($this->entityType);
if (!empty($info['entity_keys']['bundle'])) {
$entity_values[$info['entity_keys']['bundle']] = $bundle_name ?: $this->bundle;
}
$controller = $this->container->get('plugin.manager.entity')->getStorageController($this->entityType);
if (!($controller instanceof DatabaseStorageControllerNG)) {
foreach ($values as $property => $value) {
if (is_array($value)) {
$entity_values[$property] = array($langcode => $value);
}
}
}
$entity = entity_create($this->entityType, $entity_values);
$entity->save();
return $entity->id();
}
/**
* Returns an array of entity field values to be tested.
*/

View File

@ -0,0 +1,185 @@
<?php
/**
* @file
* Contains \Drupal\entity\Tests\EntityTranslationWorkflowsTest.
*/
namespace Drupal\translation_entity\Tests;
use Drupal\user\Plugin\Core\Entity\User;
/**
* Tests entity translation workflows.
*/
class EntityTranslationWorkflowsTest extends EntityTranslationTestBase {
/**
* The entity used for testing.
*
* @var \Drupal\Core\Entity\EntityInterface
*/
protected $entity;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('language', 'translation_entity', 'entity_test');
public static function getInfo() {
return array(
'name' => 'Entity Test translation workflows',
'description' => 'Tests the entity translation workflows for the test entity.',
'group' => 'Entity Translation UI',
);
}
function setUp() {
parent::setUp();
$this->setupEntity();
}
/**
* Overrides \Drupal\translation_entity\Tests\EntityTranslationTestBase::getEditorPermissions().
*/
protected function getEditorPermissions() {
return array('administer entity_test content');
}
/**
* Creates a test entity and translate it.
*/
protected function setupEntity() {
$default_langcode = $this->langcodes[0];
// Create a test entity.
$values = array(
'name' => $this->randomName(),
'user_id' => mt_rand(1, 128),
$this->fieldName => array(array('value' => $this->randomName(16))),
);
$id = $this->createEntity($values, $default_langcode);
$this->entity = entity_load($this->entityType, $id, TRUE);
// Create a translation.
$this->drupalLogin($this->translator);
$add_translation_path = $this->controller->getBasePath($this->entity) . "/translations/add/$default_langcode/{$this->langcodes[2]}";
$this->drupalPost($add_translation_path, array(), t('Save'));
}
/**
* Test simple and editorial translation workflows.
*/
function testWorkflows() {
// Test workflows for the editor.
$expected_status = array('edit' => 200, 'overview' => 403, 'add_translation' => 403, 'edit_translation' => 403);
$this->assertWorkflows($this->editor, $expected_status);
// Test workflows for the translator.
$expected_status = array('edit' => 403, 'overview' => 200, 'add_translation' => 200, 'edit_translation' => 200);
$this->assertWorkflows($this->translator, $expected_status);
// Test workflows for the admin.
$expected_status = array('edit' => 200, 'overview' => 200, 'add_translation' => 200, 'edit_translation' => 200);
$this->assertWorkflows($this->administrator, $expected_status);
// Check that translation permissions governate the associated operations.
$ops = array('create' => t('add'), 'update' => t('edit'), 'delete' => t('delete'));
$translations_path = $this->controller->getBasePath($this->entity) . "/translations";
foreach ($ops as $current_op => $label) {
$user = $this->drupalCreateUser(array($this->getTranslatePermission(), "$current_op entity translations"));
$this->drupalLogin($user);
$this->drupalGet($translations_path);
foreach ($ops as $op => $label) {
if ($op != $current_op) {
$this->assertNoLink($label, format_string('No %op link found.', array('%op' => $label)));
}
else {
$this->assertLink($label, 0, format_string('%op link found.', array('%op' => $label)));
}
}
}
}
/**
* Checks that workflows have the expected behaviors for the given user.
*
* @param \Drupal\user\Plugin\Core\Entity\User $user
* The user to test the workflow behavior against.
* @param array $expected_status
* The an associative array with the operation name as key and the expected
* status as value.
*/
protected function assertWorkflows(User $user, $expected_status) {
$default_langcode = $this->langcodes[0];
$languages = language_list();
$args = array('@user_label' => $user->name);
$this->drupalLogin($user);
// Check whether the user is allowed to access the entity form in edit mode.
$edit_path = $this->controller->getEditPath($this->entity);
$options = array('language' => $languages[$default_langcode]);
$this->drupalGet($edit_path, $options);
$this->assertResponse($expected_status['edit'], format_string('The @user_label has the expected edit access.', $args));
// Check whether the user is allowed to access the translation overview.
$langcode = $this->langcodes[1];
$translations_path = $this->controller->getBasePath($this->entity) . "/translations";
$options = array('language' => $languages[$langcode]);
$this->drupalGet($translations_path, $options);
$this->assertResponse($expected_status['overview'], format_string('The @user_label has the expected translation overview access.', $args));
// Check whether the user is allowed to create a translation.
$add_translation_path = $translations_path . "/add/$default_langcode/$langcode";
if ($expected_status['add_translation'] == 200) {
$this->clickLink('add');
$this->assertUrl($add_translation_path, $options, 'The translation overview points to the translation form when creating translations.');
// Check that the translation form does not contain shared elements for
// translators.
if ($expected_status['edit'] == 403) {
$this->assertNoSharedElements();
}
}
else {
$this->drupalGet($add_translation_path, $options);
}
$this->assertResponse($expected_status['add_translation'], format_string('The @user_label has the expected translation creation access.', $args));
// Check whether the user is allowed to edit a translation.
$langcode = $this->langcodes[2];
$edit_translation_path = $translations_path . "/edit/$langcode";
$options = array('language' => $languages[$langcode]);
if ($expected_status['edit_translation'] == 200) {
$this->drupalGet($translations_path, $options);
$editor = $expected_status['edit'] == 200;
$this->clickLink('edit', intval($editor));
if ($editor) {
// An editor should be pointed to the entity form in multilingual mode.
$this->assertUrl($edit_path, $options, 'The translation overview points to the edit form for editors when editing translations.');
}
else {
// While a translator should be pointed to the translation form.
$this->assertUrl($edit_translation_path, $options, 'The translation overview points to the translation form for translators when editing translations.');
// Check that the translation form does not contain shared elements.
$this->assertNoSharedElements();
}
}
else {
$this->drupalGet($edit_translation_path, $options);
}
$this->assertResponse($expected_status['edit_translation'], format_string('The @user_label has the expected translation creation access.', $args));
}
/**
* Assert that the current page does not contain shared form elements.
*/
protected function assertNoSharedElements() {
$language_none = LANGUAGE_NOT_SPECIFIED;
return $this->assertNoFieldByXPath("//input[@name='field_test_text[$language_none][0][value]']", NULL, 'Shared elements are not available on the translation form.');
}
}

View File

@ -8,7 +8,7 @@
namespace Drupal\translation_entity\Tests\Views;
use Drupal\views\Tests\ViewTestBase;
use Drupal\translation_entity\Tests\EntityTranslationUITest;
use Drupal\translation_entity\Tests\EntityTranslationTestBase;
use Drupal\views\Tests\ViewTestData;
/**
@ -16,7 +16,7 @@ use Drupal\views\Tests\ViewTestData;
*
* @see \Drupal\translation_entity\Plugin\views\field\TranslationLink
*/
class TranslationLinkTest extends EntityTranslationUITest {
class TranslationLinkTest extends EntityTranslationTestBase {
/**
* Views used by this test.
@ -49,13 +49,6 @@ class TranslationLinkTest extends EntityTranslationUITest {
ViewTestData::importTestViews(get_class($this), array('translation_entity_test_views'));
}
/**
* Implements \Drupal\translation_entity\Tests\EntityTranslationUITest::getTranslatorPermission().
*/
function getTranslatorPermissions() {
return array("translate $this->entityType entities", 'edit original values');
}
/**
* Tests the Entity translation overview link field handler.
*/
@ -65,12 +58,4 @@ class TranslationLinkTest extends EntityTranslationUITest {
$this->assertNoLinkByHref('user/2/translations', 'The translations link is not present when translation_entity_translate_access() is FALSE.');
}
/**
* Overrides \Drupal\translation_entity\Tests\EntityTranslationUITest::testTranslationUI().
*/
public function testTranslationUI() {
// @todo \Drupal\translation_entity\Tests\EntityTranslationUITest contains
// essential helper methods that should be seprarated from test methods.
}
}

View File

@ -155,18 +155,37 @@ function translation_entity_menu() {
'weight' => 2,
) + $item;
$items["$path/translations/overview"] = array(
'title' => 'Overview',
'type' => MENU_DEFAULT_LOCAL_TASK,
'weight' => 0,
);
// Add translation callback.
// @todo Add the access callback instead of replacing it as soon as the
// routing system supports multiple callbacks.
$add_path = "$path/translations/add/%language/%language";
$language_position = $entity_position + 3;
$args = array($entity_position, $language_position, $language_position + 1);
$items[$add_path] = array(
$items["$path/translations/add/%language/%language"] = array(
'title' => 'Add',
'page callback' => 'translation_entity_add_page',
'page arguments' => $args,
'access callback' => 'translation_entity_add_access',
'access arguments' => $args,
'type' => MENU_LOCAL_TASK,
'weight' => 1,
) + $item;
// Edit translation callback.
$args = array($entity_position, $language_position);
$items["$path/translations/edit/%language"] = array(
'title' => 'Edit',
'page callback' => 'translation_entity_edit_page',
'page arguments' => $args,
'access callback' => 'translation_entity_edit_access',
'access arguments' => $args,
'type' => MENU_LOCAL_TASK,
'weight' => 1,
) + $item;
// Delete translation callback.
@ -174,6 +193,8 @@ function translation_entity_menu() {
'title' => 'Delete',
'page callback' => 'drupal_get_form',
'page arguments' => array('translation_entity_delete_confirm', $entity_position, $language_position),
'access callback' => 'translation_entity_delete_access',
'access arguments' => $args,
) + $item;
}
}
@ -257,7 +278,10 @@ function _translation_entity_menu_strip_loaders($path) {
*/
function translation_entity_translate_access(EntityInterface $entity) {
$entity_type = $entity->entityType();
return empty($entity->language()->locked) && language_multilingual() && translation_entity_enabled($entity_type, $entity->bundle()) && (user_access('translate any entity') || user_access("translate $entity_type entities"));
return empty($entity->language()->locked) &&
language_multilingual() &&
translation_entity_enabled($entity_type, $entity->bundle()) &&
(user_access('create entity translations') || user_access('update entity translations') || user_access('delete entity translations'));
}
/**
@ -266,16 +290,50 @@ function translation_entity_translate_access(EntityInterface $entity) {
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity being translated.
* @param \Drupal\Core\Language\Language $source
* The language of the values being translated.
* (optional) The language of the values being translated. Defaults to the
* entity language.
* @param \Drupal\Core\Language\Language $target
* The language of the translated values.
* (optional) The language of the translated values. Defaults to the current
* content language.
*/
function translation_entity_add_access(EntityInterface $entity, Language $source = NULL, Language $target = NULL) {
$source = !empty($source) ? $source : $entity->language();
$target = !empty($target) ? $target : language(LANGUAGE_TYPE_CONTENT);
$translations = $entity->getTranslationLanguages();
$languages = language_list();
return $source->langcode != $target->langcode && isset($languages[$source->langcode]) && isset($languages[$target->langcode]) && !isset($translations[$target->langcode]) && translation_entity_access($entity, $target->langcode);
return $source->langcode != $target->langcode && isset($languages[$source->langcode]) && isset($languages[$target->langcode]) && !isset($translations[$target->langcode]) && translation_entity_access($entity, 'create');
}
/**
* Access callback for the translation edit page.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity being translated.
* @param \Drupal\Core\Language\Language $language
* (optional) The language of the translated values. Defaults to the current
* content language.
*/
function translation_entity_edit_access(EntityInterface $entity, Language $language = NULL) {
$language = !empty($language) ? $language : language(LANGUAGE_TYPE_CONTENT);
$translations = $entity->getTranslationLanguages();
$languages = language_list();
return isset($languages[$language->langcode]) && $language->langcode != $entity->language()->langcode && isset($translations[$language->langcode]) && translation_entity_access($entity, 'update');
}
/**
* Access callback for the translation delete page.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity being translated.
* @param \Drupal\Core\Language\Language $language
* (optional) The language of the translated values. Defaults to the current
* content language.
*/
function translation_entity_delete_access(EntityInterface $entity, Language $language = NULL) {
$language = !empty($language) ? $language : language(LANGUAGE_TYPE_CONTENT);
$translations = $entity->getTranslationLanguages();
$languages = language_list();
return isset($languages[$language->langcode]) && $language->langcode != $entity->language()->langcode && isset($translations[$language->langcode]) && translation_entity_access($entity, 'delete');
}
/**
@ -454,14 +512,18 @@ function translation_entity_form_controller(array $form_state) {
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity to be accessed.
* @param string $langcode
* The language of the translation to be accessed.
* @param $op
* The operation to be performed on the translation. Possible values are:
* - "view"
* - "update"
* - "delete"
* - "create"
*
* @return
* TRUE if the current user is allowed to view the translation.
*/
function translation_entity_access(EntityInterface $entity, $langcode) {
return translation_entity_controller($entity->entityType())->getTranslationAccess($entity, $langcode) ;
function translation_entity_access(EntityInterface $entity, $op) {
return translation_entity_controller($entity->entityType())->getTranslationAccess($entity, $op) ;
}
/**
@ -469,29 +531,52 @@ function translation_entity_access(EntityInterface $entity, $langcode) {
*/
function translation_entity_permission() {
$permission = array(
'edit original values' => array(
'title' => t('Edit original values'),
'description' => t('Access the entity form in the original language.'),
),
'administer entity translation' => array(
'title' => t('Administer entity translation'),
'title' => t('Administer translation settings'),
'description' => t('Configure translatability of entities and fields.'),
),
'create entity translations' => array(
'title' => t('Create translations'),
),
'update entity translations' => array(
'title' => t('Edit translations'),
),
'delete entity translations' => array(
'title' => t('Delete translations'),
),
'translate any entity' => array(
'title' => t('Translate any entity'),
'description' => t('Translate field content for any fieldable entity.'),
),
);
// Create a translate permission for each enabled entity type and (optionally)
// bundle.
foreach (entity_get_info() as $entity_type => $info) {
if (translation_entity_enabled($entity_type)) {
$label = !empty($info['label']) ? t($info['label']) : $entity_type;
$permission["translate $entity_type entities"] = array(
'title' => t('Translate entities of type @type', array('@type' => $label)),
'description' => t('Translate field content for entities of type @type.', array('@type' => $label)),
if (!empty($info['permission_granularity'])) {
$t_args = array('@entity_label' => drupal_strtolower(t($info['label'])));
switch ($info['permission_granularity']) {
case 'bundle':
foreach (entity_get_bundles($entity_type) as $bundle => $bundle_info) {
if (translation_entity_enabled($entity_type, $bundle)) {
$t_args['%bundle_label'] = isset($info['bundles'][$bundle]['label']) ? $info['bundles'][$bundle]['label'] : $bundle;
$permission["translate $bundle $entity_type"] = array(
'title' => t('Translate %bundle_label @entity_label', $t_args),
);
}
}
break;
case 'entity_type':
if (translation_entity_enabled($entity_type)) {
$permission["translate $entity_type"] = array(
'title' => t('Translate @entity_label', $t_args),
);
}
break;
}
}
}
return $permission;
}

View File

@ -51,11 +51,13 @@ function translation_entity_overview(EntityInterface $entity) {
$language_name = $language->name;
$langcode = $language->langcode;
$add_path = $base_path . '/translations/add/' . $original . '/' . $langcode;
$translate_path = $base_path . '/translations/edit/' . $langcode;
$delete_path = $base_path . '/translations/delete/' . $langcode;
if ($base_path) {
$add_links = _translation_entity_get_switch_links($add_path);
$edit_links = _translation_entity_get_switch_links($edit_path);
$translate_links = _translation_entity_get_switch_links($translate_path);
$delete_links = _translation_entity_get_switch_links($delete_path);
}
@ -80,8 +82,17 @@ function translation_entity_overview(EntityInterface $entity) {
$row_title = $is_original ? $label : t('n/a');
}
if ($edit_path && $controller->getAccess($entity, 'update') && $controller->getTranslationAccess($entity, $langcode)) {
// If the user is allowed to edit the entity we point the edit link to
// the entity form, otherwise if we are not dealing with the original
// language we point the link to the translation form.
if ($edit_path && $controller->getAccess($entity, 'update')) {
$links['edit'] = isset($edit_links->links[$langcode]['href']) ? $edit_links->links[$langcode] : array('href' => $edit_path, 'language' => $language);
}
elseif (!$is_original && $controller->getTranslationAccess($entity, 'update')) {
$links['edit'] = isset($translate_links->links[$langcode]['href']) ? $translate_links->links[$langcode] : array('href' => $translate_path, 'language' => $language);
}
if (isset($links['edit'])) {
$links['edit']['title'] = t('edit');
}
@ -98,16 +109,18 @@ function translation_entity_overview(EntityInterface $entity) {
}
else {
$source_name = isset($languages[$source]) ? $languages[$source]->name : t('n/a');
if ($controller->getTranslationAccess($entity, 'delete')) {
$links['delete'] = isset($delete_links->links[$langcode]['href']) ? $delete_links->links[$langcode] : array('href' => $delete_links, 'language' => $language);
$links['delete']['title'] = t('delete');
}
}
}
else {
// No such translation in the set yet: help user to create it.
$row_title = $source_name = t('n/a');
$source = $entity->language()->langcode;
if ($source != $langcode && $controller->getAccess($entity, 'update')) {
if ($source != $langcode && $controller->getTranslationAccess($entity, 'create')) {
if ($translatable) {
$links['add'] = isset($add_links->links[$langcode]['href']) ? $add_links->links[$langcode] : array('href' => $add_path, 'language' => $language);
$links['add']['title'] = t('add');
@ -188,6 +201,30 @@ function translation_entity_add_page(EntityInterface $entity, Language $source =
$form_state = entity_form_state_defaults($entity, $operation, $target->langcode);
$form_state['translation_entity']['source'] = $source;
$form_state['translation_entity']['target'] = $target;
$controller = translation_entity_controller($entity->entityType());
$form_state['translation_entity']['translation_form'] = !$controller->getAccess($entity, 'update');
$form_id = entity_form_id($entity);
return drupal_build_form($form_id, $form_state);
}
/**
* Page callback for the translation edit page.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity being translated.
* @param \Drupal\Core\Language\Language $language
* (optional) The language of the translated values. Defaults to the current
* content language.
*
* @return array
* A processed form array ready to be rendered.
*/
function translation_entity_edit_page(EntityInterface $entity, Language $language = NULL) {
$language = !empty($language) ? $language : language(LANGUAGE_TYPE_CONTENT);
$info = $entity->entityInfo();
$operation = isset($info['default_operation']) ? $info['default_operation'] : 'default';
$form_state = entity_form_state_defaults($entity, $operation, $language->langcode);
$form_state['translation_entity']['translation_form'] = TRUE;
$form_id = entity_form_id($entity);
return drupal_build_form($form_id, $form_state);
}
@ -256,7 +293,8 @@ function translation_entity_delete_confirm_submit(array $form, array &$form_stat
// Remove any existing path alias for the removed translation.
if (module_exists('path')) {
path_delete(array('source' => $controller->getViewPath($entity), 'langcode' => $language->langcode));
$conditions = array('source' => $controller->getViewPath($entity), 'langcode' => $language->langcode);
drupal_container()->get('path.crud')->delete($conditions);
}
$form_state['redirect'] = $controller->getBasePath($entity) . '/translations';

View File

@ -34,9 +34,6 @@ class UserTranslationUITest extends EntityTranslationUITest {
);
}
/**
* Overrides \Drupal\simpletest\WebTestBase::setUp().
*/
function setUp() {
$this->entityType = 'user';
$this->testLanguageSelector = FALSE;
@ -48,7 +45,7 @@ class UserTranslationUITest extends EntityTranslationUITest {
* Overrides \Drupal\translation_entity\Tests\EntityTranslationUITest::getTranslatorPermission().
*/
function getTranslatorPermissions() {
return array('administer users', "translate $this->entityType entities", 'edit original values');
return array_merge(parent::getTranslatorPermissions(), array('administer users'));
}
/**
@ -63,7 +60,7 @@ class UserTranslationUITest extends EntityTranslationUITest {
* Tests translate link on user admin list.
*/
function testTranslateLinkUserAdminPage() {
$this->admin_user = $this->drupalCreateUser(array('access administration pages', 'administer users', 'translate any entity'));
$this->admin_user = $this->drupalCreateUser(array_merge(parent::getTranslatorPermissions(), array('access administration pages', 'administer users')));
$this->drupalLogin($this->admin_user);
$uid = $this->createEntity(array('name' => $this->randomName()), $this->langcodes[0]);