diff --git a/core/lib/Drupal/Core/Entity/EntityManager.php b/core/lib/Drupal/Core/Entity/EntityManager.php index 1fcf266..caf46a2 100644 --- a/core/lib/Drupal/Core/Entity/EntityManager.php +++ b/core/lib/Drupal/Core/Entity/EntityManager.php @@ -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. diff --git a/core/modules/comment/lib/Drupal/comment/Plugin/Core/Entity/Comment.php b/core/modules/comment/lib/Drupal/comment/Plugin/Core/Entity/Comment.php index e0bbfd9..96b436e 100644 --- a/core/modules/comment/lib/Drupal/comment/Plugin/Core/Entity/Comment.php +++ b/core/modules/comment/lib/Drupal/comment/Plugin/Core/Entity/Comment.php @@ -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", diff --git a/core/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/Tables.php b/core/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/Tables.php index bf02e2b..c4f1b29 100644 --- a/core/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/Tables.php +++ b/core/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/Tables.php @@ -95,7 +95,7 @@ function addField($field, $type, $langcode) { // field), a field API field (a configurable field). $specifier = $specifiers[$key]; // First, check for field API fields by trying to retrieve the field specified. - // Normally it is a field name, but field_purge_batch() is passing in + // Normally it is a field name, but field_purge_batch() is passing in // id:$field_id so check that first. if (substr($specifier, 0, 3) == 'id:') { $field = field_info_field_by_id(substr($specifier, 3)); @@ -142,8 +142,9 @@ function addField($field, $type, $langcode) { } } else { - // If this is the last specifier, default to value. - $column = 'value'; + // If this is the last specifier, default to value or fall back to the + // first field column if value is not defined. + $column = isset($field['columns']['value']) ? 'value' : key($field['columns']); } $table = $this->ensureFieldTable($index_prefix, $field, $type, $langcode, $base_table, $entity_id_field, $field_id_field); $sql_column = _field_sql_storage_columnname($field['field_name'], $column); diff --git a/core/modules/node/lib/Drupal/node/Plugin/Core/Entity/Node.php b/core/modules/node/lib/Drupal/node/Plugin/Core/Entity/Node.php index 47de776..216cfc9 100644 --- a/core/modules/node/lib/Drupal/node/Plugin/Core/Entity/Node.php +++ b/core/modules/node/lib/Drupal/node/Plugin/Core/Entity/Node.php @@ -18,6 +18,7 @@ * @Plugin( * id = "node", * label = @Translation("Content"), + * bundle_label = @Translation("Content type"), * module = "node", * controller_class = "Drupal\node\NodeStorageController", * render_controller_class = "Drupal\node\NodeRenderController", diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/Core/Entity/Term.php b/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/Core/Entity/Term.php index 51468af..e129e87 100644 --- a/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/Core/Entity/Term.php +++ b/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/Core/Entity/Term.php @@ -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", diff --git a/core/modules/translation_entity/lib/Drupal/translation_entity/Tests/EntityTranslationSettingsTest.php b/core/modules/translation_entity/lib/Drupal/translation_entity/Tests/EntityTranslationSettingsTest.php new file mode 100644 index 0000000..1e76d42 --- /dev/null +++ b/core/modules/translation_entity/lib/Drupal/translation_entity/Tests/EntityTranslationSettingsTest.php @@ -0,0 +1,111 @@ + 'Entity Translation settings', + 'description' => 'Tests the entity translation settings UI.', + 'group' => 'Entity Translation UI', + ); + } + + function setUp() { + parent::setUp(); + + // Set up two content types to test field instances shared between 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 a form error is raised and settings are not saved if only an + // entity type and no bundle is marked as translatable. + $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 the translation settings are ignored if the bundle is marked + // translatable but the entity type is not. + $edit = array('settings[comment][comment_node_article][translatable]' => TRUE); + $this->assertSettings('comment', NULL, FALSE, $edit); + + // Test that the translation settings are ignored if only a field is marked + // as translatable and not the related entity type and bundle. + $edit = array('settings[comment][comment_node_article][fields][comment_body]' => TRUE); + $this->assertSettings('comment', NULL, FALSE, $edit); + + // Test that the translation settings are stored if an entity type and + // bundle are marked as translatable. + $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. + * + * @param string $entity_type + * The entity type for which to check translatibility. + * @param string $bundle + * The bundle for which to check translatibility. + * @param boolean $enabled + * TRUE if translatibility should be enabled, FALSE otherwise. + * @param array $edit + * An array of values to submit to the entity translation settings page. + * @return boolean + * TRUE if the assertion succeeded, FALSE otherwise. + */ + protected function assertSettings($entity_type, $bundle, $enabled, $edit) { + $this->drupalPost('admin/config/regional/translation_entity', $edit, t('Save')); + $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); + } + +} diff --git a/core/modules/translation_entity/translation_entity.admin.css b/core/modules/translation_entity/translation_entity.admin.css new file mode 100644 index 0000000..d2070ec --- /dev/null +++ b/core/modules/translation_entity/translation_entity.admin.css @@ -0,0 +1,29 @@ +/** + * @file + * Styles for the administration page. + */ + +#translation-entity-admin-settings-form table .bundle { + width: 39%; +} + +#translation-entity-admin-settings-form table td.bundle { + font-weight: bold; +} + +#translation-entity-admin-settings-form table .field { + width: 39%; + padding-left: 3em; +} + +#translation-entity-admin-settings-form table .field label { + font-weight: normal; +} + +#translation-entity-admin-settings-form table .translatable { + width: 1%; +} + +#translation-entity-admin-settings-form table .operations { + width: 60%; +} diff --git a/core/modules/translation_entity/translation_entity.admin.inc b/core/modules/translation_entity/translation_entity.admin.inc index bacba11..02decb3 100644 --- a/core/modules/translation_entity/translation_entity.admin.inc +++ b/core/modules/translation_entity/translation_entity.admin.inc @@ -8,6 +8,308 @@ use Drupal\Core\Entity\EntityInterface; /** + * Page callback: Returns the translation administration settings form. + */ +function translation_entity_admin_page() { + return drupal_get_form('translation_entity_admin_settings_form', translation_entity_supported()); +} + +/** + * Form constructor for the administration settings form. + * + * @see translation_entity_admin_settings_form_submit() + * @see translation_entity_admin_settings_form_validate() + * + * @ingroup forms + */ +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], + '#bundle_admin_path' => isset($info['bundles'][$bundle]['admin']['real path']) ? $info['bundles'][$bundle]['admin']['real path'] : FALSE, + '#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'), + ); + + return $form; +} + +/** + * Form validation handler for translation_entity_admin_settings_form(). + * + * @see translation_entity_admin_settings_form_submit() + */ +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(). + * + * @see translation_entity_admin_settings_form_validate() + */ +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 non-translatable. Similarly, if a bundle is made non-translatable + // all of its fields will be not 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_update_field_translatability($settings); + drupal_set_message(t('Settings successfully updated.')); +} + +/** + * 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. + * + * @todo Remove this migration entirely once the Field API is converted to the + * Entity Field API. + */ +function _translation_entity_update_field_translatability($settings) { + $fields = array(); + foreach ($settings as $entity_type => $entity_settings) { + foreach ($entity_settings as $bundle => $bundle_settings) { + // 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. + } + } + + $operations = array(); + foreach ($fields as $field_name => $translatable) { + $field = field_info_field($field_name); + if ($field['translatable'] != $translatable) { + // If a field is untranslatable, it can have no data except under + // LANGUAGE_NOT_SPECIFIED. Thus we need a field to be translatable before + // we convert data to the entity language. Conversely we need to switch + // data back to LANGUAGE_NOT_SPECIFIED before making a field + // untranslatable lest we lose information. + $field_operations = array( + array('translation_entity_translatable_batch', array($translatable, $field_name)), + array('translation_entity_translatable_switch', array($translatable, $field_name)), + ); + $field_operations = !$translatable ? $field_operations : array_reverse($field_operations); + $operations = array_merge($operations, $field_operations); + } + } + + // As last operation store the submitted settings. + $operations[] = array('translation_entity_save_settings', array($settings)); + + $title = !$translatable ? t('Enabling translation for the selected fields') : t('Disabling translation for the selected fields'); + $batch = array( + 'title' => $title, + 'operations' => $operations, + 'finished' => 'translation_entity_translatable_batch_done', + 'file' => drupal_get_path('module', 'translation_entity') . '/translation_entity.admin.inc', + ); + batch_set($batch); +} + +/** + * Theming function for an administration settings table. + * + * @param array $variables + * An associative array containing: + * - element: A render element representing an entity type and its bundles. + * Properties used: #bundle_label. + * + * @ingroup themable + */ +function theme_translation_entity_admin_settings_table(array $variables) { + $element = $variables['element']; + + $header = array( + array( + 'data' => t('Translatable'), + 'class' => array('translatable') + ), + array( + 'data' => $element['#bundle_label'], + 'class' => array('bundle') + ), + array( + 'data' => t('Configuration'), + 'class' => array('operations') + ), + ); + + $rows = array(); + + foreach (element_children($element) as $bundle) { + $field_names = !empty($element[$bundle]['fields']) ? element_children($element[$bundle]['fields']) : array(); + + $bundle_label = check_plain($element[$bundle]['translatable']['#label']); + $checkbox_id = $element[$bundle]['translatable']['#id']; + $rows[] = array( + 'data' => array( + array( + 'data' => drupal_render($element[$bundle]['translatable']), + 'class' => array('translatable'), + ), + array( + 'data' => '', + 'class' => array('bundle'), + ), + 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]; + $checkbox_id = $field['#id']; + $rows[] = array( + 'data' => array( + array( + 'data' => drupal_render($field), + 'class' => array('translatable'), + ), + array( + 'data' => '', + 'class' => array('field'), + ), + 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 translation_entity_translatable_form(array $form, array &$form_state, $field_name) { @@ -65,9 +367,9 @@ function translation_entity_translatable_form_submit(array $form, array $form_st } // If a field is untranslatable, it can have no data except under - // LANGUAGE_NOT_SPECIFIED. Thus we need a field to be translatable before we convert - // data to the entity language. Conversely we need to switch data back to - // LANGUAGE_NOT_SPECIFIED before making a field untranslatable lest we lose + // LANGUAGE_NOT_SPECIFIED. Thus we need a field to be translatable before we + // convert data to the entity language. Conversely we need to switch data back + // to LANGUAGE_NOT_SPECIFIED before making a field untranslatable lest we lose // information. $operations = array( array('translation_entity_translatable_batch', array(!$translatable, $field_name)), @@ -101,13 +403,10 @@ function translation_entity_translatable_form_submit(array $form, array $form_st */ function translation_entity_translatable_switch($translatable, $field_name) { $field = field_info_field($field_name); - - if ($field['translatable'] === $translatable) { - return; + if ($field['translatable'] !== $translatable) { + $field['translatable'] = $translatable; + field_update_field($field); } - - $field['translatable'] = $translatable; - field_update_field($field); } /** diff --git a/core/modules/translation_entity/translation_entity.admin.js b/core/modules/translation_entity/translation_entity.admin.js new file mode 100644 index 0000000..8793345 --- /dev/null +++ b/core/modules/translation_entity/translation_entity.admin.js @@ -0,0 +1,35 @@ +(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); diff --git a/core/modules/translation_entity/translation_entity.info b/core/modules/translation_entity/translation_entity.info index 4a28def..3032a52 100644 --- a/core/modules/translation_entity/translation_entity.info +++ b/core/modules/translation_entity/translation_entity.info @@ -4,3 +4,4 @@ dependencies[] = language package = Core version = VERSION core = 8.x +configure = admin/config/regional/translation_entity diff --git a/core/modules/translation_entity/translation_entity.install b/core/modules/translation_entity/translation_entity.install index e66e544..8d31f88 100644 --- a/core/modules/translation_entity/translation_entity.install +++ b/core/modules/translation_entity/translation_entity.install @@ -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 enable at least two languages and enable translation for content types, taxonomy vocabularies, accounts and any other element whose content you wish to translate.', $t_args); + $message = t('Content translation has been enabled. To use content translation, enable at least two languages and enable translation for content types, taxonomy vocabularies, accounts, or any other element you wish to translate.', $t_args); drupal_set_message($message, 'warning'); } diff --git a/core/modules/translation_entity/translation_entity.module b/core/modules/translation_entity/translation_entity.module index 91ee94c..f68c075 100644 --- a/core/modules/translation_entity/translation_entity.module +++ b/core/modules/translation_entity/translation_entity.module @@ -23,12 +23,7 @@ function translation_entity_help($path, $arg) { $output .= '
'; $output .= '
' . t('Enabling translation') . '
'; $output .= '

' . t('Before you can translate content, there must be at least two non-system languages added on the languages administration page.', array('!url' => url('admin/config/regional/language'))) . '

'; - $output .= '

' . t('After adding languages, enable translation for any content you wish to translate:') . '

'; - $output .= ''; - $output .= '

' . t('Finally, under the Manage fields tab, edit each field you wish to be translatable, and enable translation under Global settings.') . '

'; + $output .= '

' . t('After adding languages, configure translation.', array('!url' => url('admin/config/regional/translation_entity'))) . '

'; $output .= '
' . t('Translating content') . '
'; $output .= '
' . t('After enabling translation you can create a new piece of content, or edit existing content and assign it a language. Then, you will see a Translations tab or link that will gives an overview of the translation status for the current content. From there, you can add translations and edit or delete existing translations. This process is similar for every translatable element on your site, such as taxonomy terms, comments or user accounts.') . '
'; $output .= '
' . t('Changing source language') . '
'; @@ -39,6 +34,14 @@ function translation_entity_help($path, $arg) { $output .= '
' . t('The Entity Translation module makes a basic set of permissions available. Additional permissions are made available after translation is enabled for each translatable element.', array('@permissions' => url('admin/people/permissions', array('fragment' => 'module-translation_entity')))) . '
'; $output .= '
'; return $output; + + case 'admin/config/regional/translation_entity': + $output = ''; + $output .= t('Enable content translation for content types, taxonomy vocabularies, accounts, or any other translatable element on your site.'); + if (!language_multilingual()) { + $output .= ' ' . t('Before you can translate content, there must be at least two non-system languages added on the languages administration page.', array('!url' => url('admin/config/regional/language'))); + } + return '

' . $output . '

'; } } @@ -158,12 +161,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', ); @@ -343,6 +354,26 @@ function translation_entity_enabled($entity_type, $bundle = NULL, $skip_handler } /** + * 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. * * @param string $entity_type @@ -395,9 +426,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'), @@ -594,7 +625,7 @@ function translation_entity_form_field_ui_field_settings_form_alter(array &$form '#title' => $link_title, '#href' => $path, '#options' => array('query' => drupal_get_destination()), - '#access' => user_access('toggle field translatability'), + '#access' => user_access('administer entity translation'), ), ); } @@ -653,19 +684,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' => '', - ); + 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' => '', + ); + $form['#submit'][] = 'translation_entity_language_configuration_element_submit'; + } return $element; } @@ -710,3 +742,44 @@ 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']); + } + } + + // 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', + ), + ); +}