+/** + * Load a source string record. + * + * @param $conditions + * A source lid or an array of key => value pairs. + * @return + * Source string. + */ +function locale_source_load($conditions) { + $records = locale_source_load_multiple($conditions); + return !empty($records) ? current($records) : FALSE; +} + +/** + * Get source for a string, given a key and a value. + * + * @param $conditions + * A source lid, 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($conditions) { + if (is_numeric($conditions) || is_numeric(key($conditions))) { + $conditions = array('lid' => $conditions); + } + + $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. + * + * @param $string + * Object representing a source string. + */ +function locale_source_save(&$source) { + // Ensure we're working with an object. + $source = (object) $source; + // Invalidate cache if this text group is cacheable. + if (locale_textgroup($source->textgroup, 'cache')) { + variable_set('locale_rebuild_' . $source->textgroup, 1); + } + if (!empty($source->lid)) { + return drupal_write_record('locales_source', $source, 'lid'); + } + else { + return drupal_write_record('locales_source', $source); + } +} + +/** + * Delete one or more locale source records from the database. Optionally, also + * delete associated target strings. + * + * @param $conditions + * A single lid, 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($conditions, $delete_translations = TRUE) { + // Accept a single lid or an array of lids. + if (is_numeric($conditions) || is_numeric(key($conditions))) { + $conditions = array('lid' => $conditions); + } + // If requested and deleting by ID, remove all associated targets. + if ($delete_translations) { + locale_translation_delete($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 $conditions + * A source lid 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($conditions, $get_source = FALSE) { + $records = locale_translation_load_multiple($conditions, $get_source); + return !empty($records) ? current($records) : FALSE; +} + +/** + * Load one or more translations. + * + * @param $conditions + * A source lid or 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($conditions, $get_source = FALSE) { + // Accept a single lid or an array of lids. + if (is_numeric($conditions) || 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', array('lid', 'version')); + $join = 'leftJoin'; + } + else { + $join = 'join'; + } + $query->$join('locales_target', 't', 's.lid = t.lid AND t.language = :langcode', array(':langcode' => $conditions['language'])); + unset($conditions['language']); + $query->fields('t'); + foreach ($conditions as $field => $value) { + $query->condition('s.' . $field, $value, is_array($value) ? 'IN' : '='); + } + return $query->execute()->fetchAllAssoc('lid'); +} + +/** + * Save a translation. + * + * If the string has enough data and it doesn't have a string id, it will check + * and create the source too if it doesn't exist. + * + * @param $string + * Object representing a string translation. + * @return + * SAVED_NEW, SAVED_UPDATED, or FALSE on failure. + */ +function locale_translation_save(&$string) { + // If a current translation exists, just update the record, find it if not + // given. + if (empty($string->lid)) { + if (!empty($string->textgroup)) { + $key = locale_textgroup($string->textgroup, 'key'); + if (!empty($string->$key)) { + if ($source = locale_source_load(array('textgroup' => $string->textgroup, $key => $string->$key))) { + $string->lid = $source->lid; + } + elseif (!empty($string->source)) { + // Create source if it doesn't exist and we have enough data. + locale_source_save($string); + } + } + } + } + + // Check again for existing source and translation. + if (!empty($string->lid)) { + // Invalidate cache if this text group is cacheable. + if (!empty($string->textgroup) && locale_textgroup($string->textgroup, 'cache')) { + variable_set('locale_rebuild_' . $string->textgroup, 1); + } + $existing = locale_translation_load(array('lid' => $string->lid, 'language' => $string->language)); + if (!empty($existing)) { + return drupal_write_record('locales_target', $string, array('lid', 'language', 'plural')); + } + else { + return drupal_write_record('locales_target', $string); + } + } + else { + return FALSE; + } +} + +/** + * Delete one or more locale target records from the database. + * + * @param $conditions + * A single lid, 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($conditions) { + // Accept a single lid or an array of lids. + if (is_numeric($conditions) || 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, array('lid', 'translation', 'language', 'plural'))) { + $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->where('lid IN (' . (string)$subquery . ')', $subquery->getArguments()); + } + $query->execute(); +} + +/** + * @} End of "locale-api" + */ + +/** + * Tests for locale CRUD API functions. + */ +class LocaleApiTest extends DrupalWebTestCase { + 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 = new stdClass(); + $source->location = 'test'; + $source->textgroup = 'custom'; + $source->source = 'test'; + $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/settings/language/add', $edit, t('Add language')); + + // Create a locale target string. + $target = new stdClass(); + $target->lid = current($lids); + $target->translation = 'analyse'; + $target->language = 'fr'; + $insert_result = locale_translation_save($target); + // Awaiting http://drupal.org/node/369423. + // $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); + // Awaiting http://drupal.org/node/369423. + // $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(array('lid' => $target->lid, 'language' => $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')); + + // Load a translation. + $translation = tt($source->textgroup, $source->location, $source->source, array(), $target->language); + $this->assertTrue($translation == 'tester', t('Translation found with tt()')); + $translation = tt($source->textgroup, $source->location, $source->source, array(), 'es'); + $this->assertTrue($translation == $source->source, t('Original string returned when missing translation requested with tt()')); + + // Delete source and target strings. + locale_source_delete($lids); + $sources = locale_source_load(array('lid' => $lids)); + $this->assertTrue(empty($sources), t('Locale source strings deleted')); + } +} +