- <?php
- * @file
- * Manages content translations.
- *
- * Translations are managed in sets of posts, which represent the same
- * information in different languages. Only content types for which the
- * administrator has explicitly enabled translations could have translations
- * associated. Translations are managed in sets with exactly one source post
- * per set. The source post is used to translate to different languages, so if
- * the source post is significantly updated, the editor can decide to mark all
- * translations outdated.
- *
- * The node table stores the values used by this module:
- * - tnid: Integer for the translation set ID, which equals the node ID of the
- * source post.
- * - translate: Integer flag, either indicating that the translation is up to
- * date (0) or needs to be updated (1).
- */
-
- * Identifies a content type which has translation support enabled.
- */
- define('TRANSLATION_ENABLED', 2);
-
- * Implements hook_menu().
- */
- function translation_menu() {
- $items = array();
- $items['node/%node/translate'] = array(
- 'title' => 'Translate',
- 'page callback' => 'translation_node_overview',
- 'page arguments' => array(1),
- 'access callback' => '_translation_tab_access',
- 'access arguments' => array(1),
- 'type' => MENU_LOCAL_TASK,
- 'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
- 'weight' => 2,
- 'file' => 'translation.pages.inc',
- );
- return $items;
- }
-
- * Access callback: Checks that the user has permission to 'translate content'.
- *
- * Only displays the translation tab for nodes that are not language-neutral
- * of types that have translation enabled.
- *
- * @param $node
- * A node entity.
- *
- * @return
- * TRUE if the translation tab should be displayed, FALSE otherwise.
- *
- * @see translation_menu()
- */
- function _translation_tab_access($node) {
- if ($node->langcode != LANGUAGE_NONE && translation_supported_type($node->type) && node_access('view', $node)) {
- return user_access('translate content');
- }
- return FALSE;
- }
-
- * Implements hook_admin_paths().
- */
- function translation_admin_paths() {
- if (config_get('system.core', 'node_admin_theme')) {
- $paths = array(
- 'node/*/translate' => TRUE,
- );
- return $paths;
- }
- }
-
- * Implements hook_permission().
- */
- function translation_permission() {
- return array(
- 'translate content' => array(
- 'title' => t('Translate content'),
- ),
- );
- }
-
- * Implements hook_config_info().
- */
- function translation_config_info() {
- $prefixes['translation.settings'] = array(
- 'label' => t('Translation settings'),
- 'group' => t('Configuration'),
- );
- return $prefixes;
- }
-
- * Implements hook_views_api().
- */
- function translation_views_api() {
- return array(
- 'api' => '3.0',
- 'path' => backdrop_get_path('module', 'translation') . '/views',
- );
- }
-
- * Implements hook_autoload_info().
- */
- function translation_autoload_info() {
- return array(
-
- 'views_handler_argument_node_tnid' => 'views/views_handler_argument_node_tnid.inc',
- 'views_handler_field_node_link_translate' => 'views/views_handler_field_node_link_translate.inc',
- 'views_handler_field_node_translation_link' => 'views/views_handler_field_node_translation_link.inc',
- 'views_handler_filter_node_tnid' => 'views/views_handler_filter_node_tnid.inc',
- 'views_handler_filter_node_tnid_child' => 'views/views_handler_filter_node_tnid_child.inc',
- 'views_handler_relationship_translation' => 'views/views_handler_relationship_translation.inc',
- );
- }
-
- * Implements hook_form_FORM_ID_alter() for node_type_form().
- */
- function translation_form_node_type_form_alter(&$form, &$form_state) {
- $node_type = $form['#node_type'];
-
- $form['multilingual']['language']['#type'] = 'radios';
- $form['multilingual']['language']['#options'] = array(
- 0 => t('Disabled'),
- 1 => t('Enabled'),
- TRANSLATION_ENABLED => t('Enabled, with translation'),
- );
- $form['multilingual']['language'][0]['#description'] = t('New content will be created with no defined language.');
- $form['multilingual']['language'][1]['#description'] = t('New content will have a language selection option listing all the <a href="@languages">enabled languages</a>.', array('@languages' => url('admin/config/regional/language')));
- $form['multilingual']['language'][TRANSLATION_ENABLED]['#description'] = t('Content may be translated into any of the <a href="@languages">enabled languages</a>.', array('@languages' => url('admin/config/regional/language')));
-
- $form['multilingual']['language']['#description'] = t('Existing content will not be affected by changing this option.');
- $form['multilingual']['translation_show_links'] = array(
- '#type' => 'checkbox',
- '#title' => t('Show content translation links'),
- '#description' => t('Show the links to translations in content body and teasers. If you uncheck this option, switching language will only be available from the language switcher block.'),
- '#default_value' => (bool) $node_type->settings['translation_show_links'],
- '#states' => array(
- 'visible' => array(
- ':input[name="language"]' => array('value' => TRANSLATION_ENABLED),
- ),
- ),
- );
- }
-
- * Implements hook_node_type_load().
- */
- function translation_node_type_load(&$types) {
- foreach ($types as $type_name => $type) {
- $types[$type_name]->settings += array(
- 'translation_show_links' => TRUE,
- );
- }
- }
-
- * Implements hook_form_BASE_FORM_ID_alter() for node_form().
- *
- * Alters language fields on node edit forms when a translation is about to be
- * created.
- *
- * @see node_form()
- */
- function translation_form_node_form_alter(&$form, &$form_state) {
- if (translation_supported_type($form['#node']->type)) {
- $node = $form['#node'];
-
-
- $languages = language_list();
- $grouped_languages = array();
- foreach ($languages as $langcode => $language) {
- $grouped_languages[(int) $language->enabled][$langcode] = $language;
- }
-
- $translator_widget = !empty($grouped_languages[0]) && user_access('translate content');
- $groups = array(t('Disabled'), t('Enabled'));
-
-
-
- if ($translator_widget) {
- $options = array($groups[1] => array(LANGUAGE_NONE => t('- None -')));
- foreach (array(1, 0) as $status) {
- $group = $groups[$status];
- foreach ($grouped_languages[$status] as $langcode => $language) {
- $options[$group][$langcode] = $language->name;
- }
- }
- $form['langcode']['#options'] = $options;
- }
- if (!empty($node->translation_source)) {
-
- $form['translation_source'] = array('#type' => 'value', '#value' => $node->translation_source);
- $form['langcode']['#disabled'] = TRUE;
- }
- elseif (!empty($node->nid) && !empty($node->tnid)) {
-
-
-
- unset($form['langcode']['#options'][LANGUAGE_NONE]);
- foreach (translation_node_get_translations($node->tnid) as $langcode => $translation) {
- if ($translation->nid != $node->nid) {
- if ($translator_widget) {
- $group = $groups[(int)!isset($disabled_languages[$langcode])];
- unset($form['langcode']['#options'][$group][$langcode]);
- }
- else {
- unset($form['langcode']['#options'][$langcode]);
- }
- }
- }
-
- $form['tnid'] = array('#type' => 'value', '#value' => $node->tnid);
- $form['translation'] = array(
- '#type' => 'fieldset',
- '#title' => t('Translation settings'),
- '#access' => user_access('translate content'),
- '#collapsible' => TRUE,
- '#collapsed' => !$node->translate,
- '#tree' => TRUE,
- '#weight' => 30,
- );
- if ($node->tnid == $node->nid) {
-
- $form['translation']['retranslate'] = array(
- '#type' => 'checkbox',
- '#title' => t('Flag translations as outdated'),
- '#default_value' => 0,
- '#description' => t('If you made a significant change, which means translations should be updated, you can flag all translations of this post as outdated. This will not change any other property of those posts, like whether they are published or not.'),
- );
- $form['translation']['status'] = array('#type' => 'value', '#value' => 0);
- }
- else {
- $form['translation']['status'] = array(
- '#type' => 'checkbox',
- '#title' => t('This translation needs to be updated'),
- '#default_value' => $node->translate,
- '#description' => t('When this option is checked, this translation needs to be updated because the source post has changed. Uncheck when the translation is up to date again.'),
- );
- }
- }
- }
- }
-
- * Implements hook_node_view().
- *
- * Displays translation links with language names if this node is part of a
- * translation set. If no language provider is enabled, "fall back" to simple
- * links built through the result of translation_node_get_translations().
- */
- function translation_node_view(Node $node, $view_mode) {
-
-
-
- $translation_show_links = config_get('node.type.' . $node->type, 'settings.translation_show_links');
- if ($translation_show_links && isset($node->tnid) && language_multilingual() && $translations = translation_node_get_translations($node->tnid)) {
- $languages = language_list(TRUE);
-
-
-
-
-
-
-
- $type = config_get('translation.settings', 'language_type');
- $custom_links = language_negotiation_get_switch_links($type, "node/$node->nid");
- $links = array();
-
- foreach ($translations as $langcode => $translation) {
-
-
- if ($translation->status && isset($languages[$langcode]) && $langcode != $node->langcode) {
- $language = $languages[$langcode];
- $key = "translation_$langcode";
-
- if (isset($custom_links->links[$langcode])) {
- $links[$key] = $custom_links->links[$langcode];
- }
- else {
- $links[$key] = array(
- 'href' => "node/{$translation->nid}",
- 'title' => isset($language->native) ? $language->native : $language->name,
- 'language' => $language,
- );
- }
-
-
-
- $links[$key] += array('attributes' => array());
- $attributes = array(
- 'title' => $translation->title,
- 'class' => array('translation-link'),
- );
- $links[$key]['attributes'] = $attributes + $links[$key]['attributes'];
- }
- }
-
- $node->content['links']['translation'] = array(
- '#theme' => 'links__node__translation',
- '#links' => $links,
- '#attributes' => array('class' => array('links', 'inline')),
- );
- }
- }
-
- * Implements hook_node_prepare().
- */
- function translation_node_prepare(Node $node) {
-
- if (translation_supported_type($node->type) &&
-
- empty($node->nid) &&
-
- user_access('translate content') &&
-
- isset($_GET['translation']) &&
- isset($_GET['target']) &&
- is_numeric($_GET['translation'])) {
-
- $source_node = node_load($_GET['translation']);
- if (empty($source_node) || !node_access('view', $source_node)) {
-
-
-
- return;
- }
-
- $language_list = language_list();
- $langcode = $_GET['target'];
- if (!isset($language_list[$langcode]) || ($source_node->langcode == $langcode)) {
-
- return;
- }
-
-
- if (!empty($source_node->tnid)) {
- $translations = translation_node_get_translations($source_node->tnid);
- if (isset($translations[$langcode])) {
- backdrop_set_message(t('A translation of %title in %language already exists, a new %type will be created instead of a translation.', array(
- '%title' => $source_node->title,
- '%language' => isset($language_list[$langcode]->native) ? $language_list[$langcode]->native : $language_list[$langcode]->name,
- '%type' => $node->type,
- )), 'error');
- return;
- }
- }
-
-
- $node->langcode = $langcode;
- $node->translation_source = $source_node;
- $node->title = $source_node->title;
-
-
-
- field_attach_prepare_translation('node', $node, $node->langcode, $source_node, $source_node->langcode);
- }
- }
-
- * Implements hook_node_insert().
- */
- function translation_node_insert(Node $node) {
-
- if (translation_supported_type($node->type)) {
- if (!empty($node->translation_source)) {
- if ($node->translation_source->tnid) {
-
- $tnid = $node->translation_source->tnid;
- }
- else {
-
- $tnid = $node->translation_source->nid;
- db_update('node')
- ->fields(array(
- 'tnid' => $tnid,
- 'translate' => 0,
- ))
- ->condition('nid', $tnid)
- ->execute();
-
-
- entity_get_controller('node')->resetCache(array($tnid));
- }
-
- db_update('node')
- ->fields(array(
- 'tnid' => $tnid,
- 'translate' => 0,
- ))
- ->condition('nid', $node->nid)
- ->execute();
-
- $node->tnid = $tnid;
- }
- }
- }
-
- * Implements hook_node_update().
- */
- function translation_node_update(Node $node) {
-
- if (translation_supported_type($node->type)) {
- if (isset($node->translation) && $node->translation && !empty($node->langcode) && $node->tnid) {
-
- db_update('node')
- ->fields(array(
- 'tnid' => $node->tnid,
- 'translate' => $node->translation['status'],
- ))
- ->condition('nid', $node->nid)
- ->execute();
-
- if (!empty($node->translation['retranslate'])) {
-
- $translations = db_select('node', 'n')
- ->fields('n', array('nid'))
- ->condition('nid', $node->nid, '<>')
- ->condition('tnid', $node->tnid)
- ->execute()
- ->fetchCol();
-
- db_update('node')
- ->fields(array('translate' => 1))
- ->condition('nid', $translations, 'IN')
- ->execute();
-
-
- entity_get_controller('node')->resetCache($translations);
- }
- }
- }
- }
-
- * Implements hook_node_validate().
- *
- * Ensures that duplicate translations can't be created for the same source.
- */
- function translation_node_validate(Node $node, $form) {
-
- if (translation_supported_type($node->type) && (!empty($node->tnid) || !empty($form['#node']->translation_source->nid))) {
- $tnid = !empty($node->tnid) ? $node->tnid : $form['#node']->translation_source->nid;
- $translations = translation_node_get_translations($tnid);
- if (isset($translations[$node->langcode]) && $translations[$node->langcode]->nid != $node->nid ) {
- form_set_error('langcode', t('There is already a translation in this language.'));
- }
- }
- }
-
- * Implements hook_node_predelete().
- */
- function translation_node_predelete(Node $node) {
-
- if (translation_supported_type($node->type)) {
- translation_remove_from_set($node);
- }
- }
-
- * Removes a node from its translation set and updates accordingly.
- *
- * @param $node
- * A node entity.
- */
- function translation_remove_from_set($node) {
- if (isset($node->tnid) && $node->tnid) {
- $query = db_update('node')
- ->fields(array(
- 'tnid' => 0,
- 'translate' => 0,
- ));
-
-
- $set_nids = db_query('SELECT nid FROM {node} WHERE tnid = :tnid', array(':tnid' => $node->tnid))->fetchCol();
- if (count($set_nids) == 1) {
-
- $query
- ->condition('tnid', $node->tnid)
- ->execute();
-
- $flush_set = TRUE;
- }
- else {
- $query
- ->condition('nid', $node->nid)
- ->execute();
-
-
-
- if ($node->tnid == $node->nid) {
- $new_tnid = db_query('SELECT nid FROM {node} WHERE tnid = :tnid ORDER BY translate ASC, nid ASC', array(':tnid' => $node->tnid))->fetchField();
- db_update('node')
- ->fields(array('tnid' => $new_tnid))
- ->condition('tnid', $node->tnid)
- ->execute();
-
- $flush_set = TRUE;
- }
- }
-
-
- $nids = !empty($flush_set) ? $set_nids : array($node->nid);
- entity_get_controller('node')->resetCache($nids);
- }
- }
-
- * Gets all nodes in a given translation set.
- *
- * @param $tnid
- * The translation source nid of the translation set, the identifier of the
- * node used to derive all translations in the set.
- *
- * @return
- * Array of partial node objects (nid, title, language) representing all
- * nodes in the translation set, in effect all translations of node $tnid,
- * including node $tnid itself. Because these are partial nodes, you need to
- * node_load() the full node, if you need more properties. The array is
- * indexed by language code.
- */
- function translation_node_get_translations($tnid) {
- if (is_numeric($tnid) && $tnid) {
- $translations = &backdrop_static(__FUNCTION__, array());
-
- if (!isset($translations[$tnid])) {
- $translations[$tnid] = array();
- $result = db_select('node', 'n')
- ->fields('n', array('nid', 'type', 'uid', 'status', 'title', 'langcode'))
- ->condition('n.tnid', $tnid)
- ->addTag('node_access')
- ->execute();
-
- foreach ($result as $node) {
- $translations[$tnid][$node->langcode] = $node;
- }
- }
- return $translations[$tnid];
- }
- }
-
- * Returns whether the given content type has support for translations.
- *
- * @return
- * TRUE if translation is supported, and FALSE if not.
- */
- function translation_supported_type($type) {
- $node_type = node_type_get_type($type);
- return $node_type->settings['language'] == TRANSLATION_ENABLED;
- }
-
- * Returns the paths of all translations of a node, based on its Backdrop path.
- *
- * @param $path
- * A Backdrop path, for example node/432.
- * @return
- * An array of paths of translations of the node accessible to the current
- * user, keyed with language codes.
- */
- function translation_path_get_translations($path) {
- $paths = array();
-
- if ((preg_match("!^node/(\d+)(/.+|)$!", $path, $matches)) && ($node = node_load((int) $matches[1])) && !empty($node->tnid)) {
- foreach (translation_node_get_translations($node->tnid) as $language => $translation_node) {
- $paths[$language] = 'node/' . $translation_node->nid . $matches[2];
- }
- }
- return $paths;
- }
-
- * Implements hook_language_switch_links_alter().
- *
- * Replaces links with pointers to translated versions of the content.
- */
- function translation_language_switch_links_alter(array &$links, $type, $path) {
- $language_type = config_get('translation.settings', 'language_type');
-
-
- $query = backdrop_get_query_parameters();
- foreach ($links as $langcode => $link) {
- $links[$langcode]['query'] = $query;
- }
-
- if ($type == $language_type && preg_match("!^node/(\d+)(/.+|)!", $path, $matches)) {
- $node = node_load((int) $matches[1]);
-
- if (empty($node->tnid)) {
-
-
-
-
- if (empty($node) || $node->langcode == LANGUAGE_NONE || !translation_supported_type($node->type)) {
- return;
- }
- $translations = array($node->langcode => $node);
- }
- else {
- $translations = translation_node_get_translations($node->tnid);
- }
-
- foreach ($links as $langcode => $link) {
- if (isset($translations[$langcode]) && $translations[$langcode]->status) {
-
- $links[$langcode]['href'] = 'node/' . $translations[$langcode]->nid . $matches[2];
- }
- else {
-
- unset($links[$langcode]['href']);
- $links[$langcode]['attributes']['class'][] = 'locale-untranslated';
- }
- }
- }
- }