security update core+modules

This commit is contained in:
Bachir Soussi Chiadmi
2015-04-26 18:38:56 +02:00
parent 2f45ea820a
commit 7c96373038
1022 changed files with 30319 additions and 11259 deletions

View File

@@ -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);
}
}

View File

@@ -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"

View File

@@ -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
*/

View File

@@ -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.

View File

@@ -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;