machine_name;
if (isset($form['i18n_translation']['i18n_mode'])) {
$args = array('!url' => url('admin/config/regional/entity_translation'));
$form['i18n_translation']['i18n_mode']['#options'][I18N_MODE_ENTITY_TRANSLATION] = t('Field translation. Term fields will be translated through the Entity translation module.', $args);
if (entity_translation_enabled_bundle('taxonomy_term', $name)) {
$form['i18n_translation']['i18n_mode']['#default_value'] = I18N_MODE_ENTITY_TRANSLATION;
}
}
else {
$form['entity_translation_taxonomy'] = array(
'#title' => t('Enable field translation'),
'#type' => 'checkbox',
'#prefix' => '',
'#default_value' => entity_translation_enabled('taxonomy_term', $name),
);
}
$form['#submit'][] = 'entity_translation_form_taxonomy_form_vocabulary_submit';
}
}
/**
* Submit handler for the taxonomy vocabulary form.
*/
function entity_translation_form_taxonomy_form_vocabulary_submit($form, &$form_state) {
if (!empty($form_state['values']['i18n_mode']) && $form_state['values']['i18n_mode'] == I18N_MODE_ENTITY_TRANSLATION) {
$form_state['values']['entity_translation_taxonomy'] = TRUE;
}
$info = variable_get('entity_translation_taxonomy', array());
$info[$form_state['vocabulary']->machine_name] = !empty($form_state['values']['entity_translation_taxonomy']);
variable_set('entity_translation_taxonomy', $info);
}
/**
* Returns a translated label for the specified taxonomy term.
*
* @param object $term
* A taxonomy term object.
* @param string $langcode
* The language the label should be translated in.
*
* @return string
* The translated taxonomy term label.
*
* @internal This is supposed to be used only by the ET taxonomy integration
* code, as it might be removed/replaced in any moment of the ET lifecycle.
*/
function _entity_translation_taxonomy_label($term, $langcode) {
$entity_type = 'taxonomy_term';
if (function_exists('title_entity_label')) {
$label = title_entity_label($term, $entity_type, $langcode);
}
else {
$label = entity_label($entity_type, $term);
}
return (string) $label;
}
/**
* Implements entity_translation_form_field_ui_field_edit_WIDGET_TYPE_form_alter().
*
* {@inheritdoc}
*/
function entity_translation_form_field_ui_field_edit_taxonomy_autocomplete_form_alter(&$form, &$form_state) {
$key = 'entity_translation_taxonomy_autocomplete_translate';
$instance = $form['#instance'];
$field_name = $instance['field_name'];
$entity_type = $instance['entity_type'];
$field = field_info_field($field_name);
$translatable = field_is_translatable($entity_type, $field);
$bundle = !empty($field['settings']['allowed_values'][0]['vocabulary']) ? $field['settings']['allowed_values'][0]['vocabulary'] : NULL;
$access = variable_get('entity_translation_taxonomy_autocomplete', FALSE);
// Add a checkbox to toggle in-place translation for autocomplete widgets.
$form['instance']['settings'][$key] = array(
'#type' => 'checkbox',
'#title' => t('Enable in-place translation of terms'),
'#description' => t('Check this option if you wish to use translation forms to perform in-place translation for terms entered in the original language.'),
'#default_value' => !$translatable && !empty($form['#instance']['settings'][$key]),
'#access' => $access && (!$form_state['field_has_data'] || !$translatable) && entity_translation_enabled('taxonomy_term', $bundle),
'#states' => array(
'visible' => array(':input[name="field[translatable]"]' => array('checked' => FALSE)),
),
'#weight' => -8,
);
}
/**
* Checks whether in-place translation is enabled for the autocomplete widget.
*
* @param array $element
* The widget form element.
* @param array $form_state
* The form state array.
*
* @return bool
* TRUE if in-place translation is enabled, FALSE otherwise.
*/
function _entity_translation_taxonomy_autocomplete_translation_enabled($element, $form_state) {
$field = field_info_field($element['#field_name']);
if (field_is_translatable($element['#entity_type'], $field)) {
return FALSE;
}
list($id, , $bundle) = entity_extract_ids($element['#entity_type'], $element['#entity']);
if (!$id) {
return FALSE;
}
$entity_type = 'taxonomy_term';
$parent_handler = entity_translation_get_handler($element['#entity_type'], $element['#entity']);
$active_langcode = $parent_handler->getActiveLanguage();
$translations = $parent_handler->getTranslations();
$entity_langcode = isset($translations->original) ? $translations->original : LANGUAGE_NONE;
$instance = field_info_instance($element['#entity_type'], $field['field_name'], $bundle);
// We need to make sure that we are not dealing with a translation form.
// However checking the active language is not enough, because the user may
// have changed the entity language.
return
(isset($form_state['entity_translation']['is_translation']) ?
$form_state['entity_translation']['is_translation'] : ($active_langcode != $entity_langcode)) &&
!empty($instance['settings']['entity_translation_taxonomy_autocomplete_translate']) &&
(user_access('translate any entity') || user_access("translate $entity_type entities"));
}
/**
* Implements hook_field_widget_WIDGET_TYPE_form_alter().
*
* {@inheritdoc}
*/
function entity_translation_field_widget_taxonomy_autocomplete_form_alter(&$element, &$form_state, $context) {
// The autocomplete widget is also displayed in the field configuration form,
// in which case we do not need to perform any alteration. To preserve BC, by
// default we enable our taxonomy autocomplete override only on new sites.
if (!isset($element['#entity']) || !variable_get('entity_translation_taxonomy_autocomplete', FALSE)) {
return;
}
// We will need to translate term names, if Title is enabled and configured
// for this vocabulary.
$entity_type = 'taxonomy_term';
$field = field_widget_field($element, $form_state);
$bundle = !empty($field['settings']['allowed_values'][0]['vocabulary']) ? $field['settings']['allowed_values'][0]['vocabulary'] : NULL;
if ($bundle && entity_translation_enabled($entity_type, $bundle)) {
$parent_handler = entity_translation_get_handler($element['#entity_type'], $element['#entity']);
$langcode = $parent_handler->getActiveLanguage();
$terms = array_values(_entity_translation_taxonomy_autocomplete_widget_get_terms($element));
// If we are using the regular autocomplete behavior also in translation
// forms, we need to set our custom callback.
if (!_entity_translation_taxonomy_autocomplete_translation_enabled($element, $form_state)) {
$element['#autocomplete_path'] = 'entity_translation/' . $entity_type . '/autocomplete/' . $langcode . '/' . $element['#field_name'];
$translations = $parent_handler->getTranslations();
if (isset($translations->original) && $translations->original != $langcode) {
$labels = array();
foreach ($terms as $delta => $term) {
$labels[] = _entity_translation_taxonomy_label($term, $langcode);
}
$element['#default_value'] = implode(', ', $labels);
}
}
// Otherwise we just provide the in-place translation widget.
else {
$element['#type'] = 'fieldset';
$element['#description'] = t('Enter one translation for each term');
$element['#access'] = (bool) $terms;
foreach ($terms as $delta => $term) {
$element[$delta] = array(
'#type' => 'textfield',
'#default_value' => _entity_translation_taxonomy_label($term, $langcode),
'#required' => TRUE,
'#tid' => $term->tid,
);
}
$element['#process'][] = 'entity_translation_taxonomy_autocomplete_process';
}
// The native term save logic is performed at widget validation level, so we
// just replace the validation handler to provide our logic instead.
$element['#element_validate'] = array_values(array_diff($element['#element_validate'], array('taxonomy_autocomplete_validate')));
$element['#element_validate'][] = 'entity_translation_taxonomy_autocomplete_validate';
}
}
/**
* Returns the terms referenced by the taxonomy autocomplete widget field.
*
* @param array $element
* The taxonomy autocomplete form element.
*
* @return object[]
* An associative array of taxonomy term object keyed by their identifiers.
*/
function _entity_translation_taxonomy_autocomplete_widget_get_terms($element) {
$items = isset($element['#entity']->{$element['#field_name']}[$element['#language']]) ?
$element['#entity']->{$element['#field_name']}[$element['#language']] : array();
$tids = array_map(function ($item) { return $item['tid']; }, $items);
return taxonomy_term_load_multiple($tids);
}
/**
* Process callback for the ET taxonomy autocomplete widget.
*
* {@inheritdoc}
*/
function entity_translation_taxonomy_autocomplete_process($element) {
// The in-place translation widget makes sense only for untranslatable field,
// which may have the "(all languages)" label suffix. In this case it would be
// confusing so we need to revert that.
$instance = field_info_instance($element['#entity_type'], $element['#field_name'], $element['#bundle']);
$element['#title'] = check_plain($instance['label']);
return $element;
}
/**
* Entity translation taxonomy autocomplete callback.
*
* @param string $langcode
* The input language.
* @param string $field_name
* The name of the term reference field.
* @param string $tags_typed
* (optional) A comma-separated list of term names entered in the
* autocomplete form element. Only the last term is used for autocompletion.
* Defaults to an empty string.
*
* @see taxonomy_autocomplete()
*/
function entity_translation_taxonomy_term_autocomplete($langcode = NULL, $field_name = '', $tags_typed = '') {
// If the request has a '/' in the search text, then the menu system will have
// split it into multiple arguments, recover the intended $tags_typed.
$args = func_get_args();
// Shift off the $langcode and $field_name arguments.
array_shift($args);
array_shift($args);
$tags_typed = implode('/', $args);
// Make sure the field exists and is a taxonomy field.
if (!($field = field_info_field($field_name)) || $field['type'] !== 'taxonomy_term_reference') {
// Error string. The JavaScript handler will realize this is not JSON and
// will display it as debugging information.
print t('Taxonomy field @field_name not found.', array('@field_name' => $field_name));
exit;
}
// The user enters a comma-separated list of tags. We only autocomplete the
// last tag.
$tags_typed = drupal_explode_tags($tags_typed);
$tag_last = drupal_strtolower(array_pop($tags_typed));
$term_matches = array();
if ($tag_last != '') {
if (!isset($langcode) || $langcode == LANGUAGE_NONE) {
$langcode = $GLOBALS['language_content']->language;
}
// Part of the criteria for the query come from the field's own settings.
$vocabulary = _entity_translation_taxonomy_reference_get_vocabulary($field);
$entity_type = 'taxonomy_term';
$query = new EntityFieldQuery();
$query->addTag('taxonomy_term_access');
$query->entityCondition('entity_type', $entity_type);
// If the Title module is enabled and the taxonomy term name is replaced for
// the current bundle, we can look for translated names, otherwise we fall
// back to the regular name property.
if (module_invoke('title', 'field_replacement_enabled', $entity_type, $vocabulary->machine_name, 'name')) {
$name_field = 'name_field';
$language_group = 0;
// Do not select already entered terms.
$column = 'value';
if (!empty($tags_typed)) {
$query->fieldCondition($name_field, $column, $tags_typed, 'NOT IN', NULL, $language_group);
}
$query->fieldCondition($name_field, $column, $tag_last, 'CONTAINS', NULL, $language_group);
$query->fieldLanguageCondition($name_field, array($langcode, LANGUAGE_NONE), NULL, NULL, $language_group);
}
else {
$name_field = 'name';
// Do not select already entered terms.
if (!empty($tags_typed)) {
$query->propertyCondition($name_field, $tags_typed, 'NOT IN');
}
$query->propertyCondition($name_field, $tag_last, 'CONTAINS');
}
// Select rows that match by term name.
$query->propertyCondition('vid', $vocabulary->vid);
$query->range(0, 10);
$result = $query->execute();
// Populate the results array.
$prefix = count($tags_typed) ? drupal_implode_tags($tags_typed) . ', ' : '';
$terms = !empty($result[$entity_type]) ? taxonomy_term_load_multiple(array_keys($result[$entity_type])) : array();
foreach ($terms as $tid => $term) {
$name = _entity_translation_taxonomy_label($term, $langcode);
$n = $name;
// Term names containing commas or quotes must be wrapped in quotes.
if (strpos($name, ',') !== FALSE || strpos($name, '"') !== FALSE) {
$n = '"' . str_replace('"', '""', $name) . '"';
}
$term_matches[$prefix . $n] = check_plain($name);
}
}
drupal_json_output($term_matches);
}
/**
* Returns the vocabulary enabled for the specified taxonomy reference field.
*
* @param array $field
* A field definition.
*
* @return object|null
* A vocabulary object or NULL if none is found.
*/
function _entity_translation_taxonomy_reference_get_vocabulary($field) {
$vocabulary = NULL;
if (!empty($field['settings']['allowed_values'])) {
$vids = array();
$vocabularies = taxonomy_vocabulary_get_names();
foreach ($field['settings']['allowed_values'] as $tree) {
$vids[] = $vocabularies[$tree['vocabulary']]->vid;
}
$vocabulary = taxonomy_vocabulary_load(reset($vids));
}
return $vocabulary;
}
/**
* Form element validate handler for taxonomy term autocomplete element.
*
* {@inheritdoc}
*
* @see taxonomy_autocomplete_validate()
*/
function entity_translation_taxonomy_autocomplete_validate($element, &$form_state) {
$value = array();
list($id) = entity_extract_ids($element['#entity_type'], $element['#entity']);
$is_new = !isset($id);
// This is the language of the parent entity, that we will be applying to new
// terms.
$parent_handler = entity_translation_get_handler($element['#entity_type'], $element['#entity']);
$langcode = !empty($form_state['entity_translation']['form_langcode']) ?
$form_state['entity_translation']['form_langcode'] : $parent_handler->getActiveLanguage();
// Handle in-place translation.
if (_entity_translation_taxonomy_autocomplete_translation_enabled($element, $form_state)) {
// The referenced terms cannot change, so we just need to collect their term
// identifiers. We also build a map of the corresponding deltas for later
// use.
$deltas = array();
foreach (element_children($element) as $delta) {
$tid = $element[$delta]['#tid'];
$deltas[$tid] = $delta;
$value[$delta]['tid'] = $tid;
}
// Save term translations.
$entity_type = 'taxonomy_term';
$name_field = 'name_field';
$source_langcode = $parent_handler->getSourceLanguage();
// This is a validation handler, so we must defer the actual save to the
// submit phase.
$terms_to_save = &$form_state['entity_translation']['taxonomy_autocomplete'][$element['#entity_type']][$id][$element['#field_name']];
foreach (taxonomy_term_load_multiple(array_keys($deltas)) as $term) {
// This is also the right context to perform validation.
$term_translation = $element[$deltas[$term->tid]]['#value'];
if (!$term_translation) {
$instance = field_info_instance($element['#entity_type'], $element['#field_name'], $element['#bundle']);
drupal_set_message(t('The translations for %field_label cannot be empty.', array('%field_label' => $instance['label'])), 'error', FALSE);
continue;
}
$handler = entity_translation_get_handler($entity_type, $term);
$translations = $handler->getTranslations();
$term_langcode = $handler->getLanguage();
$typed_langcode = $term_langcode != LANGUAGE_NONE ? $langcode : $term_langcode;
// Create a new translation in the active language, if it is missing.
if (!isset($translations->data[$typed_langcode]) && $typed_langcode != LANGUAGE_NONE) {
$translation = array(
'language' => $typed_langcode,
'source' => $source_langcode,
'uid' => $GLOBALS['user']->uid,
'status' => 1,
'created' => REQUEST_TIME,
'changed' => REQUEST_TIME,
);
$translation_values = array(
$name_field => array($typed_langcode => array(array('value' => $term_translation))),
);
$handler->setTranslation($translation, $translation_values);
$terms_to_save[] = $term;
}
// Otherwise we just update the existing translation, if it has changed.
// If the term is language-neutral, we just update its main value. This is
// expected to happen normally, but could when referencing existing terms.
elseif ($term_translation != _entity_translation_taxonomy_label($term, $typed_langcode)) {
$term->{$name_field}[$typed_langcode][0]['value'] = $term_translation;
$terms_to_save[] = $term;
}
}
}
// Autocomplete widgets do not send their tids in the form, so we must detect
// them here and process them independently.
elseif ($tags = $element['#value']) {
$entity_type = 'taxonomy_term';
$field = field_widget_field($element, $form_state);
$vocabulary = _entity_translation_taxonomy_reference_get_vocabulary($field);
$typed_tags = drupal_explode_tags($tags);
// Collect existing terms by name.
$existing_terms = array();
foreach (_entity_translation_taxonomy_autocomplete_widget_get_terms($element) as $term) {
$name = _entity_translation_taxonomy_label($term, $langcode);
$existing_terms[$name] = $term;
}
// Select terms that match by the (translated) name.
$query = new EntityFieldQuery();
$query->addTag('taxonomy_term_access');
$query->entityCondition('entity_type', $entity_type);
$query->propertyCondition('vid', $vocabulary->vid);
if ($langcode != LANGUAGE_NONE && module_invoke('title', 'field_replacement_enabled', $entity_type, $vocabulary->machine_name, 'name')) {
$language_group = 0;
// Do not select already entered terms.
$name_field = 'name_field';
$column = 'value';
$query->fieldCondition($name_field, $column, $typed_tags, NULL, NULL, $language_group);
// When we are creating a new entity, we cannot filter by active language,
// as that may have not be applied to the autocomplete query.
if (!$is_new) {
$query->fieldLanguageCondition($name_field, array($langcode, LANGUAGE_NONE), NULL, NULL, $language_group);
}
}
else {
$query->propertyCondition('name', $typed_tags);
}
$result = $query->execute();
// When we are creating a new entity, the language used for the autocomplete
// query is the current content language, so we should use that to update
// the map of existing terms.
if (!empty($result[$entity_type])) {
$typed_langcode = !$is_new ? $langcode : $GLOBALS['language_content']->language;
foreach (taxonomy_term_load_multiple(array_keys($result[$entity_type])) as $term) {
$name = _entity_translation_taxonomy_label($term, $typed_langcode);
$existing_terms[$name] = $term;
}
}
// Now collect the identifiers for the various terms and update the taxonomy
// reference field values.
foreach ($typed_tags as $delta => $typed_tag) {
// See if the term exists in the chosen vocabulary and return the tid.
// Otherwise create a new 'autocreate' term for insert/update.
if (isset($existing_terms[$typed_tag])) {
$term = $existing_terms[$typed_tag];
}
else {
$term = (object) array(
'tid' => 'autocreate',
'vid' => $vocabulary->vid,
'name' => $typed_tag,
'vocabulary_machine_name' => $vocabulary->machine_name,
);
$handler = entity_translation_get_handler($entity_type, $term);
$handler->setOriginalLanguage($langcode);
$handler->initTranslations();
}
$value[] = (array) $term;
}
}
form_set_value($element, $value, $form_state);
}
/**
* Term-specific implementation of hook_field_attach_submit().
*/
function entity_translation_taxonomy_term_field_attach_submit($entity_type, $entity, $form, &$form_state) {
// Finally save in-place translations
if (!empty($form_state['entity_translation']['taxonomy_autocomplete'])) {
foreach ($form_state['entity_translation']['taxonomy_autocomplete'] as $entity_type => $entity_type_data) {
foreach ($entity_type_data as $id => $field_name_data) {
foreach ($field_name_data as $field_name => $term_data) {
if (!is_array($term_data)) {
continue;
}
foreach ($term_data as $term) {
entity_translation_entity_save('taxonomy_term', $term);
}
}
}
}
}
}