From 509f03673e938f389c5cbd3ae55680b4d910e6c3 Mon Sep 17 00:00:00 2001
From: Logan Smyth <loganfsmyth@gmail.com>
Date: Thu, 15 Sep 2011 15:09:04 -0400
Subject: [PATCH] Issue #361597: API tests to load and save a source and a
 translation string set

---
 modules/locale/locale.module |  251 ++++++++++++++++++++++++++++++++++++++++++
 modules/locale/locale.test   |   90 +++++++++++++++
 2 files changed, 341 insertions(+), 0 deletions(-)

diff --git a/modules/locale/locale.module b/modules/locale/locale.module
index 4a15ed9..b54ee4a 100644
--- a/modules/locale/locale.module
+++ b/modules/locale/locale.module
@@ -970,6 +970,257 @@ function locale_library_alter(&$libraries, $module) {
   }
 }
 
+// --------------------------------------------------------------------------------
+// Locale CRUD API
+
+/**
+ * @defgroup locale-api Locale CRUD API
+ * @{
+ * API for managing Locale sources and targets
+ */
+
+/**
+ * Load a source string record.
+ *
+ * @param $lid
+ *   A source lid.
+ * @return
+ *   Source string.
+ */
+function locale_source_load($lid) {
+  $records = locale_source_load_multiple(array('lid' => $lid));
+  return !empty($records) ? current($records) : FALSE;
+}
+
+/**
+ * Get source for a string, given key/value conditions.
+ *
+ * @param $conditions
+ *   An array of source lids, or an array of key => value pairs.
+ * @return
+ *   Array of source strings (objects) that meet the condtions, indexed by lid.
+ */
+function locale_source_load_multiple(array $conditions) {
+  // Allow list of lids as arguments.
+  if (is_numeric(key($conditions))) {
+    $conditions = array('lid' => $conditions);
+  }
+
+  // Build basic query on locales_source.
+  $query = db_select('locales_source', 's');
+  $query->fields('s');
+  foreach ($conditions as $field => $value) {
+    $query->condition($field, $value, is_array($value) ? 'IN' : '=');
+  }
+  return $query->execute()->fetchAllAssoc('lid');
+}
+
+/**
+ * Save / update a source string record.
+ *
+ * @param $source
+ *   Object representing a source string.
+ *   'lid' property used for updates.
+ */
+function locale_source_save(&$source) {
+  // Ensure we're working with an object.
+  $source = (object)$source;
+  if (!empty($source->lid)) {
+    return drupal_write_record('locales_source', $source, 'lid');
+  }
+  else {
+    return drupal_write_record('locales_source', $source);
+  }
+}
+
+/**
+ * Delete a specific source record from the database.
+ *
+ * @param $lid
+ *   The lid of source string to delete.
+ */
+function locale_source_delete($lid, $delete_translations = TRUE) {
+  locale_source_delete_multiple(array('lid' => $lid), $delete_translations);
+}
+
+
+/**
+ * Delete one or more locale source records from the database. Optionally, also
+ * delete associated target strings.
+ *
+ * @param $conditions
+ *   An array of lids, or an array field-value pairs to match.
+ *   Values may be arrays, in which case matching will use the 'IN' operator.
+ * @param $delete_translations
+ *   Boolean, whether to delete all associated target strings.
+ */
+function locale_source_delete_multiple(array $conditions, $delete_translations = TRUE) {
+  // Accept a single lid or an array of lids.
+  if (is_numeric(key($conditions))) {
+    $conditions = array('lid' => $conditions);
+  }
+  // If requested and deleting by ID, remove all associated targets.
+  if ($delete_translations) {
+    locale_translation_delete_multiple($conditions);
+  }
+  // Remove the sources.
+  $query = db_delete('locales_source');
+  foreach ($conditions as $field => $value) {
+    $query->condition($field, $value, is_array($value) ? 'IN' : '=');
+  }
+  $query->execute();
+}
+
+/**
+ * Load a translation string record.
+ *
+ * @param $lid
+ *   The source id of the translation.
+ * @param $langcode
+ *   The language code of the translation.
+ * @param $plural
+ *   The plural of the translation.
+ * @param $get_source
+ *   Whether to get source data only in case there's no translation.
+ *
+ * @return
+ *   The first translation object matching the given conditions.
+ */
+function locale_translation_load($lid, $langcode, $plural = 0, $get_source = FALSE) {
+  $records = locale_translation_load_multiple(array('lid' => $lid, 'language' => $langcode, 'plural' => $plural), $get_source);
+  return !empty($records) ? current($records) : FALSE;
+}
+
+/**
+ * Load one or more translations.
+ *
+ * @param $conditions
+ *   An array of source lids or an array of key => value pairs.
+ * @param $get_source
+ *   Whether to get source data only in case there's no translation.
+ * @return
+ *   The full translation object.
+ */
+function locale_translation_load_multiple(array $conditions, $get_source = FALSE) {
+  // Get target fields to check which table each condition is in.
+  $target_schema = drupal_get_schema('locales_target');
+  $target_field_info = $target_schema['fields'];
+
+  // Check for an array of lids.
+  if (is_numeric(key($conditions))) {
+    $conditions = array('lid' => $conditions);
+  }
+
+  $query = db_select('locales_source', 's');
+  // If we want source data too, add fields and LEFT JOIN, otherwise no fields
+  // and INNER JOIN.
+  if ($get_source) {
+    $query->fields('s');
+    $join = 'leftJoin';
+
+    // exclude lid because that is ALSO in locales_source
+    unset($target_field_info['lid']);
+  }
+  else {
+    $join = 'join';
+  }
+  $target_fields = array_keys($target_field_info);
+
+  // Join localse_target table, and add all fields except 'lid'.
+  $query->$join('locales_target', 't', 's.lid = t.lid');
+
+  // Not using '*' because then target lid overrides source lid.
+  $query->fields('t', $target_fields);
+
+  foreach ($conditions as $field => $value) {
+    $table_alias = in_array($field, $target_fields) ? 't' : 's';
+    $query->condition($table_alias . '.' . $field, $value, is_array($value) ? 'IN' : '=');
+  }
+
+  $translations = array();
+  foreach ($query->execute() as $row) {
+    // Make uniqe identifier by appending primary-key items.
+    $translations[$row->lid . '-' . $row->language . '-' . $row->plural] = $row;
+  }
+  return $translations;
+}
+
+/**
+ * Save a translation.
+ *
+ * @param $translation
+ *   Object representing a string translation.
+ * @return
+ *   SAVED_NEW or SAVED_UPDATED.
+ */
+function locale_translation_save(&$translation) {
+  $translation = (object)$translation;
+
+  // Check again for existing source and translation.
+  $existing = locale_translation_load($translation->lid, $translation->language, $translation->plural);
+  if (!empty($existing)) {
+    return drupal_write_record('locales_target', $translation, array('lid', 'language', 'plural'));
+  }
+  else {
+    return drupal_write_record('locales_target', $translation);
+  }
+}
+
+/**
+ * Delete one locale target record from database.
+ *
+ * @param $translation
+ *   Object representing a string translation.
+ */
+function locale_translation_delete($lid, $langcode, $plural = 0) {
+  locale_translation_delete_multiple(array(
+    'lid'       => $lid,
+    'language'  => $langcode,
+    'plural'    => $plural,
+  ));
+}
+
+/**
+ * Delete one or more locale target records from the database.
+ *
+ * @param $conditions
+ *   An array of lids, or an array field-value pairs to match.
+ *   Values may be arrays, in which case matching will use the 'IN' operator.
+ */
+function locale_translation_delete_multiple(array $conditions) {
+  $target_schema = drupal_get_schema('locales_target');
+  $target_fields = array_keys($target_schema['fields']);
+
+  // Accept an array of lids.
+  if (is_numeric(key($conditions))) {
+    $conditions = array('lid' => $conditions);
+  }
+
+  // Build the query which may have conditions for the source table and the
+  // target table.
+  $query = db_delete('locales_target');
+  $subquery = db_select('locales_source', 's');
+  foreach ($conditions as $field => $value) {
+    if (in_array($field, $target_fields)) {
+      $query->condition($field, $value, is_array($value) ? 'IN' : '=');
+    }
+    else {
+      $subquery->condition($field, $value, is_array($value) ? 'IN' : '=');
+    }
+  }
+  // If we have conditions for the target table we need to build a IN subquery.
+  if ($subquery->conditions()) {
+    $subquery->addField('s', 'lid');
+    $query->condition('lid', $subquery, 'IN');
+  }
+
+  $query->execute();
+}
+
+/**
+ * @} End of "locale-api"
+ */
+
 // ---------------------------------------------------------------------------------
 // Language switcher block
 
