Commit cf860477 authored by webchick's avatar webchick

Issue #1687864 by sun, BarisW, mgifford, sebsebseb123: Bring theme_breadcrumb() up to WCAG 2.0 AA.

parent 4cc6afff
......@@ -1830,7 +1830,7 @@ function theme_breadcrumb($variables) {
// Provide a navigational heading to give context for breadcrumb links to
// screen-reader users. Make the heading invisible with .element-invisible.
$output .= '<h2 class="element-invisible">' . t('You are here') . '</h2>';
$output .= '<ol><li>' . implode(' » </li><li>', $breadcrumb) . '</li></ol>';
$output .= '<ol><li>' . implode('</li><li>', $breadcrumb) . '</li></ol>';
$output .= '</nav>';
}
return $output;
......
......@@ -44,6 +44,8 @@
* multiple entity forms when the forms are similar. Defaults to
* Drupal\Core\Entity\EntityFormController.
* - label: The human-readable name of the type.
* - bundle_label: The human-readable name of the entity bundles, e.g.
* Vocabulary.
* - label_callback: (optional) A function taking an entity and optional
* langcode argument, and returning the label of the entity. If langcode is
* omitted, the entity's default language is used.
......
......@@ -18,6 +18,7 @@
* @Plugin(
* id = "comment",
* label = @Translation("Comment"),
* bundle_label = @Translation("Content type"),
* module = "comment",
* controller_class = "Drupal\comment\CommentStorageController",
* render_controller_class = "Drupal\comment\CommentRenderController",
......
......@@ -18,6 +18,7 @@
* @Plugin(
* id = "node",
* label = @Translation("Node"),
* bundle_label = @Translation("Content type"),
* module = "node",
* controller_class = "Drupal\node\NodeStorageController",
* render_controller_class = "Drupal\node\NodeRenderController",
......
......@@ -305,6 +305,13 @@ ul.inline li {
margin: 0;
padding: 0;
}
/* IE8 does not support :not() and :last-child. */
.breadcrumb li:before {
content: ' » ';
}
.breadcrumb li:first-child:before {
content: none;
}
/**
* Markup generated by theme_menu_local_tasks().
......
......@@ -18,6 +18,7 @@
* @Plugin(
* id = "taxonomy_term",
* label = @Translation("Taxonomy term"),
* bundle_label = @Translation("Vocabulary"),
* module = "taxonomy",
* controller_class = "Drupal\taxonomy\TermStorageController",
* render_controller_class = "Drupal\taxonomy\TermRenderController",
......
<?php
/**
* @file
* Definition of Drupal\entity\Tests\EntityTranslationSettingsTest.
*/
namespace Drupal\translation_entity\Tests;
use Drupal\simpletest\WebTestBase;
/**
* Tests the Entity Test Translation UI.
*/
class EntityTranslationSettingsTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('language', 'translation_entity', 'comment');
public static function getInfo() {
return array(
'name' => 'Entity Translation settings',
'description' => 'Tests the entity translation settings UI.',
'group' => 'Entity Translation UI',
);
}
/**
* Overrides \Drupal\simpletest\WebTestBase::setUp().
*/
function setUp() {
parent::setUp();
// Setup two content types to test instances shared among different bundles.
$this->drupalCreateContentType(array('type' => 'article'));
$this->drupalCreateContentType(array('type' => 'page'));
$admin_user = $this->drupalCreateUser(array('administer entity translation'));
$this->drupalLogin($admin_user);
}
/**
* Tests that the settings UI works as expected.
*/
function testSettingsUI() {
// Test that by marking only an entity type and no bundle as translatable a
// form error is raised and the settings are not saved.
$edit = array('entity_types[comment]' => TRUE);
$this->assertSettings('comment', NULL, FALSE, $edit);
$this->assertTrue($this->xpath('//div[@id="messages"]//div[contains(@class, "error")]'), 'Enabling translation only for entity types generates a form error.');
// Test that by marking only a bundle and not the related entity type as
// translatable the settings are ignored.
$edit = array('settings[comment][comment_node_article][translatable]' => TRUE);
$this->assertSettings('comment', NULL, FALSE, $edit);
// Test that by marking only a field as translatable and not the related
// entity type and bundle the settings are ignored.
$edit = array('settings[comment][comment_node_article][fields][comment_body]' => TRUE);
$this->assertSettings('comment', NULL, FALSE, $edit);
// Test that by marking entity type and bundle as translatable the settings
// are stored.
$edit = array(
'entity_types[comment]' => TRUE,
'settings[comment][comment_node_article][translatable]' => TRUE,
);
$this->assertSettings('comment', 'comment_node_article', TRUE, $edit);
// Test that a field shared among different bundles can be enabled without
// needing to make all the related bundles translatable.
$edit = array(
'settings[comment][comment_node_article][settings][language][langcode]' => 'current_interface',
'settings[comment][comment_node_article][settings][language][language_hidden]' => FALSE,
'settings[comment][comment_node_article][fields][comment_body]' => TRUE,
);
$this->assertSettings('comment', 'comment_node_article', TRUE, $edit);
$field = field_info_field('comment_body');
$this->assertTrue($field['translatable'], 'Comment body is translatable.');
// Test that language settings are correctly stored.
$language_configuration = language_get_default_configuration('comment', 'comment_node_article');
$this->assertEqual($language_configuration['langcode'], 'current_interface', 'The default language for article comments is set to the currrent interface language.');
$this->assertFalse($language_configuration['language_hidden'], 'The language selector for article comments is shown.');
}
/**
* Asserts that translatability has the expected value for the given bundle.
*/
protected function assertSettings($entity_type, $bundle, $enabled, $edit) {
$this->drupalPost('admin/config/regional/translation_entity', $edit, t('Save settings'));
$args = array('@entity_type' => $entity_type, '@bundle' => $bundle, '@enabled' => $enabled ? 'enabled' : 'disabled');
$message = format_string('Translation for entity @entity_type (@bundle) is @enabled.', $args);
drupal_static_reset();
return $this->assertEqual(translation_entity_enabled($entity_type, $bundle), $enabled, $message);
}
}
/**
* @file
* Styles for the administration page.
*/
#translation-entity-admin-settings-form table .bundle {
width: 25%;
}
#translation-entity-admin-settings-form table .field {
width: 25%;
}
#translation-entity-admin-settings-form table .translatable {
width: 10%;
}
#translation-entity-admin-settings-form table .operations {
width: 40%;
}
......@@ -7,6 +7,199 @@
use Drupal\Core\Entity\EntityInterface;
/**
* Administration settings page callback.
*/
function translation_entity_admin_page() {
return drupal_get_form('translation_entity_admin_settings_form', translation_entity_supported());
}
/**
* Form builder for the administration settings form.
*/
function translation_entity_admin_settings_form(array $form, array $form_state, array $supported) {
$entity_info = entity_get_info();
$labels = array();
$default = array();
foreach ($supported as $entity_type) {
$labels[$entity_type] = isset($entity_info[$entity_type]['label']) ? $entity_info[$entity_type]['label'] : $entity_type;
$default[$entity_type] = translation_entity_enabled($entity_type) ? $entity_type : FALSE;
}
$path = drupal_get_path('module', 'translation_entity');
$form = array(
'#attached' => array(
'css' => array($path . '/translation_entity.admin.css'),
'js' => array($path . '/translation_entity.admin.js'),
),
);
$translatable = t('Translatable');
$form['entity_types'] = array(
'#title' => $translatable,
'#type' => 'checkboxes',
'#options' => $labels,
'#default_value' => $default,
);
$form['settings'] = array('#tree' => TRUE);
foreach ($supported as $entity_type) {
$info = $entity_info[$entity_type];
$form['settings'][$entity_type] = array(
'#title' => $labels[$entity_type],
'#type' => 'item',
'#theme' => 'translation_entity_admin_settings_table',
'#bundle_label' => isset($info['bundle_label']) ? $info['bundle_label'] : $labels[$entity_type],
'#states' => array(
'visible' => array(
':input[name="entity_types[' . $entity_type . ']"]' => array('checked' => TRUE),
),
),
);
foreach (entity_get_bundles($entity_type) as $bundle) {
$form['settings'][$entity_type][$bundle]['translatable'] = array(
'#label' => isset($info['bundles'][$bundle]) ? $info['bundles'][$bundle]['label'] : $labels[$entity_type],
'#type' => 'checkbox',
'#default_value' => translation_entity_enabled($entity_type, $bundle),
);
$language_configuration = language_get_default_configuration($entity_type, $bundle);
$form['settings'][$entity_type][$bundle]['settings'] = array(
'#type' => 'item',
'#states' => array(
'visible' => array(
':input[name="settings[' . $entity_type . '][' . $bundle . '][translatable]"]' => array('checked' => TRUE),
),
),
'language' => array(
'#type' => 'language_configuration',
'#entity_information' => array(
'entity_type' => $entity_type,
'bundle' => $bundle,
),
'#default_value' => $language_configuration,
// Here we do not want the widget to be altered and hold also the
// "Enable translation" checkbox, which would be redundant. Hence we
// add this key to be able to skip alterations.
'#translation_entity_skip_alter' => TRUE,
),
);
// @todo Exploit field definitions once all core entities and field types
// are migrated to the Entity Field API.
foreach (field_info_instances($entity_type, $bundle) as $field_name => $instance) {
$field = field_info_field($field_name);
$form['settings'][$entity_type][$bundle]['fields'][$field_name] = array(
'#label' => $instance['label'],
'#type' => 'checkbox',
'#default_value' => $field['translatable'],
);
}
}
}
$form['actions'] = array('#type' => 'actions');
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => t('Save settings'),
);
return $form;
}
/**
* Form validation handler for translation_entity_admin_settings_form().
*/
function translation_entity_admin_settings_form_validate(array $form, array &$form_state) {
$settings = &$form_state['values']['settings'];
foreach (array_filter($form_state['values']['entity_types']) as $entity_type) {
$translatable = array_filter($settings[$entity_type], function($value) { return !empty($value['translatable']); });
if (empty($translatable)) {
$t_args = array(
'%bundle' => $form['settings'][$entity_type]['#bundle_label'],
'%entity_type' => $form['settings'][$entity_type]['#title'],
);
form_set_error('entity_types][' . $entity_type, t('At least one %bundle needs to be translatable to enable %entity_type translation.', $t_args));
}
}
}
/**
* Form submission handler for translation_entity_admin_settings_form().
*/
function translation_entity_admin_settings_form_submit(array $form, array &$form_state) {
$entity_types = $form_state['values']['entity_types'];
$settings = &$form_state['values']['settings'];
// If an entity type is not translatable all its bundles and fields must be
// marked as not translatable. Similarly, if a bundle is made non translatable
// all of its fields will be non translatable.
foreach ($settings as $entity_type => &$entity_settings) {
foreach ($entity_settings as $bundle => &$bundle_settings) {
$bundle_settings['translatable'] = $bundle_settings['translatable'] && $entity_types[$entity_type];
if (!empty($bundle_settings['fields'])) {
foreach ($bundle_settings['fields'] as $field_name => $translatable) {
$bundle_settings['fields'][$field_name] = $translatable && $bundle_settings['translatable'];
}
}
}
}
translation_entity_save_settings($settings);
drupal_set_message(t('Settings successfully updated.'));
}
/**
* Theming function for an administration settings table.
*/
function theme_translation_entity_admin_settings_table(array $variables) {
$element = $variables['element'];
$header = array(
array('data' => $element['#bundle_label'], 'class' => array('bundle')),
array('data' => t('Field'), 'class' => array('field')),
array('data' => t('Translatable'), 'class' => array('translatable')),
array('data' => t('Operations'), 'class' => array('operations')),
);
$rows = array();
foreach (element_children($element) as $bundle) {
$field_names = !empty($element[$bundle]['fields']) ? element_children($element[$bundle]['fields']) : array();
$rows[] = array(
'data' => array(
array('data' => check_plain($element[$bundle]['translatable']['#label']), 'class' => array('bundle')),
array('data' => '', 'class' => array('field')),
array('data' => drupal_render($element[$bundle]['translatable']), 'class' => array('translatable')),
array('data' => drupal_render($element[$bundle]['settings']), 'class' => array('operations')),
),
'class' => array('bundle-settings'),
);
foreach ($field_names as $field_name) {
$field = &$element[$bundle]['fields'][$field_name];
$rows[] = array(
'data' => array(
array('data' => '', 'class' => array('bundle')),
array('data' => check_plain($field['#label']), 'class' => array('field')),
array('data' => drupal_render($field), 'class' => array('translatable')),
array('data' => '', 'class' => array('operations')),
),
'class' => array('field-settings'),
);
}
}
$output = theme('table', array('header' => $header, 'rows' => $rows));
$output .= drupal_render_children($element);
return $output;
}
/**
* Form constructor for the confirmation of translatability switching.
*/
......
(function ($) {
"use strict";
/**
* Makes field translatability inherit bundle translatability.
*/
Drupal.behaviors.translationEntity = {
attach: function (context) {
// Initially hide all field rows for non translatable bundles.
$('table .bundle-settings .translatable :input:not(:checked)').once('translation-entity-admin-hide', function() {
$(this).closest('.bundle-settings').nextUntil('.bundle-settings').hide();
});
// When a bundle is made translatable all of its field instances should
// inherit this setting. Instead when it is made non translatable its field
// instances are hidden, since their translatability no longer matters.
$('table .bundle-settings .translatable :input').once('translation-entity-admin-bind', function() {
var $bundleTranslatable = $(this).click(function() {
var $bundleSettings = $bundleTranslatable.closest('.bundle-settings');
var $fieldSettings = $bundleSettings.nextUntil('.bundle-settings');
if ($bundleTranslatable.is(':checked')) {
$bundleSettings.find('.operations :input[name$="[language_hidden]"]').attr('checked', false);
$fieldSettings.find('.translatable :input').attr('checked', true);
$fieldSettings.show();
}
else {
$fieldSettings.hide();
}
});
});
}
};
})(jQuery);
......@@ -4,3 +4,4 @@ dependencies[] = language
package = Core
version = VERSION
core = 8.x
configure = admin/config/regional/translation_entity
......@@ -66,7 +66,8 @@ function translation_entity_install() {
function translation_entity_enable() {
$t_args = array(
'!language_url' => url('admin/config/regional/language'),
'!settings_url' => url('admin/config/regional/translation_entity'),
);
$message = t('You just added content translation capabilities to your site. To exploit them be sure to <a href="!language_url">enable at least two languages</a> and enable translation for <em>content types</em>, <em>taxonomy vocabularies</em>, <em>accounts</em> and any other element whose content you wish to translate.', $t_args);
$message = t('You just added content translation capabilities to your site. To exploit them be sure to <a href="!language_url">enable at least two languages</a> and <a href="!settings_url">enable translation</a> for <em>content types</em>, <em>taxonomy vocabularies</em>, <em>accounts</em> and any other element whose content you wish to translate.', $t_args);
drupal_set_message($message, 'warning');
}
......@@ -39,6 +39,9 @@ function translation_entity_help($path, $arg) {
$output .= '<dd>' . t('The Entity Translation module makes a basic set of permissions available. Additional <a href="@permissions">permissions</a> are made available after translation is enabled for each translatable element.', array('@permissions' => url('admin/people/permissions', array('fragment' => 'module-translation_entity')))) . '</dd>';
$output .= '</dl>';
return $output;
case 'admin/config/regional/translation_entity':
return '<p>' . t('Setup your content translation settings for all the translatable elements on your website. This allows you to enable/disable and configure them at once. As soon as new content types, vocabularies and other translatable elements are available, you will be able to configure them here.') . '</p>';
}
}
......@@ -157,12 +160,20 @@ function translation_entity_menu() {
}
}
$items['admin/config/regional/translation_entity'] = array(
'title' => 'Content translation settings',
'description' => 'Configure content translation for any translatable element.',
'page callback' => 'translation_entity_admin_page',
'access arguments' => array('administer entity translation'),
'file' => 'translation_entity.admin.inc',
);
$items['admin/config/regional/translation_entity/translatable/%'] = array(
'title' => 'Confirm change in translatability.',
'description' => 'Confirm page for changing field translatability.',
'page callback' => 'drupal_get_form',
'page arguments' => array('translation_entity_translatable_form', 5),
'access arguments' => array('toggle field translatability'),
'access arguments' => array('administer entity translation'),
'file' => 'translation_entity.admin.inc',
);
......@@ -341,6 +352,26 @@ function translation_entity_enabled($entity_type, $bundle = NULL, $skip_handler
return $enabled && ($skip_handler || field_has_translation_handler($entity_type, 'translation_entity'));
}
/**
* Returns a list of supported entity types.
*
* @return array
* An array of entity type names.
*/
function translation_entity_supported() {
$supported = array();
foreach (entity_get_info() as $entity_type => $info) {
// @todo Revisit this once all core entities are migrated to the Entity
// Field API and translation for configuration entities has been sorted
// out.
$entity_class = new ReflectionClass($info['class']);
if (!empty($info['fieldable']) && !$entity_class->implementsInterface('Drupal\Core\Config\Entity\ConfigEntityInterface')) {
$supported[$entity_type] = $entity_type;
}
}
return $supported;
}
/**
* Entity translation controller factory.
*
......@@ -394,9 +425,9 @@ function translation_entity_permission() {
'title' => t('Edit original values'),
'description' => t('Access the entity form in the original language.'),
),
'toggle field translatability' => array(
'title' => t('Toggle field translatability'),
'description' => t('Toggle translatability of fields performing a bulk update.'),
'administer entity translation' => array(
'title' => t('Administer entity translation'),
'description' => t('Configure translatability of entities and fields.'),
),
'translate any entity' => array(
'title' => t('Translate any entity'),
......@@ -598,7 +629,7 @@ function translation_entity_form_field_ui_field_edit_form_alter(array &$form, ar
'#title' => $link_title,
'#href' => $path,
'#options' => array('query' => drupal_get_destination()),
'#access' => user_access('toggle field translatability'),
'#access' => user_access('administer entity translation'),
),
);
}
......@@ -658,19 +689,20 @@ function translation_entity_enable_widget($entity_type, $bundle, array &$form, a
* Processed language configuration element.
*/
function translation_entity_language_configuration_element_process(array $element, array &$form_state, array &$form) {
$form_state['translation_entity']['key'] = $element['#name'];
$context = $form_state['language'][$element['#name']];
$element['translation_entity'] = array(
'#type' => 'checkbox',
'#title' => t('Enable translation'),
'#default_value' => translation_entity_enabled($context['entity_type'], $context['bundle']),
'#element_validate' => array('translation_entity_language_configuration_element_validate'),
'#prefix' => '<label>' . t('Translation') . '</label>',
);
if (empty($element['#translation_entity_skip_alter'])) {
$form_state['translation_entity']['key'] = $element['#name'];
$context = $form_state['language'][$element['#name']];
$form['#submit'][] = 'translation_entity_language_configuration_element_submit';
$element['translation_entity'] = array(
'#type' => 'checkbox',
'#title' => t('Enable translation'),
'#default_value' => translation_entity_enabled($context['entity_type'], $context['bundle']),
'#element_validate' => array('translation_entity_language_configuration_element_validate'),
'#prefix' => '<label>' . t('Translation') . '</label>',
);
$form['#submit'][] = 'translation_entity_language_configuration_element_submit';
}
return $element;
}
......@@ -715,3 +747,63 @@ function translation_entity_language_configuration_element_submit(array $form, a
menu_router_rebuild();
}
}
/**
* Stores entity translation settings.
*
* @param array $settings
* An associative array of settings keyed by entity type and bundle. At bundle
* level the following keys are available:
* - translatable: The bundle translatability status.
* - settings: An array of language configuration settings as defined by
* language_save_default_configuration().
* - fields: An associative array with field names as keys and a boolean as
* value, indicating field translatability.
*/
function translation_entity_save_settings($settings) {
$fields = array();
foreach ($settings as $entity_type => $entity_settings) {
foreach ($entity_settings as $bundle => $bundle_settings) {
// Update bundle translatability.
translation_entity_set_config($entity_type, $bundle, 'enabled', $bundle_settings['translatable']);
// Update language settings.
language_save_default_configuration($entity_type, $bundle, $bundle_settings['settings']['language']);
// Collapse field settings since here we have per instance settings, but
// translatability has per-field scope. We assume that all the field
// instances have the same value.
if (!empty($bundle_settings['fields'])) {
foreach ($bundle_settings['fields'] as $field_name => $translatable) {
// If a field is enabled for translation for at least one instance we
// need to mark it as translatable.
$fields[$field_name] = $translatable || !empty($fields[$field_name]);
}
}
// @todo Store non-configurable field settings to be able to alter their
// definition afterwards.
}
}
// Update field translatability.
foreach ($fields as $field_name => $translatable) {
$field = field_info_field($field_name);
$field['translatable'] = $translatable;
field_update_field($field);
}
// Ensure entity and menu router information are correctly rebuilt.
entity_info_cache_clear();
menu_router_rebuild();
}
/**
* Implemements hook_theme().
*/
function translation_entity_theme() {
return array(
'translation_entity_admin_settings_table' => array(
'render element' => 'element',
'file' => 'translation_entity.admin.inc',
),
);
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment