diff options
Diffstat (limited to 'sites/all/modules/l10n_update/includes/locale/StringDatabaseStorage.php')
-rw-r--r-- | sites/all/modules/l10n_update/includes/locale/StringDatabaseStorage.php | 518 |
1 files changed, 518 insertions, 0 deletions
diff --git a/sites/all/modules/l10n_update/includes/locale/StringDatabaseStorage.php b/sites/all/modules/l10n_update/includes/locale/StringDatabaseStorage.php new file mode 100644 index 000000000..d478b07b0 --- /dev/null +++ b/sites/all/modules/l10n_update/includes/locale/StringDatabaseStorage.php @@ -0,0 +1,518 @@ +<?php + +/** + * @file + * Definition of StringDatabaseStorage. + */ + +/** + * Defines the locale string class. + * + * This is the base class for SourceString and TranslationString. + */ +class StringDatabaseStorage implements StringStorageInterface { + + /** + * Additional database connection options to use in queries. + * + * @var array + */ + protected $options = array(); + + /** + * Constructs a new StringStorage controller. + * + * @param array $options + * (optional) Any additional database connection options to use in queries. + */ + public function __construct(array $options = array()) { + $this->options = $options; + } + + /** + * Implements StringStorageInterface::getStrings(). + */ + public function getStrings(array $conditions = array(), array $options = array()) { + return $this->dbStringLoad($conditions, $options, 'SourceString'); + } + + /** + * Implements StringStorageInterface::getTranslations(). + */ + public function getTranslations(array $conditions = array(), array $options = array()) { + return $this->dbStringLoad($conditions, array('translation' => TRUE) + $options, 'TranslationString'); + } + + /** + * Implements StringStorageInterface::findString(). + */ + public function findString(array $conditions) { + $values = $this->dbStringSelect($conditions) + ->execute() + ->fetchAssoc(); + + if (!empty($values)) { + $string = new SourceString($values); + $string->setStorage($this); + return $string; + } + } + + /** + * Implements StringStorageInterface::findTranslation(). + */ + public function findTranslation(array $conditions) { + $values = $this->dbStringSelect($conditions, array('translation' => TRUE)) + ->execute() + ->fetchAssoc(); + + if (!empty($values)) { + $string = new TranslationString($values); + $this->checkVersion($string, VERSION); + $string->setStorage($this); + return $string; + } + } + + /** + * Implements StringStorageInterface::countStrings(). + */ + public function countStrings() { + return $this->dbExecute("SELECT COUNT(*) FROM {locales_source}")->fetchField(); + } + + /** + * Implements StringStorageInterface::countTranslations(). + */ + public function countTranslations() { + return $this->dbExecute("SELECT t.language, COUNT(*) AS translated FROM {locales_source} s INNER JOIN {locales_target} t ON s.lid = t.lid GROUP BY t.language")->fetchAllKeyed(); + } + + /** + * Implements StringStorageInterface::save(). + */ + public function save($string) { + if ($string->isNew()) { + $result = $this->dbStringInsert($string); + if ($string->isSource() && $result) { + // Only for source strings, we set the locale identifier. + $string->setId($result); + } + $string->setStorage($this); + } + else { + $this->dbStringUpdate($string); + } + return $this; + } + + /** + * Checks whether the string version matches a given version, fix it if not. + * + * @param StringInterface $string + * The string object. + * @param string $version + * Drupal version to check against. + */ + protected function checkVersion($string, $version) { + if ($string->getId() && $string->getVersion() != $version) { + $string->setVersion($version); + db_update('locales_source', $this->options) + ->condition('lid', $string->getId()) + ->fields(array('version' => $version)) + ->execute(); + } + } + + /** + * Implements StringStorageInterface::delete(). + */ + public function delete($string) { + if ($keys = $this->dbStringKeys($string)) { + $this->dbDelete('locales_target', $keys)->execute(); + if ($string->isSource()) { + $this->dbDelete('locales_source', $keys)->execute(); + $this->dbDelete('locales_location', $keys)->execute(); + $string->setId(NULL); + } + } + else { + throw new StringStorageException(format_string('The string cannot be deleted because it lacks some key fields: @string', array( + '@string' => $string->getString() + ))); + } + return $this; + } + + /** + * Implements StringStorageInterface::deleteLanguage(). + */ + public function deleteStrings($conditions) { + $lids = $this->dbStringSelect($conditions, array('fields' => array('lid')))->execute()->fetchCol(); + if ($lids) { + $this->dbDelete('locales_target', array('lid' => $lids))->execute(); + $this->dbDelete('locales_source', array('lid' => $lids))->execute(); + $this->dbDelete('locales_location', array('sid' => $lids))->execute(); + } + } + + /** + * Implements StringStorageInterface::deleteLanguage(). + */ + public function deleteTranslations($conditions) { + $this->dbDelete('locales_target', $conditions)->execute(); + } + + /** + * Implements StringStorageInterface::createString(). + */ + public function createString($values = array()) { + return new SourceString($values + array('storage' => $this)); + } + + /** + * Implements StringStorageInterface::createTranslation(). + */ + public function createTranslation($values = array()) { + return new TranslationString($values + array( + 'storage' => $this, + 'is_new' => TRUE + )); + } + + /** + * Gets table alias for field. + * + * @param string $field + * Field name to find the table alias for. + * + * @return string + * Either 's', 't' or 'l' depending on whether the field belongs to source, + * target or location table table. + */ + protected function dbFieldTable($field) { + if (in_array($field, array('language', 'translation', 'customized'))) { + return 't'; + } + elseif (in_array($field, array('type', 'name'))) { + return 'l'; + } + else { + return 's'; + } + } + + /** + * Gets table name for storing string object. + * + * @param StringInterface $string + * The string object. + * + * @return string + * The table name. + */ + protected function dbStringTable($string) { + if ($string->isSource()) { + return 'locales_source'; + } + elseif ($string->isTranslation()) { + return 'locales_target'; + } + } + + /** + * Gets keys values that are in a database table. + * + * @param StringInterface $string + * The string object. + * + * @return array + * Array with key fields if the string has all keys, or empty array if not. + */ + protected function dbStringKeys($string) { + if ($string->isSource()) { + $keys = array('lid'); + } + elseif ($string->isTranslation()) { + $keys = array('lid', 'language'); + } + if (!empty($keys) && ($values = $string->getValues($keys)) && count($keys) == count($values)) { + return $values; + } + else { + return array(); + } + } + + /** + * Loads multiple string objects. + * + * @param array $conditions + * Any of the conditions used by dbStringSelect(). + * @param array $options + * Any of the options used by dbStringSelect(). + * @param string $class + * Class name to use for fetching returned objects. + * + * @return array + * Array of objects of the class requested. + */ + protected function dbStringLoad(array $conditions, array $options, $class) { + $strings = array(); + $result = $this->dbStringSelect($conditions, $options)->execute(); + foreach ($result as $item) { + $string = new $class($item); + $string->setStorage($this); + $strings[] = $string; + } + return $strings; + } + + /** + * Builds a SELECT query with multiple conditions and fields. + * + * The query uses both 'locales_source' and 'locales_target' tables. + * Note that by default, as we are selecting both translated and untranslated + * strings target field's conditions will be modified to match NULL rows too. + * + * @param array $conditions + * An associative array with field => value conditions that may include + * NULL values. If a language condition is included it will be used for + * joining the 'locales_target' table. + * @param array $options + * An associative array of additional options. It may contain any of the + * options used by StringStorageInterface::getStrings() and these additional + * ones: + * - 'translation', Whether to include translation fields too. Defaults to + * FALSE. + * @return SelectQuery + * Query object with all the tables, fields and conditions. + */ + protected function dbStringSelect(array $conditions, array $options = array()) { + // Change field 'customized' into 'l10n_status'. This enables the Drupal 8 + // backported code to work with the Drupal 7 style database tables. + if (isset($conditions['customized'])) { + $conditions['l10n_status'] = $conditions['customized']; + unset($conditions['customized']); + } + if (isset($options['customized'])) { + $options['l10n_status'] = $options['customized']; + unset($options['customized']); + } + // Start building the query with source table and check whether we need to + // join the target table too. + $query = db_select('locales_source', 's', $this->options) + ->fields('s'); + + // Figure out how to join and translate some options into conditions. + if (isset($conditions['translated'])) { + // This is a meta-condition we need to translate into simple ones. + if ($conditions['translated']) { + // Select only translated strings. + $join = 'innerJoin'; + } + else { + // Select only untranslated strings. + $join = 'leftJoin'; + $conditions['translation'] = NULL; + } + unset($conditions['translated']); + } + else { + $join = !empty($options['translation']) ? 'leftJoin' : FALSE; + } + + if ($join) { + if (isset($conditions['language'])) { + // If we've got a language condition, we use it for the join. + $query->$join('locales_target', 't', "t.lid = s.lid AND t.language = :langcode", array( + ':langcode' => $conditions['language'] + )); + unset($conditions['language']); + } + else { + // Since we don't have a language, join with locale id only. + $query->$join('locales_target', 't', "t.lid = s.lid"); + } + if (!empty($options['translation'])) { + // We cannot just add all fields because 'lid' may get null values. + $query->addField('t', 'language'); + $query->addField('t', 'translation'); + $query->addField('t', 'l10n_status', 'customized'); + } + } + + // If we have conditions for location's type or name, then we need the + // location table, for which we add a subquery. + if (isset($conditions['type']) || isset($conditions['name'])) { + $subquery = db_select('locales_location', 'l', $this->options) + ->fields('l', array('sid')); + foreach (array('type', 'name') as $field) { + if (isset($conditions[$field])) { + $subquery->condition('l.' . $field, $conditions[$field]); + unset($conditions[$field]); + } + } + $query->condition('s.lid', $subquery, 'IN'); + } + + // Add conditions for both tables. + foreach ($conditions as $field => $value) { + $table_alias = $this->dbFieldTable($field); + $field_alias = $table_alias . '.' . $field; + if (is_null($value)) { + $query->isNull($field_alias); + } + elseif ($table_alias == 't' && $join === 'leftJoin') { + // Conditions for target fields when doing an outer join only make + // sense if we add also OR field IS NULL. + $query->condition(db_or() + ->condition($field_alias, $value) + ->isNull($field_alias) + ); + } + else { + $query->condition($field_alias, $value); + } + } + + // Process other options, string filter, query limit, etc... + if (!empty($options['filters'])) { + if (count($options['filters']) > 1) { + $filter = db_or(); + $query->condition($filter); + } + else { + // If we have a single filter, just add it to the query. + $filter = $query; + } + foreach ($options['filters'] as $field => $string) { + $filter->condition($this->dbFieldTable($field) . '.' . $field, '%' . db_like($string) . '%', 'LIKE'); + } + } + + if (!empty($options['pager limit'])) { + $query = $query->extend('PagerDefault')->limit($options['pager limit']); + } + + return $query; + } + + /** + * Createds a database record for a string object. + * + * @param StringInterface $string + * The string object. + * + * @return bool|int + * If the operation failed, returns FALSE. + * If it succeeded returns the last insert ID of the query, if one exists. + * + * @throws StringStorageException + * If the string is not suitable for this storage, an exception ithrown. + */ + protected function dbStringInsert($string) { + if ($string->isSource()) { + $string->setValues(array('context' => '', 'version' => 'none'), FALSE); + $fields = $string->getValues(array('source', 'context', 'version')); + } + elseif ($string->isTranslation()) { + $string->setValues(array('customized' => 0), FALSE); + $fields = $string->getValues(array('lid', 'language', 'translation', 'customized')); + } + if (!empty($fields)) { + // Change field 'customized' into 'l10n_status'. This enables the Drupal 8 + // backported code to work with the Drupal 7 style database tables. + if (isset($fields['customized'])) { + $fields['l10n_status'] = $fields['customized']; + unset($fields['customized']); + } + + return db_insert($this->dbStringTable($string), $this->options) + ->fields($fields) + ->execute(); + } + else { + throw new StringStorageException(format_string('The string cannot be saved: @string', array( + '@string' => $string->getString() + ))); + } + } + + /** + * Updates string object in the database. + * + * @param StringInterface $string + * The string object. + * + * @return bool|int + * If the record update failed, returns FALSE. If it succeeded, returns + * SAVED_NEW or SAVED_UPDATED. + * + * @throws StringStorageException + * If the string is not suitable for this storage, an exception is thrown. + */ + protected function dbStringUpdate($string) { + if ($string->isSource()) { + $values = $string->getValues(array('source', 'context', 'version')); + } + elseif ($string->isTranslation()) { + $values = $string->getValues(array('translation', 'customized')); + } + if (!empty($values) && $keys = $this->dbStringKeys($string)) { + // Change field 'customized' into 'l10n_status'. This enables the Drupal 8 + // backported code to work with the Drupal 7 style database tables. + if (isset($keys['customized'])) { + $keys['l10n_status'] = $keys['customized']; + unset($keys['customized']); + } + if (isset($values['customized'])) { + $values['l10n_status'] = $values['customized']; + unset($values['customized']); + } + + return db_merge($this->dbStringTable($string), $this->options) + ->key($keys) + ->fields($values) + ->execute(); + } + else { + throw new StringStorageException(format_string('The string cannot be updated: @string', array( + '@string' => $string->getString() + ))); + } + } + + /** + * Creates delete query. + * + * @param string $table + * The table name. + * @param array $keys + * Array with object keys indexed by field name. + * + * @return DeleteQuery + * Returns a new DeleteQuery object for the active database. + */ + protected function dbDelete($table, $keys) { + $query = db_delete($table, $this->options); + // Change field 'customized' into 'l10n_status'. This enables the Drupal 8 + // backported code to work with the Drupal 7 style database tables. + if (isset($keys['customized'])) { + $keys['l10n_status'] = $keys['customized']; + unset($keys['customized']); + } + + foreach ($keys as $field => $value) { + $query->condition($field, $value); + } + return $query; + } + + /** + * Executes an arbitrary SELECT query string. + */ + protected function dbExecute($query, array $args = array()) { + return db_query($query, $args, $this->options); + } +} |