diff --git a/modules/locale/locale.test b/modules/locale/locale.test
index 90e313d..5607fcb 100644
--- a/modules/locale/locale.test
+++ b/modules/locale/locale.test
@@ -591,6 +591,96 @@ class LocaleTranslationFunctionalTest extends DrupalWebTestCase {
 }
 
 /**
+ * Tests for locale CRUD API functions.
+ */
+class LocaleApiTest extends DrupalWebTestCase {
+  public static function getInfo() {
+    return array(
+      'name' => t('Locale API functions'),
+      'description' => t('Tests the performance of locale CRUD APIs.'),
+      'group' => t('Locale'),
+    );
+  }
+
+  function setUp() {
+    parent::setUp('locale', 'locale_test');
+    include_once DRUPAL_ROOT . '/includes/locale.inc';
+    // Create and login user.
+    $admin_user = $this->drupalCreateUser(array('administer blocks', 'administer languages', 'translate interface', 'access administration pages'));
+    $this->drupalLogin($admin_user);
+  }
+
+  /**
+   * Test locale source APIs.
+   */
+  function testApis() {
+    // Insert an initial source string.
+    $source = (object)array(
+      'location'  => 'test',
+      'source'    => 'new',
+      'context'   => 'context',
+      'version'   => 'none',
+    );
+    $insert_result = locale_source_save($source);
+    $this->assertTrue($insert_result == SAVED_NEW, t('Correct value returned when new locale source string saved'));
+
+    // Update the initial source string after changing a property.
+    $source->source = 'changed';
+    $update_result = locale_source_save($source);
+    $this->assertTrue($update_result == SAVED_UPDATED, t('Correct value returned when new locale source string updated'));
+
+    // Load the source string.
+    $id_loaded_source = locale_source_load($source->lid);
+    $this->assertTrue(isset($id_loaded_source->lid) && $id_loaded_source->lid == $source->lid, t('Source loaded by ID'));
+    $this->assertTrue(isset($id_loaded_source->source) && $id_loaded_source->source == 'changed', t('Source updated'));
+
+    // Save a second source string.
+    $lids = array($source->lid);
+    unset($source->lid);
+    locale_source_save($source);
+    $lids[] = $source->lid;
+    // Load multiple strings.
+    $multiple = locale_source_load_multiple($lids);
+    $this->assertTrue(count($multiple) == 2, t('Multiple locale source strings loaded by ID'));
+
+    // Add language.
+    $edit = array(
+      'langcode' => 'fr',
+    );
+    $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language'));
+
+    // Create a locale target string.
+    $target = (object)array(
+      'lid'         => current($lids),
+      'translation' => 'analyse',
+      'language'    => 'fr',
+      'plural'      => 0,
+    );
+    $insert_result = locale_translation_save($target);
+    $this->assertTrue($insert_result == SAVED_NEW, t('Correct value returned when new locale target string saved'));
+
+    // Update the initial target string.
+    $target->translation = 'tester';
+    $update_result = locale_translation_save($target);
+    $this->assertTrue($update_result == SAVED_UPDATED, t('Correct value returned when new locale target string updated'));
+
+    // Load the target string.
+    $id_loaded_target = locale_translation_load($target->lid, $target->language);
+    $this->assertTrue(isset($id_loaded_target->lid) && $id_loaded_target->lid == $target->lid, t('Target loaded by ID'));
+    $this->assertTrue(isset($id_loaded_target->translation) && $id_loaded_target->translation == 'tester', t('Target updated'));
+
+    // Delete source and target strings.
+    locale_source_delete($lids);
+    $sources = locale_source_load_multiple($lids);
+    $this->assertTrue(empty($sources), t('Locale source strings deleted'));
+
+    $translations = locale_translation_load_multiple($lids);
+    $this->assertTrue(empty($translations), t('Locale target strings deleted'));
+  }
+}
+
+
+/**
  * Functional tests for the import of translation files.
  */
 class LocaleImportFunctionalTest extends DrupalWebTestCase {
-- 
1.7.5.3

