From 68e7573fedb3c791d9db411ca180a809dbf223fb Mon Sep 17 00:00:00 2001
From: Sascha Grossenbacher <saschagros@gmail.com>
Date: Wed, 11 May 2011 17:15:16 +0200
Subject: [PATCH] Issue #1153830 by Berdir: Ported select translations interface to Drupal 7 including tests.

---
 i18n.test                     |    6 +-
 i18n_node/i18n_node.info      |    1 +
 i18n_node/i18n_node.module    |    8 +++-
 i18n_node/i18n_node.pages.inc |  118 +++++++++++++++++++++++-----------------
 i18n_node/i18n_node.test      |   89 +++++++++++++++++++++++++++++++
 5 files changed, 168 insertions(+), 54 deletions(-)
 create mode 100644 i18n_node/i18n_node.test

diff --git a/i18n.test b/i18n.test
index 1c80657..fbce39f 100644
--- a/i18n.test
+++ b/i18n.test
@@ -8,9 +8,9 @@
 class Drupali18nTestCase extends DrupalWebTestCase {
   protected $current_user;
 
-  function setUpLanguages() {
+  function setUpLanguages($admin_permissions = array()) {
     // Setup users.
-    $this->admin_user = $this->drupalCreateUser(array('bypass node access', 'administer nodes', 'administer languages', 'administer content types', 'administer blocks', 'access administration pages'));
+    $this->admin_user = $this->drupalCreateUser(array_merge(array('bypass node access', 'administer nodes', 'administer languages', 'administer content types', 'administer blocks', 'access administration pages'), $admin_permissions));
     $this->translator = $this->drupalCreateUser(array('translate interface'));
 
     $this->i18nLogin($this->admin_user);
@@ -134,7 +134,7 @@ class Drupali18nTestCase extends DrupalWebTestCase {
     $edit["body[$langcode][0][value]"] = $body;
     $edit['language'] = $language;
     $this->drupalPost('node/add/' . $type, $edit, t('Save'));
-    $this->assertRaw(t('Basic node %title has been created.', array('%title' => $title)), t('Basic page created.'));
+    $this->assertRaw(t('Basic @type %title has been created.', array('@type' => $type, '%title' => $title)), t('Basic page created.'));
 
     // Check to make sure the node was created.
     $node = $this->drupalGetNodeByTitle($title);
diff --git a/i18n_node/i18n_node.info b/i18n_node/i18n_node.info
index 5578628..d410fca 100644
--- a/i18n_node/i18n_node.info
+++ b/i18n_node/i18n_node.info
@@ -6,3 +6,4 @@ dependencies[] = i18n_string
 package = Multilingual - Internationalization
 core = 7.x
 configure = admin/config/regional/i18n/node
+files[]=i18n_node.test
diff --git a/i18n_node/i18n_node.module b/i18n_node/i18n_node.module
index dd22c84..fe8764f 100644
--- a/i18n_node/i18n_node.module
+++ b/i18n_node/i18n_node.module
@@ -69,6 +69,12 @@ function i18n_node_menu() {
     'type' => MENU_LOCAL_TASK,
     'weight' => 10,
   );
+  $items['i18n/node/autocomplete'] = array(
+    'page callback' => 'i18n_node_autocomplete',
+    'file' => 'i18n_node.pages.inc',
+    'access arguments' => array('administer content translations'),
+    'type' => MENU_CALLBACK,
+  );
   return $items;
 }
 
@@ -534,7 +540,7 @@ function _i18n_node_form_node_form_alter($form, $form_state) {
 function i18n_node_theme() {
   return array(
     'i18n_node_select_translation' => array(
-      'arguments' => array('element' => NULL),
+      'render element' => 'element',
       'file' => 'i18n_node.pages.inc',
     ),
   );
diff --git a/i18n_node/i18n_node.pages.inc b/i18n_node/i18n_node.pages.inc
index 8d3ebfd..e44a929 100644
--- a/i18n_node/i18n_node.pages.inc
+++ b/i18n_node/i18n_node.pages.inc
@@ -37,7 +37,7 @@ function i18n_node_add_page() {
 function i18n_node_translation_overview($node) {
   include_once DRUPAL_ROOT . '/includes/language.inc';
 
-  if ($node->tnid) {
+  if (!empty($node->tnid)) {
     // Already part of a set, grab that set.
     $tnid = $node->tnid;
     $translations = translation_node_get_translations($node->tnid);
@@ -99,14 +99,10 @@ function i18n_node_translation_overview($node) {
     '#header' => $header,
     '#rows' => $rows,
   );
-  /**
-   * @todo Update selectable translations feature
-   */
-  /*
-  if (user_access('administer translations')) {
+
+  if (user_access('administer content translations')) {
     $build['translation_node_select'] = drupal_get_form('i18n_node_select_translation', $node, $translations);
   }
-  */
   return $build;
 }
 
@@ -115,7 +111,7 @@ function i18n_node_translation_overview($node) {
  *
  * This one uses autocomplete fields for all languages
  */
-function i18n_node_select_translation($form_state, $node, $translations) {
+function i18n_node_select_translation($form, &$form_state, $node, $translations) {
   $form['node'] = array('#type' => 'value', '#value' => $node);
   $form['translations'] = array(
     '#type' => 'fieldset',
@@ -124,20 +120,30 @@ function i18n_node_select_translation($form_state, $node, $translations) {
     '#theme' => 'i18n_node_select_translation',
     '#description' => t("Alternatively, you can select existing nodes as translations of this one or remove nodes from this translation set. Only nodes that have the right language and don't belong to other translation set will be available here.")
   );
-  foreach (language_list() as $language) {
-    if ($language->language != $node->language) {
-      $trans_nid = isset($translations->language) ? $translations->tnid : 0;
-      $form['translations']['nid'][$language->language] = array('#type' => 'value', '#value' => $trans_nid);
-      $form['translations']['language'][$language->language] = array('#value' => $language->name);
-      $form['translations']['node'][$language->language] = array(
+  foreach (language_list() as $language => $object) {
+    if ($language != $node->language) {
+      $nid = isset($translations[$language]) ? $translations[$language]->nid : 0;
+      $form['translations']['nid'][$language] = array(
+        '#type' => 'value',
+        '#value' => $nid,
+      );
+      $form['translations']['language'][$language] = array(
+        '#type' => 'value',
+        '#value' => $object->name,
+      );
+      $form['translations']['node'][$language] = array(
         '#type' => 'textfield',
         '#maxlength' => 255,
-        '#autocomplete_path' => 'i18n/node/autocomplete/' . $node->type . '/' . $language->language,
-        '#default_value' => $trans_nid ? i18n_node_nid2autocomplete($trans_nid) : '',
+        '#autocomplete_path' => 'i18n/node/autocomplete/' . $node->type . '/' . $language,
+        '#default_value' => $nid ? i18n_node_nid2autocomplete($nid) : '',
       );
     }
   }
-  $form['buttons']['update'] = array('#type' => 'submit', '#value' => t('Update translations'));
+  $form['actions'] = array('#type' => 'actions');
+  $form['actions']['update'] = array(
+    '#type' => 'submit',
+    '#value' => t('Update translations'),
+  );
   //$form['buttons']['clean'] = array('#type' => 'submit', '#value' => t('Delete translation set'));
   return $form;
 }
@@ -151,7 +157,7 @@ function i18n_node_select_translation_validate($form, &$form_state) {
       $nid = 0;
     }
     else {
-      $nid = i18n_node_autocomplete2nid($title, "translations][node][$lang", array($node->type), array($lang));
+      $nid = i18n_node_autocomplete2nid($title, "translations][node][$lang", array($form_state['values']['node']->type), array($lang));
     }
     $form_state['values']['translations']['nid'][$lang] = $nid;
   }
@@ -184,14 +190,23 @@ function i18n_node_select_translation_submit($form, &$form_state) {
   }
   // Now update values for all nodes
   if ($add) {
-    $args = array('' => $tnid) + $add;
-    db_query('UPDATE {node} SET tnid = %d WHERE nid IN (' . db_placeholders($add) . ')', $args);
+    db_update('node')
+      ->fields(array(
+        'tnid' => $tnid,
+      ))
+      ->condition('nid', $add)
+      ->execute();
     if (count($new)) {
       drupal_set_message(format_plural(count($new), 'Added a node to the translation set.', 'Added @count nodes to the translation set.'));
     }
   }
   if ($remove) {
-    db_query('UPDATE {node} SET tnid = 0 WHERE nid IN (' . db_placeholders($remove) . ')', $remove);
+    db_update('node')
+      ->fields(array(
+        'tnid' => 0,
+      ))
+      ->condition('nid', $remove)
+      ->execute();
     drupal_set_message(format_plural(count($remove), 'Removed a node from the translation set.', 'Removed @count nodes from the translation set.'));
   }
 }
@@ -206,7 +221,7 @@ function i18n_node_autocomplete($type, $language, $string = '') {
     // Add a class wrapper for a few required CSS overrides.
     $matches[$row['title'] ." [nid:$id]"] = '<div class="reference-autocomplete">'. $row['rendered'] . '</div>';
   }
-  drupal_json($matches);
+  drupal_json_output($matches);
 }
 
 /**
@@ -262,41 +277,43 @@ function i18n_node_autocomplete2nid($name, $field = NULL, $type, $language) {
  *   Match mode: 'contains', 'equals', 'starts_with'
  * @param $params
  *   Other query arguments: type, language or numeric ones
- *
- * Some code from CCK's nodereference.module
  */
 function _i18n_node_references($string, $match = 'contains', $params = array(), $limit = 10) {
-  $where = $args = array();
-  $match_operators = array(
-    'contains' => "LIKE '%%%s%%'",
-    'equals' => "= '%s'",
-    'starts_with' => "LIKE '%s%%'",
-  );
+  $query = db_select('node', 'n')
+    ->fields('n', array('nid', 'title' ,'type'))
+    ->orderBy('n.title')
+    ->orderBy('n.type')
+    ->range(0, $limit);
+
   foreach ($params as $key => $value) {
-    $type = in_array($key, array('type', 'language')) ? 'char' : 'int';
-    if (is_array($value)) {
-      $where[] = "n.$key IN (" . db_placeholders($value, $type) . ') ';
-      $args = array_merge($args, $value);
-    }
-    else {
-      $where[] = "n.$key = " . db_type_placeholder($type);
-      $args[] = $value;
-    }
+    $query->condition($key, $value);
   }
-  $where[] = 'n.title '. (isset($match_operators[$match]) ? $match_operators[$match] : $match_operators['contains']);
-  $args[] = $string;
+
+  switch ($match) {
+    case 'equals':
+      $query->condition('n.title', $string);
+      break;
+
+    case 'starts_with':
+      $query->condition('n.title', $string . '%', LIKE);
+      break;
+
+    case 'contains':
+    default:
+      $query->condition('n.title', '%' . $string . '%', 'LIKE');
+      break;
+  }
+
   // Disable and reenable i18n selection mode so no language conditions are inserted
-  i18n_selection_mode('off');
-  $sql = db_rewrite_sql('SELECT n.nid, n.title, n.type FROM {node} n WHERE ' . implode(' AND ', $where) . ' ORDER BY n.title, n.type');
-  $result = db_query_range($sql, $args, 0, $limit) ;
-  i18n_selection_mode('reset');
+  i18n_select(false);
   $references = array();
-  while ($node = db_fetch_object($result)) {
+  foreach ($query->execute() as $node) {
     $references[$node->nid] = array(
       'title' => $node->title,
       'rendered' => check_plain($node->title),
     );
   }
+  i18n_select(true);
   return $references;
 }
 
@@ -304,18 +321,19 @@ function _i18n_node_references($string, $match = 'contains', $params = array(),
  * Theme select translation form
  * @ingroup themeable
  */
-function theme_i18n_node_select_translation($elements) {
+function theme_i18n_node_select_translation($variables) {
+  $elements = $variables['element'];
   $output = '';
   if (isset($elements['nid'])) {
     $rows = array();
     foreach (element_children($elements['nid']) as $lang) {
       $rows[] = array(
-        drupal_render($elements['language'][$lang]),
+        $elements['language'][$lang]['#value'],
         drupal_render($elements['node'][$lang]),
       );
     }
-    $output .= theme('table', array(), $rows);
-    $output .= drupal_render($elements);
+    $output .= theme('table', array('rows' => $rows));
+    $output .= drupal_render_children($elements);
   }
   return $output;
 }
\ No newline at end of file
diff --git a/i18n_node/i18n_node.test b/i18n_node/i18n_node.test
new file mode 100644
index 0000000..e1dca5e
--- /dev/null
+++ b/i18n_node/i18n_node.test
@@ -0,0 +1,89 @@
+<?php
+
+/**
+ * @file
+ * Contains test cases for the i18n_node module.
+ */
+
+class I18nNodeTestCase extends Drupali18nTestCase {
+  function getInfo() {
+    return array(
+      'name' => 'Content translation',
+      'group' => 'Internationalization',
+      'description' => 'Content translation functions',
+    );
+  }
+
+  function setUp() {
+    parent::setUp('translation', 'i18n_node');
+    parent::setUpLanguages(array('administer content translations', 'translate content'));
+    parent::setUpContentTranslation();
+  }
+
+  /**
+   * Tests for adding content to an existing translation set.
+   */
+  function testAddContentToTranslationSet() {
+
+    // Create 3 nodes in different languages.
+    $en_title = $this->randomName(10);
+    $en_body = $this->randomString(50);
+    $en_node = $this->createNode('page', $en_title, $en_body, 'en');
+
+    $es_title = $this->randomName(10);
+    $es_body = $this->randomString(50);
+    $es_node = $this->createNode('page', $es_title, $es_body, 'es');
+
+    $ptbr_title = $this->randomName(10);
+    $ptbr_body = $this->randomString(50);
+    $ptbr_node = $this->createNode('page', $ptbr_title, $ptbr_body, 'pt-br');
+
+    // Check the autocomplete suggestions.
+    $this->drupalGet('i18n/node/autocomplete/page/es/' . substr($es_title, 0, 3));
+    $this->assertText($es_title);
+    $this->assertNoText($en_title);
+    $this->assertNoText($ptbr_title);
+
+    $this->drupalGet('i18n/node/autocomplete/page/es/' . substr($en_title, 0, 3));
+    $this->assertNoText($es_title);
+    $this->assertNoText($en_title);
+    $this->assertNoText($ptbr_title);
+
+    $this->drupalGet('i18n/node/autocomplete/page/pt-br/' . substr($ptbr_title, 0, 3));
+    $this->assertNoText($es_title);
+    $this->assertNoText($en_title);
+    $this->assertText($ptbr_title);
+
+    // Go to the translations tab.
+    $this->drupalGet('node/' . $en_node->nid);
+    $this->clickLink(t('Translate'));
+
+    // Test validation.
+    $edit = array(
+      'translations[node][es]' => $ptbr_title,
+    );
+    $this->drupalPost(NULL, $edit, t('Update translations'));
+    $this->assertText(t('Found no valid post with that title: @title', array('@title' => $ptbr_title)));
+
+    // Add two translated nodes.
+    $edit = array(
+      'translations[node][pt-br]' => $ptbr_title,
+      'translations[node][es]' => $es_title,
+    );
+    $this->drupalPost(NULL, $edit, t('Update translations'));
+    $this->assertText(t('Added @count nodes to the translation set.', array('@count' => 2)));
+
+    $this->assertFieldByName('translations[node][es]', i18n_node_nid2autocomplete($es_node->nid));
+    $this->assertFieldByName('translations[node][pt-br]', i18n_node_nid2autocomplete($ptbr_node->nid));
+
+    // Remove a translation node again.
+    $edit = array(
+      'translations[node][pt-br]' => '',
+    );
+    $this->drupalPost(NULL, $edit, t('Update translations'));
+    $this->assertText(t('Removed a node from the translation set.'));
+
+    $this->assertFieldByName('translations[node][es]', i18n_node_nid2autocomplete($es_node->nid));
+    $this->assertFieldByName('translations[node][pt-br]', '');
+  }
+}
\ No newline at end of file
-- 
1.7.4.1

