security update core+modules
This commit is contained in:
@@ -28,7 +28,7 @@ class i18n_string_object {
|
||||
// Properties from metadata
|
||||
public $title;
|
||||
// Array of translations to multiple languages
|
||||
public $translations;
|
||||
public $translations = array();
|
||||
// Textgroup object
|
||||
protected $_textgroup;
|
||||
|
||||
@@ -39,7 +39,49 @@ class i18n_string_object {
|
||||
if ($data) {
|
||||
$this->set_properties($data);
|
||||
}
|
||||
// Attempt to re-build the data from the persistent cache.
|
||||
$this->rebuild_from_cache($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuild the object data based on the persistent cache.
|
||||
*
|
||||
* Since the textgroup defines if a string is cacheable or not the caching
|
||||
* of the string objects happens in the textgroup handler itself.
|
||||
*
|
||||
* @see i18n_string_textgroup_cached::__destruct()
|
||||
*/
|
||||
protected function rebuild_from_cache($data = NULL) {
|
||||
// Check if we've the required information to repopulate the cache and do so
|
||||
// if possible.
|
||||
$meta_data_exist = isset($this->textgroup) && isset($this->type) && isset($this->objectid) && isset($this->property);
|
||||
if ($meta_data_exist && ($cache = cache_get($this->get_cid())) && !empty($cache->data)) {
|
||||
// Re-spawn the cached data.
|
||||
// @TODO do we need a array_diff to ensure we don't overwrite the data
|
||||
// provided by the $data parameter?
|
||||
$this->set_properties($cache->data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset cache, needed for tests.
|
||||
*/
|
||||
public function cache_reset() {
|
||||
$this->translations = array();
|
||||
// Ensure a possible persistent cache of this object is cleared too.
|
||||
cache_clear_all($this->get_cid(), 'cache', TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the caching id for this object.
|
||||
*
|
||||
* @return string
|
||||
* The caching id.
|
||||
*/
|
||||
public function get_cid() {
|
||||
return 'i18n:string:obj:' . $this->get_name();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get message parameters from context and string.
|
||||
*/
|
||||
@@ -63,6 +105,10 @@ class i18n_string_object {
|
||||
$this->objectkey = (int)$this->objectid;
|
||||
// Remaining elements glued again with ':'
|
||||
$this->property = $parts ? implode(':', $parts) : '';
|
||||
|
||||
// Attempt to re-build the other data from the persistent cache.
|
||||
$this->rebuild_from_cache();
|
||||
|
||||
return $this;
|
||||
}
|
||||
/**
|
||||
@@ -100,13 +146,14 @@ class i18n_string_object {
|
||||
if (isset($string['format'])) {
|
||||
$this->format = $string['format'];
|
||||
}
|
||||
if (isset($string['title'])) {
|
||||
$this->title = $string['title'];
|
||||
}
|
||||
}
|
||||
else {
|
||||
$this->string = $string;
|
||||
}
|
||||
if (isset($string['title'])) {
|
||||
$this->title = $string['title'];
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
/**
|
||||
@@ -279,9 +326,9 @@ class i18n_string_textgroup_default {
|
||||
// Debug flag, set to true to print out more information.
|
||||
public $debug;
|
||||
// Cached or preloaded string objects
|
||||
public $strings;
|
||||
public $strings = array();
|
||||
// Multiple translations search map
|
||||
protected $cache_multiple;
|
||||
protected $cache_multiple = array();
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
@@ -400,8 +447,13 @@ class i18n_string_textgroup_default {
|
||||
/**
|
||||
* Filter array of strings
|
||||
*
|
||||
* @param $filter
|
||||
* @param array $string_list
|
||||
* Array of strings to be filtered.
|
||||
* @param array $filter
|
||||
* Array of name value conditions.
|
||||
*
|
||||
* @return array
|
||||
* Strings from $string_list that match the filter conditions.
|
||||
*/
|
||||
protected static function string_filter($string_list, $filter) {
|
||||
// Remove 'language' and '*' conditions.
|
||||
@@ -566,7 +618,11 @@ class i18n_string_textgroup_default {
|
||||
public function cache_reset() {
|
||||
$this->strings = array();
|
||||
$this->string_format = array();
|
||||
$this->translations = array();
|
||||
|
||||
// Reset the persistent caches.
|
||||
cache_clear_all('i18n:string:tgroup:' . $this->textgroup , 'cache', TRUE);
|
||||
// Reset the complete string object cache too.
|
||||
cache_clear_all('i18n:string:obj:', 'cache', TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -613,7 +669,7 @@ class i18n_string_textgroup_default {
|
||||
public static function load_translation($i18nstring, $langcode) {
|
||||
// Search the database using lid if we've got it or textgroup, context otherwise
|
||||
if (!empty($i18nstring->lid)) {
|
||||
// We've alreay got lid, we just need translation data
|
||||
// We've already got lid, we just need translation data
|
||||
$query = db_select('locales_target', 't');
|
||||
$query->condition('t.lid', $i18nstring->lid);
|
||||
}
|
||||
@@ -1188,6 +1244,10 @@ class i18n_string_object_wrapper extends i18n_object_wrapper {
|
||||
* Translate access (localize strings)
|
||||
*/
|
||||
protected function localize_access() {
|
||||
// We could check also whether the object has strings to translate:
|
||||
// && $this->get_strings(array('empty' => TRUE))
|
||||
// However it may be better to display the 'No available strings' message
|
||||
// for the user to have a clue of what's going on. See i18n_string_translate_page_object()
|
||||
return user_access('translate interface') && user_access('translate user-defined strings');
|
||||
}
|
||||
|
||||
@@ -1280,3 +1340,163 @@ class i18n_string_object_wrapper extends i18n_object_wrapper {
|
||||
return $this->textgroup()->load_strings(array('type' => $type, 'objectid' => $id));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Textgroup handler for i18n_string API which integrated persistent caching.
|
||||
*/
|
||||
class i18n_string_textgroup_cached extends i18n_string_textgroup_default {
|
||||
|
||||
/**
|
||||
* Defines the timeout for the persistent caching.
|
||||
* @var int
|
||||
*/
|
||||
public $caching_time = CACHE_TEMPORARY;
|
||||
|
||||
/**
|
||||
* Extends the existing constructor with a cache handling.
|
||||
*
|
||||
* @param string $textgroup
|
||||
* The name of this textgroup.
|
||||
*/
|
||||
public function __construct($textgroup) {
|
||||
parent::__construct($textgroup);
|
||||
// Fetch persistent caches, the persistent caches contain only metadata.
|
||||
// Those metadata are processed by the related cache_get() methods.
|
||||
foreach (array('cache_multiple', 'strings') as $caches_type) {
|
||||
if (($cache = cache_get('i18n:string:tgroup:' . $this->textgroup . ':' . $caches_type)) && !empty($cache->data)) {
|
||||
$this->{$caches_type} = $cache->data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class destructor.
|
||||
*
|
||||
* Updates the persistent caches for the next usage.
|
||||
* This function not only stores the data of the textgroup objects but also
|
||||
* of the string objects. That way we ensure that only cacheable string object
|
||||
* go into the persistent cache.
|
||||
*/
|
||||
public function __destruct() {
|
||||
// Reduce size to cache by removing NULL values.
|
||||
$this->strings = array_filter($this->strings);
|
||||
|
||||
|
||||
$strings_to_cache = array();
|
||||
// Store the persistent caches. We just store the metadata the translations
|
||||
// are stored by the string object itself. However storing the metadata
|
||||
// reduces the number of DB queries executed during runtime.
|
||||
$cache_data = array();
|
||||
foreach ($this->strings as $context => $i18n_string_object) {
|
||||
$cache_data[$context] = $context;
|
||||
$strings_to_cache[$context] = $i18n_string_object;
|
||||
}
|
||||
cache_set('i18n:string:tgroup:' . $this->textgroup . ':strings', $cache_data, 'cache', $this->caching_time);
|
||||
|
||||
$cache_data = array();
|
||||
foreach ($this->cache_multiple as $pattern => $strings) {
|
||||
foreach ($strings as $context => $i18n_string_object) {
|
||||
$cache_data[$pattern][$context] = $context;
|
||||
$strings_to_cache[$context] = $i18n_string_object;
|
||||
}
|
||||
}
|
||||
cache_set('i18n:string:tgroup:' . $this->textgroup . ':cache_multiple', $cache_data, 'cache', $this->caching_time);
|
||||
|
||||
// Cache the string objects related to this textgroup.
|
||||
// Store only the public visible data into the persistent cache.
|
||||
foreach ($strings_to_cache as $i18n_string_object) {
|
||||
// If this isn't an object it's an unprocessed cache item and doesn't need
|
||||
// to be stored again.
|
||||
if (is_object($i18n_string_object)) {
|
||||
cache_set($i18n_string_object->get_cid(), get_object_vars($i18n_string_object), 'cache', $this->caching_time);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset cache, needed for tests.
|
||||
*
|
||||
* Takes care of the persistent caches.
|
||||
*/
|
||||
public function cache_reset() {
|
||||
// Reset the persistent caches.
|
||||
cache_clear_all('i18n:string:tgroup:' . $this->textgroup , 'cache', TRUE);
|
||||
// Reset the complete string object cache too. This will affect string
|
||||
// objects of other textgroups as well.
|
||||
cache_clear_all('i18n:string:obj:', 'cache', TRUE);
|
||||
|
||||
return parent::cache_reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get translation from cache.
|
||||
*
|
||||
* Extends the original handler with persistent caching.
|
||||
*
|
||||
* @param string $context
|
||||
* The context to look out for.
|
||||
* @return i18n_string_object|NULL
|
||||
* The string object if available or NULL otherwise.
|
||||
*/
|
||||
protected function cache_get($context) {
|
||||
if (isset($this->strings[$context])) {
|
||||
// If the cache contains a string re-build i18n_string_object.
|
||||
if (is_string($this->strings[$context])) {
|
||||
$i8n_string_object = new i18n_string_object(array('textgroup' => $this->textgroup));
|
||||
$i8n_string_object->set_context($context);
|
||||
$this->strings[$context] = $i8n_string_object;
|
||||
}
|
||||
// Now run the original handling.
|
||||
return parent::cache_get($context);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get strings from multiple cache.
|
||||
*
|
||||
* @param $context array
|
||||
* String context as array with language property at the end.
|
||||
*
|
||||
* @return mixed
|
||||
* Array of strings (may be empty) if we've got a cache hit.
|
||||
* Null otherwise.
|
||||
*/
|
||||
protected function multiple_cache_get($context) {
|
||||
// Ensure the values from the persistent cache are properly re-build.
|
||||
$cache_key = implode(':', $context);
|
||||
if (isset($this->cache_multiple[$cache_key])) {
|
||||
foreach ($this->cache_multiple[$cache_key] as $cached_context) {
|
||||
if (is_string($cached_context)) {
|
||||
$i8n_string_object = new i18n_string_object(array('textgroup' => $this->textgroup));
|
||||
$i8n_string_object->set_context($cached_context);
|
||||
$this->cache_multiple[$cache_key][$cached_context] = $i8n_string_object;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Now we try more generic keys. For instance, if we are searching 'term:1:*'
|
||||
// we may try too 'term:*:*' and filter out the results.
|
||||
foreach ($context as $key => $value) {
|
||||
if ($value != '*') {
|
||||
$try = array_merge($context, array($key => '*'));
|
||||
return $this->multiple_cache_get($try);
|
||||
}
|
||||
}
|
||||
}
|
||||
return parent::multiple_cache_get($context);
|
||||
}
|
||||
|
||||
public function string_update($i18nstring, $options = array()) {
|
||||
// Flush persistent cache.
|
||||
cache_clear_all($i18nstring->get_cid(), 'cache', TRUE);
|
||||
return parent::string_update($i18nstring, $options);
|
||||
}
|
||||
|
||||
public function string_remove($i18nstring, $options = array()) {
|
||||
// Flush persistent cache.
|
||||
cache_clear_all($i18nstring->get_cid(), 'cache', TRUE);
|
||||
return parent::string_remove($i18nstring, $options);
|
||||
}
|
||||
}
|
||||
|
@@ -10,9 +10,9 @@ files[] = i18n_string.inc
|
||||
files[] = i18n_string.test
|
||||
configure = admin/config/regional/i18n/strings
|
||||
|
||||
; Information added by drupal.org packaging script on 2013-01-13
|
||||
version = "7.x-1.8"
|
||||
; Information added by Drupal.org packaging script on 2015-01-26
|
||||
version = "7.x-1.12"
|
||||
core = "7.x"
|
||||
project = "i18n"
|
||||
datestamp = "1358075001"
|
||||
datestamp = "1422286982"
|
||||
|
||||
|
@@ -28,6 +28,8 @@ function i18n_string_install() {
|
||||
i18n_string_update_7000();
|
||||
i18n_string_update_7001();
|
||||
}
|
||||
// Create new index in {locales_source}, performance improvement in sites with i18n.
|
||||
db_add_index('locales_source', 'textgroup_context', array('textgroup', 'context'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -36,6 +38,8 @@ function i18n_string_install() {
|
||||
function i18n_string_uninstall() {
|
||||
// Drop custom field.
|
||||
db_drop_field('locales_target', 'i18n_status');
|
||||
// Drop custom index in locales_source table
|
||||
db_drop_index('locales_source', 'textgroup_context');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -230,6 +234,14 @@ function i18n_string_update_7001() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new index in {locales_source}, performance improvement in sites with i18n.
|
||||
*/
|
||||
function i18n_string_update_7002() {
|
||||
db_add_index('locales_source', 'textgroup_context', array('textgroup', 'context'));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Notes for update script
|
||||
*/
|
||||
|
@@ -12,7 +12,14 @@ include_once DRUPAL_ROOT . '/includes/locale.inc';
|
||||
include_once drupal_get_path('module', 'locale') . '/locale.admin.inc';
|
||||
|
||||
/**
|
||||
* Generate translate page from object
|
||||
* Generate translate page from object.
|
||||
*
|
||||
* @param string $object_type
|
||||
* Obejct type as declared in hook_i18n_object_info().
|
||||
* @param object $object_value
|
||||
* Drupal object to translate.
|
||||
* @param object $language
|
||||
* Optional language object.
|
||||
*/
|
||||
function i18n_string_translate_page_object($object_type, $object_value, $language = NULL) {
|
||||
// For backwards compatibility, ensure parameter is a language object
|
||||
@@ -22,6 +29,13 @@ function i18n_string_translate_page_object($object_type, $object_value, $languag
|
||||
$object = i18n_object($object_type, $object_value);
|
||||
$strings = $object->get_strings(array('empty' => TRUE));
|
||||
|
||||
// If no localizable strings, print message and fail gracefully.
|
||||
// Possibly this object comes from some other contrib module.
|
||||
// See http://drupal.org/node/1889878
|
||||
if (!$strings) {
|
||||
return t('This object has no strings available for translation.');
|
||||
}
|
||||
|
||||
if (empty($langcode)) {
|
||||
drupal_set_title(t('Translate !name', array('!name' => i18n_object_info($object_type, 'title'))));
|
||||
return i18n_string_translate_page_overview($object, $strings);
|
||||
@@ -44,7 +58,6 @@ function i18n_string_translate_page_overview($object, $strings) {
|
||||
* Provide a core translation module like overview page for this object.
|
||||
*/
|
||||
function i18n_string_translate_page_overview_form($form, &$form_state, $object, $strings) {
|
||||
//include_once DRUPAL_ROOT . '/includes/language.inc';
|
||||
// Set the default item key, assume it's the first.
|
||||
$item_title = reset($strings);
|
||||
$header = array(
|
||||
@@ -385,6 +398,9 @@ function i18n_string_locale_translate_edit_form_submit($form, &$form_state) {
|
||||
// Invoke locale submission.
|
||||
locale_translate_edit_form_submit($form, $form_state);
|
||||
$lid = $form_state['values']['lid'];
|
||||
if ($i18n_string_object = i18n_string_get_by_lid($lid)) {
|
||||
$i18n_string_object->cache_reset();
|
||||
}
|
||||
foreach ($form_state['values']['translations'] as $key => $value) {
|
||||
if (!empty($value)) {
|
||||
// An update has been made, so we assume the translation is now current.
|
||||
|
@@ -24,7 +24,7 @@ class i18nStringTestCase extends Drupali18nTestCase {
|
||||
|
||||
function setUp() {
|
||||
// We can use any of the modules that define a text group, to use it for testing
|
||||
parent::setUp('i18n_string', 'i18n_menu');
|
||||
parent::setUp('i18n_string', 'i18n_menu', 'i18n_test');
|
||||
parent::setUpLanguages();
|
||||
$this->translator = $this->drupalCreateUser(array('translate interface', 'translate user-defined strings'));
|
||||
}
|
||||
@@ -55,6 +55,106 @@ class i18nStringTestCase extends Drupali18nTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test base i18n_string caching.
|
||||
*/
|
||||
function testCaching() {
|
||||
// Create a bunch of strings for all languages.
|
||||
$textgroup = 'test_cached';
|
||||
$strings = $this->stringCreateArray(2);
|
||||
$translations = array();
|
||||
$textgroup_object = i18n_string_textgroup($textgroup);
|
||||
// Save source strings and store translations.
|
||||
foreach ($strings as $key => $string) {
|
||||
$name = "$textgroup:item:$key:title";
|
||||
i18n_string_update($name, $string);
|
||||
$translations[$key] = $this->createStringTranslation($textgroup, $string);
|
||||
}
|
||||
|
||||
// Now fetch the strings to fill the cache.
|
||||
foreach ($textgroup_object->strings as $context => $string_object) {
|
||||
$this->drupalGet('tests/i18n/i18n_string_build/' . $textgroup . ':' . $context);
|
||||
}
|
||||
foreach ($strings as $key => $string) {
|
||||
$this->drupalGet('tests/i18n/i18n_string_translation_search/' . $textgroup . ':item:' . $key . ':*/es');
|
||||
}
|
||||
|
||||
// Check the persistent cache for contents.
|
||||
$cache = cache_get('i18n:string:tgroup:' . $textgroup . ':strings');
|
||||
if ($this->assertNotEqual($cache, FALSE, 'Textgroup strings cache found')) {
|
||||
foreach ($textgroup_object->strings as $context => $string_object) {
|
||||
if ($this->assertTrue(isset($cache->data[$context]), format_string('Cached string %context found', array('%context' => $context)))) {
|
||||
$this->assertEqual($cache->data[$context], $context, 'Cached string is a string and not an object');
|
||||
}
|
||||
// Check if the string object cache is also available.
|
||||
$string_cache = cache_get($string_object->get_cid());
|
||||
if ($this->assertNotEqual($string_cache, FALSE, format_string('Cached string object %cid found', array('%cid' => $string_object->get_cid())))) {
|
||||
$this->assertTrue(is_array($string_cache->data), 'Cached string object is an array.');
|
||||
}
|
||||
}
|
||||
}
|
||||
$cache = cache_get('i18n:string:tgroup:' . $textgroup . ':cache_multiple');
|
||||
if ($this->assertNotEqual($cache, FALSE, 'Textgroup cache_multiple cache found')) {
|
||||
foreach ($strings as $key => $string) {
|
||||
$pattern = 'item:' . $key . ':*:es';
|
||||
if ($this->assertTrue(isset($cache->data[$pattern]), format_string('Cached multiple cache for pattern %pattern found', array('%pattern_key' => $pattern)))) {
|
||||
$property_pattern = 'item:' . $key . ':title';
|
||||
if ($this->assertTrue(isset($cache->data[$pattern][$property_pattern]), format_string('Cached multiple property title found', array('%pattern_key' => $pattern)))) {
|
||||
$this->assertEqual($cache->data[$pattern][$property_pattern], $property_pattern);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test cache injection.
|
||||
foreach ($textgroup_object->strings as $context => $string_object) {
|
||||
// Check if the string object cache is also available.
|
||||
$string_cache = cache_get($string_object->get_cid());
|
||||
if (isset($string_cache->data)) {
|
||||
// Modify cache.
|
||||
$string_cache->data['string'] = "Injected value.";
|
||||
cache_set($string_object->get_cid(), $string_cache->data, 'cache', CACHE_TEMPORARY);
|
||||
|
||||
// Check if modification is reflected on the next page call.
|
||||
$this->drupalGet('tests/i18n/i18n_string_build/' . $textgroup . ':' . $context);
|
||||
$this->assertText($string_cache->data['string']);
|
||||
}
|
||||
}
|
||||
|
||||
// Test that un-translated strings are cached correctly.
|
||||
$textgroup = 'test_cached';
|
||||
$key = 3;
|
||||
$string = self::randomName(100);
|
||||
$name = "$textgroup:item:$key:title";
|
||||
i18n_string_update($name, $string);
|
||||
|
||||
// Generate the cache entry.
|
||||
$string_object = i18n_string_build($name, $string);
|
||||
$langcode = i18n_langcode();
|
||||
$string_object->get_translation($langcode);
|
||||
|
||||
// Destroy the textgroup object to write the cache entry.
|
||||
$textgroup_object = i18n_string_textgroup($textgroup);
|
||||
$textgroup_object->__destruct();
|
||||
$this->assertTrue(cache_get($string_object->get_cid()) !== FALSE, "Cache entry created.");
|
||||
drupal_static_reset('i18n_string_textgroup');
|
||||
|
||||
// Reset the loaded translation variable.
|
||||
variable_del('i18n_loaded_translations');
|
||||
$loaded_translations = variable_get('i18n_loaded_translations', array());
|
||||
$this->verbose(var_export($loaded_translations, TRUE));
|
||||
|
||||
// Rebuild the string.
|
||||
$string_object = i18n_string_build($name, $string);
|
||||
$string_object->get_translation($langcode);
|
||||
|
||||
// Check that the string hasn't been loaded.
|
||||
$loaded_translations = variable_get('i18n_loaded_translations', array());
|
||||
$this->verbose(var_export($loaded_translations, TRUE));
|
||||
$this->assertFalse(isset($loaded_translations['test_cached:item:3:title']), "The untranslated string was correctly cached.");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create strings for all languages
|
||||
*/
|
||||
@@ -76,7 +176,7 @@ class i18nStringTestCase extends Drupali18nTestCase {
|
||||
/**
|
||||
* Create and store one translation into the db
|
||||
*/
|
||||
public static function stringCreateTranslation($name, $lang, $length = 20) {
|
||||
public function stringCreateTranslation($name, $lang, $length = 20) {
|
||||
$translation = $this->randomName($length);
|
||||
if (self::stringSaveTranslation($name, $lang, $translation)) {
|
||||
return $translation;
|
||||
|
Reference in New Issue
Block a user