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

@@ -4,15 +4,13 @@ dependencies[] = locale
dependencies[] = variable
package = Multilingual - Internationalization
core = 7.x
files[] = i18n.install
files[] = i18n_object.inc
files[] = i18n.test
configure = admin/config/regional/i18n
; 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

@@ -22,12 +22,21 @@ function i18n_uninstall() {
}
/**
* Add fields to schema if they don't exist
* Add fields to the schema if they don't exist.
*
* @param string $table
* The name of the database table.
* @param array $fields
* The list of database fields to create.
* @param boolean $force_rebuild_schema
* Whether to force drupal_get_schema() to rebuild the schema. This may be
* necessary when additional implementations of hook_schema_alter() have
* become available since the schema was originally built.
*/
function i18n_install_create_fields($table, $fields) {
function i18n_install_create_fields($table, $fields, $force_rebuild_schema = FALSE) {
static $schema;
// Do not force schema refresh more than once per request.
$schema = drupal_get_schema($table, !isset($schema));
$rebuild_schema = !isset($schema) || $force_rebuild_schema;
$schema = drupal_get_schema($table, $rebuild_schema);
foreach ($fields as $field) {
if (!empty($schema['fields'][$field])) {
if (!db_field_exists($table, $field)) {

View File

@@ -403,9 +403,17 @@ function i18n_get_object($type, $key, $object = NULL) {
$index = is_array($key) ? implode(':', $key) : $key;
if (!isset($cache[$type][$index])) {
$class = i18n_object_info($type, 'class', 'i18n_object_wrapper');
$cache[$type][$index] = new $class($type, $key, $object);
$object_wrapper = new $class($type, $key, $object);
// Do not cache object with empty index.
if (!empty($index)) {
$cache[$type][$index] = $object_wrapper;
}
}
return $cache[$type][$index];
else {
$object_wrapper = $cache[$type][$index];
}
return $object_wrapper;
}
/**
@@ -498,9 +506,12 @@ function i18n_object_translate_access($type, $object) {
*
* @param $path
* Path to get translations for or '<front>' for front page.
* @param $check_access
* Whether to check access to paths, defaults to TRUE
*/
function i18n_get_path_translations($path) {
function i18n_get_path_translations($path, $check_access = TRUE) {
$translations = &drupal_static(__FUNCTION__);
if (!isset($translations[$path])) {
$translations[$path] = array();
foreach (module_implements('i18n_translate_path') as $module) {
@@ -510,10 +521,31 @@ function i18n_get_path_translations($path) {
$translations[$path] += $translated;
}
}
// Add access information if not there.
foreach ($translations[$path] as $langcode => &$info) {
if (!isset($info['access'])) {
$item = menu_get_item($info['href']);
// If no menu item, it may be an external URL, we allow access.
$info['access'] = $item ? !empty($item['access']) : TRUE;
}
}
// Chance for altering the results.
drupal_alter('i18n_translate_path', $translations[$path], $path);
}
return $translations[$path];
if ($check_access) {
return array_filter($translations[$path], '_i18n_get_path_translations_access');
}
else {
return $translations[$path];
}
}
/**
* Helper function to check access to path translation.
*/
function _i18n_get_path_translations_access($path) {
return $path['access'];
}
/**

View File

@@ -144,12 +144,14 @@ class Drupali18nTestCase extends DrupalWebTestCase {
}
/**
* Create a "Basic page" in the specified language.
*
* Create a node of the specified type in the specified language.
* @param $type
* The node type.
* @param $title
* Title of basic page in specified language.
* Title of node in specified language.
* @param $body
* Body of basic page in specified language.
* Body of node in specified language.
* @param $langcode
* Language code.
*/
@@ -159,7 +161,9 @@ class Drupali18nTestCase extends DrupalWebTestCase {
$edit["body[$lang][0][value]"] = $body;
$edit['language'] = $langcode;
$this->drupalPost('node/add/' . $type, $edit, t('Save'));
$this->assertRaw(t('Basic @type %title has been created.', array('@type' => $type, '%title' => $title)), t('Basic page created.'));
$info = node_type_load($type);
$message = t('@name %title has been created.', array('@name' => $info->name, '%title' => $title));
$this->assertRaw($message);
// Check to make sure the node was created.
$node = $this->drupalGetNodeByTitle($title);
@@ -169,15 +173,14 @@ class Drupali18nTestCase extends DrupalWebTestCase {
}
/**
* Create a translation for the specified basic page in the specified
* language.
* Create a translation for the specified node in the specified language.
*
* @param $node
* The basic page to create translation for.
* @param $title
* Title of basic page in specified language.
* Title of node in specified language.
* @param $body
* Body of basic page in specified language.
* Body of node in specified language.
* @param $language
* Language code.
*/
@@ -196,8 +199,9 @@ class Drupali18nTestCase extends DrupalWebTestCase {
$edit["title"] = $title;
$edit[$body_key] = $body;
$this->drupalPost(NULL, $edit, t('Save'));
$this->assertRaw(t('Basic page %title has been created.', array('%title' => $title)), t('Translation created.'));
$info = node_type_load($node->type);
$message = t('@name %title has been created.', array('@name' => $info->name, '%title' => $title));
$this->assertRaw($message);
// Check to make sure that translation was successful.
$translation = $this->drupalGetNodeByTitle($title);
$this->assertTrue($translation, t('Node found in database.'));

View File

@@ -8,9 +8,9 @@ files[] = i18n_block.inc
files[] = i18n_block.test
; 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

@@ -59,7 +59,7 @@ function i18n_block_menu_alter(&$items) {
*/
function i18n_block_translate_tab_access($module, $delta) {
$block = block_load($module, $delta);
return user_access('translate interface') && isset($block) && ($block->i18n_mode == I18N_MODE_LOCALIZE);
return user_access('translate interface') && $block && isset($block->i18n_mode) && ($block->i18n_mode == I18N_MODE_LOCALIZE);
}
/**

View File

@@ -5,9 +5,9 @@ dependencies[] = i18n_string
package = Multilingual - Internationalization
core = 7.x
; 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

@@ -0,0 +1,53 @@
<?php
/**
* @file
* API documentation file for Field translation module.
*
* This module takes care of translating common field elements like title and
* description for all fields, plus some field specific values (default, options)
* for field types defined by Drupal core.
*
* Before implementing any of these hooks, consider whether you would be better
* off implementing Drupal core's hook_field_widget_form_alter().
*
* @see i18n_field_field_widget_form_alter()
*/
/**
* Provide information about callbacks for translating specific field types.
*
* This information can be retrieved using i18n_field_type_info().
* @return
* Array of values indexed by field type. Valid keys are:
* - 'translate_default', Callback for translating the default value for this field type.
* - 'translate_options', Callback for translating options for this field type.
*
* @see i18n_field_type_info()
* @see i18n_field_i18n_field_info()
*
* For examples of both callback types:
*
* @see i18n_field_translate_allowed_values()
* @see i18n_field_translate_default()
*
*/
function hook_i18n_field_info() {
$info['text'] = $info['text_long'] = $info['text_with_summary'] = array(
'translate_default' => 'i18n_field_translate_default',
);
$info['list_text'] = $info['list_boolean'] = $info['list_integer'] = array(
'translate_options' => 'i18n_field_translate_allowed_values',
);
return $info;
}
/**
* Alter information provided by hook_i18n_field_info().
*
* @see i18n_field_type_info()
*/
function hook_i18n_field_info_alter(&$info) {
// Unset the default callback for text fields.
unset($info['text']['translate_default']);
}

View File

@@ -72,6 +72,7 @@ function i18n_field_i18n_string_info() {
'description' => t('Configurable fields descriptions, defaults, options, etc.'),
'format' => FALSE, // This group doesn't have formatted strings
'list' => TRUE, // This group can list all strings
'class' => 'i18n_string_textgroup_cached',
);
return $groups;
}

View File

@@ -6,9 +6,9 @@ package = Multilingual - Internationalization
core = 7.x
files[] = i18n_field.inc
files[] = i18n_field.test
; 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

@@ -163,7 +163,6 @@ function i18n_field_field_formatter_view($entity_type, $entity, $field, $instanc
return $element;
}
/**
* Implements hook_field_widget_form_alter().
*
@@ -176,8 +175,8 @@ function i18n_field_field_formatter_view($entity_type, $entity, $field, $instanc
function i18n_field_field_widget_form_alter(&$element, &$form_state, $context) {
global $language;
// Skip the node type edit fields by checking for existing entity
if (empty($element['#entity'])) {
// Don't translate if the widget is being shown on the field edit form.
if ($form_state['build_info']['form_id'] == 'field_ui_field_edit_form') {
return;
}
@@ -189,8 +188,38 @@ function i18n_field_field_widget_form_alter(&$element, &$form_state, $context) {
$instance = $context['instance'];
$langcode = $context['langcode'];
// Get the element to alter. Account for inconsistencies in how the element
// is built for different field types.
if (isset($element[0]) && count($element) == 1) {
// Single-value file fields and image fields.
$alter_element = &$element[0];
}
elseif (isset($element['value'])) {
// Number fields. Single-value text fields.
$alter_element = &$element['value'];
}
elseif ($field['type'] == 'entityreference' && isset($element['target_id'])) {
// Entityreference fields using the entityreference_autocomplete widget.
$alter_element = &$element['target_id'];
}
else {
// All other fields.
$alter_element = &$element;
}
// If a subelement has the same title as the parent, translate it instead.
// Allows fields such as email and commerce_price to be translated.
foreach (element_get_visible_children($element) as $key) {
$single_value = ($field['cardinality'] == 1);
$has_title = (isset($element['#title']) && isset($element[$key]['#title']));
if ($single_value && $has_title && $element[$key]['#title'] == $element['#title']) {
$alter_element = &$element[$key];
break;
}
}
// The field language may affect some variables (default) but not others (description will be in current page language)
$i18n_langcode = empty($element['#language']) || $element['#language'] == LANGUAGE_NONE ? $language->language : $element['#language'];
$i18n_langcode = empty($alter_element['#language']) || $alter_element['#language'] == LANGUAGE_NONE ? $language->language : $alter_element['#language'];
// Translate instance to current page language and set to form_state
// so it will be used for validation messages later.
@@ -200,37 +229,60 @@ function i18n_field_field_widget_form_alter(&$element, &$form_state, $context) {
}
// Translate field title if set and it is the default one.
// When cardinality is 1, $element['value'] is used instead.
if (!empty($instance_current['label']) && $instance_current['label'] != $instance['label']) {
if (!empty($element['#title']) && $element['#title'] == $instance['label']) {
$element['#title'] = $instance_current['label'];
}
if (isset($element['value']) && !empty($element['value']['#title']) && $element['value']['#title'] == $instance['label']) {
$element['value']['#title'] = $instance_current['label'];
if (!empty($alter_element['#title']) && $alter_element['#title'] == check_plain($instance['label'])) {
$alter_element['#title'] = check_plain($instance_current['label']);
}
}
// Translate field description if set and it is the default one.
// When cardinality is 1, $element['value'] is used instead.
if (!empty($instance_current['description']) && $instance_current['description'] != $instance['description']) {
if (!empty($element['#description']) && $element['#description'] == $instance['description']) {
$element['#description'] = $instance_current['description'];
}
if (isset($element['value']) && !empty($element['value']['#description']) && $element['value']['#description'] == $instance['description']) {
$element['value']['#description'] = $instance_current['description'];
if (!empty($alter_element['#description'])) {
// Allow single-value file fields and image fields to have their
// descriptions translated. file_field_widget_form() passes the
// description through theme('file_upload_help'), so i18n_field
// must do the same.
$filefield = in_array($field['type'], array('file', 'image'));
$single_value = ($field['cardinality'] == 1);
$no_default = empty($alter_element['#default_value']['fid']);
if ($filefield && $single_value && $no_default) {
$help_variables = array(
'description' => field_filter_xss($instance['description']),
'upload_validators' => isset($alter_element['#upload_validators']) ? $alter_element['#upload_validators'] : array(),
);
$original_description = theme('file_upload_help', $help_variables);
if ($alter_element['#description'] == $original_description) {
$help_variables = array(
'description' => field_filter_xss($instance_current['description']),
'upload_validators' => isset($alter_element['#upload_validators']) ? $alter_element['#upload_validators'] : array(),
);
$alter_element['#description'] = theme('file_upload_help', $help_variables);
}
}
elseif ($alter_element['#description'] == field_filter_xss($instance['description'])) {
$alter_element['#description'] = field_filter_xss($instance_current['description']);
}
}
}
// Translate list options
if (!empty($element['#options']) && ($translate = i18n_field_type_info($field['type'], 'translate_options')) && !empty($field['settings']['allowed_values'])) {
$element['#options'] = $translate($field, $i18n_langcode);
if (isset($element['#properties']) && !empty($element['#properties']['empty_option'])) {
$label = theme('options_none', array('instance' => $instance, 'option' => $element['#properties']['empty_option']));
$element['#options'] = array('_none' => $label) + $element['#options'];
// For some elements, change title to new translated option
if (!empty($element['#title']) && $field['type'] == 'list_boolean' && !empty($element['#on_value'])) {
$on_value = $element['#on_value'];
$element['#title'] = $element['#options'][$on_value];
// Translate list options.
$has_options = (!empty($alter_element['#options']) || $field['type'] == 'list_boolean');
$has_allowed_values = !empty($field['settings']['allowed_values']);
$translate = i18n_field_type_info($field['type'], 'translate_options');
if ($has_options && $has_allowed_values && $translate) {
$alter_element['#options'] = $translate($field, $i18n_langcode);
if (isset($alter_element['#properties']) && !empty($alter_element['#properties']['empty_option'])) {
$label = theme('options_none', array('instance' => $instance, 'option' => $alter_element['#properties']['empty_option']));
$alter_element['#options'] = array('_none' => $label) + $alter_element['#options'];
}
// Translate list_boolean fields using the checkboxes widget.
if (!empty($alter_element['#title']) && $field['type'] == 'list_boolean' && !empty($alter_element['#on_value'])) {
$on_value = $alter_element['#on_value'];
$alter_element['#options'];
$alter_element['#title'] = $alter_element['#options'][$on_value];
// For using label instead of "On value".
if ($instance['widget']['settings']['display_label']) {
$alter_element['#title'] = $instance_current['label'];
}
}
}
@@ -242,11 +294,15 @@ function i18n_field_field_widget_form_alter(&$element, &$form_state, $context) {
$delta = $context['delta'];
$items = $context['items'];
// Translate default value if exists and the current value is the default
if (isset($element['value']['#default_value']) && ($translate = i18n_field_type_info($field['type'], 'translate_default')) &&
!empty($instance['default_value'][$delta]['value']) && !empty($items[$delta]['value']) &&
$instance['default_value'][$delta]['value'] === $items[$delta]['value']) {
$element['value']['#default_value'] = $translate($instance, $items[$delta]['value'], $i18n_langcode);
// Translate default value.
$has_default_value = (isset($alter_element['#default_value']) && !empty($instance['default_value'][$delta]['value']));
$storage_has_value = !empty($items[$delta]['value']);
$translate = i18n_field_type_info($field['type'], 'translate_default');
if ($has_default_value && $storage_has_value && $translate) {
// Compare the default value with the value currently in storage.
if ($instance['default_value'][$delta]['value'] === $items[$delta]['value']) {
$alter_element['#default_value'] = $translate($instance, $items[$delta]['value'], $i18n_langcode);
}
}
}
@@ -344,10 +400,12 @@ function i18n_field_translate_allowed_values($field, $langcode = NULL) {
}
/**
* Translate field default
* Translate field default.
*/
function i18n_field_translate_default($instance, $value, $langcode = NULL) {
return i18n_string_translate(array('field', $instance['field_name'], $instance['bundle'], 'default_value'), $value, array('langcode' => $langcode));
// The default value does not need sanitizing in a text_textfield widget.
$sanitize = !($instance['widget']['type'] == 'text_textfield' && $instance['widget']['module'] == 'text');
return i18n_string_translate(array('field', $instance['field_name'], $instance['bundle'], 'default_value'), $value, array('langcode' => $langcode, 'sanitize' => $sanitize));
}
/**
@@ -360,7 +418,17 @@ function i18n_field_translate_property($instance, $property, $langcode = NULL) {
}
/**
* Get i18n information for fields
* Get i18n information for translating fields.
*
* @param $type
* Optional field type.
* @param $property
* Optional property to get from field type.
*
* @return
* - The property for the field if $type and $property set.
* - Array of properties for the field type if only $type is set.
* - Array of translation information for all field types.
*/
function i18n_field_type_info($type = NULL, $property = NULL) {
$info = &drupal_static(__FUNCTION__);

View File

@@ -7,9 +7,9 @@ package = Multilingual - Internationalization
core = 7.x
files[] = i18n_forum.test
; 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

@@ -35,7 +35,7 @@ function i18n_forum_menu_local_tasks_alter(&$data, $router_item, $root_path) {
if ($vid && ($vocabulary = taxonomy_vocabulary_load($vid)) && ($field = field_info_field('taxonomy_' . $vocabulary->machine_name))) {
foreach ($field['bundles']['node'] as $type) {
if (isset($data['actions']['output'][$type])) {
$data['actions']['output'][$type]['#link']['title'] = t('Add new @node_type', array('@node_type' => i18n_node_type_name($type, node_type_get_name($type))));
$data['actions']['output'][$type]['#link']['title'] = t('Add new @node_type', array('@node_type' => i18n_node_type_name($type)));
}
}
}

View File

@@ -10,7 +10,7 @@
* Produces a menu translation form.
*/
function i18n_menu_translation_form($form, $form_state, $translation_set = NULL, $item = NULL) {
$translation_set = $translation_set ? $translation_set : i18n_translation_set_create('menu_link');
$translation_set = $translation_set ? $translation_set : i18n_translation_set_build('menu_link');
$form['translation_set'] = array('#type' => 'value', '#value' => $translation_set);
$translations = $translation_set->get_translations();
// What to do with title? Let's make it a hidden field for now, some tests relay on it
@@ -55,7 +55,7 @@ function i18n_menu_translation_form($form, $form_state, $translation_set = NULL,
}
$form['actions'] = array('#type' => 'actions');
$form['actions']['update'] = array('#type' => 'submit', '#value' => t('Save'));
if ($translation_set) {
if (!empty($translation_set->tsid)) {
$form['actions']['delete'] = array('#type' => 'submit', '#value' => t('Delete'));
}
return $form;
@@ -244,12 +244,16 @@ function i18n_menu_translation_item_overview($item, $translation_set = NULL) {
$title = t('n/a');
$options[] = l(t('add translation'), 'admin/structure/menu/manage/' . $item['menu_name'] . '/add', array('query' => array('translation' => $item['mlid'], 'target' => $langcode) + drupal_get_destination()));
}
$rows[] = array($language_name, $title, implode(" | ", $options));
$rows[$langcode] = array(
'language' => $language_name,
'title' => $title,
'operations' => implode(" | ", $options)
);
}
drupal_set_title(t('Translations of menu item %title', array('%title' => $item['link_title'])), PASS_THROUGH);
$build['translation_item_overview'] = array(
$build['translation_overview'] = array(
'#theme' => 'table',
'#header' => $header,
'#rows' => $rows,

View File

@@ -123,8 +123,8 @@ function i18n_menu_item_translation_page($type, $item) {
module_load_include('admin.inc', 'i18n_menu');
// If the item has a language code, we can only support translation sets.
$translation_set = !empty($item['i18n_tsid']) ? i18n_translation_set_load($item['i18n_tsid']) : NULL;
$build['overview'] = i18n_menu_translation_item_overview($item, $translation_set);
$build['translation_form'] = drupal_get_form('i18n_menu_translation_form', $translation_set, $item);
return $build;
$overview = i18n_menu_translation_item_overview($item, $translation_set);
$translation_form = drupal_get_form('i18n_menu_translation_form', $translation_set, $item);
return $overview + $translation_form;
}

View File

@@ -69,6 +69,7 @@ class i18n_menu_link extends i18n_string_object_wrapper {
return I18N_MODE_NONE;
}
}
/**
* Access to object translation. This should check object properties and permissions
*/

View File

@@ -10,9 +10,9 @@ core = 7.x
files[] = i18n_menu.inc
files[] = i18n_menu.test
; 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

@@ -343,6 +343,8 @@ function i18n_menu_variable_info_alter(&$variables, $options) {
// Make menu variables translatable
$variables['menu_main_links_source']['localize'] = TRUE;
$variables['menu_secondary_links_source']['localize'] = TRUE;
$variables['menu_parent_[node_type]']['localize'] = TRUE;
$variables['menu_options_[node_type]']['localize'] = TRUE;
}
/**
@@ -468,7 +470,8 @@ function i18n_menu_navigation_links($menu_name, $level = 0) {
* Get localized menu title
*/
function _i18n_menu_link_title($link, $langcode = NULL) {
return i18n_string_translate(array('menu', 'item', $link['mlid'], 'title'), $link['link_title'], array('langcode' => $langcode, 'sanitize' => FALSE));
$key = i18n_object_info('menu_link', 'key');
return i18n_string_translate(array('menu', 'item', $link[$key], 'title'), $link['link_title'], array('langcode' => $langcode, 'sanitize' => FALSE));
}
/**
@@ -500,7 +503,8 @@ function _i18n_menu_link_localize(&$link, $langcode = NULL) {
*/
function _i18n_menu_link_description($link, $langcode = NULL) {
if (!empty($link['options']['attributes']['title'])) {
return i18n_string_translate(array('menu', 'item', $link['mlid'], 'description'), $link['options']['attributes']['title'], array('langcode' => $langcode));
$key = i18n_object_info('menu_link', 'key');
return i18n_string_translate(array('menu', 'item', $link[$key], 'description'), $link['options']['attributes']['title'], array('langcode' => $langcode));
}
else {
return NULL;
@@ -525,6 +529,10 @@ function _i18n_menu_link_process(&$link) {
return FALSE;
}
}
// Skip if administering this menu item through the node edit form.
elseif (arg(0) == 'node' && arg(2) == 'edit' && $link['link_path'] == arg(0) . '/' . arg(1)) {
return FALSE;
}
return TRUE;
}
else {
@@ -709,6 +717,26 @@ function i18n_menu_form_menu_edit_item_alter(&$form, &$form_state) {
array_unshift($form['#validate'], 'i18n_menu_menu_item_prepare_normal_path');
}
/**
* Implements hook_form_FORM_ID_alter().
* FORM_ID = menu-overview-form.
* Add a "translate" link in operations column for each menu item.
*/
function i18n_menu_form_menu_overview_form_alter(&$form, &$form_state) {
foreach (element_children($form) as $element) {
if (substr($element, 0, 5) == 'mlid:') {
$mlid = $form[$element]['#item']['mlid'];
if (i18n_get_object('menu', $mlid)->get_translate_access()) {
$form[$element]['operations']['translate'] = array(
'#type' => 'link',
'#title' => t('translate'),
'#href' => "admin/structure/menu/item/{$mlid}/translate",
);
}
}
}
}
/**
* Normal path should be checked with menu item's language to avoid
* troubles when a node and it's translation has the same url alias.
@@ -727,6 +755,9 @@ function i18n_menu_item_get_language($item) {
}
else {
$menu = menu_load($item['menu_name']);
if (!isset($menu['i18n_mode'])) {
return LANGUAGE_NONE;
}
switch ($menu['i18n_mode']) {
case I18N_MODE_LANGUAGE:
return $menu['language'];
@@ -858,6 +889,14 @@ function i18n_menu_link_load($path, $langcode) {
}
}
/**
* Implements hook_query_TAG_alter() for features_menu_links.
* Add needed fields to properly serialize localization information.
*/
function i18n_menu_query_features_menu_link_alter($query) {
$query->fields('menu_links', array('language', 'customized'));
}
/**
* Implements hook_init().
*/

View File

@@ -130,6 +130,8 @@ class i18nMenuTestCase extends Drupali18nTestCase {
$this->drupalGet('admin/structure/menu/item/' . $node->menu['mlid'] . '/edit');
$this->assertText(t('This menu item belongs to a node, so it will have the same language as the node and cannot be localized.'));
$this->assertNoField('language', 'We cannot edit language for menu items that belong to nodes.');
// Edit the node and submit it to see if the menu link stays enabled.
$this->drupalPost('node/' . $node->nid . '/edit', array(), t('Save'));
}
// Check menu items show up for the right language.
$this->drupalGet('<front>');

View File

@@ -9,9 +9,9 @@ configure = admin/config/regional/i18n/node
files[]=i18n_node.test
files[]=i18n_node.variable.inc
; 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

@@ -54,7 +54,7 @@ function i18n_node_block_view_system_help_alter(&$block) {
$arg = drupal_help_arg(arg(NULL));
if ($arg[0] == 'node' && $arg[1] == 'add' && $arg[2]) {
if (($type = node_type_get_type(str_replace('-', '_', $arg[2]))) && !empty($type->help)) {
$help = i18n_node_translate_type($type, 'help');
$help = i18n_node_translate_type($type, 'help', NULL, array('sanitize' => FALSE));
if ($help !== $type->help) {
$block['content'] = str_replace(filter_xss_admin($type->help), filter_xss_admin($help), $block['content']);
}
@@ -64,7 +64,7 @@ function i18n_node_block_view_system_help_alter(&$block) {
$node = menu_get_object();
if ($node && isset($node->type)) {
$type = node_type_get_type($node->type);
$help = i18n_node_translate_type($type, 'help');
$help = i18n_node_translate_type($type, 'help', NULL, array('sanitize' => FALSE));
if ($help !== $type->help) {
$block['content'] = str_replace(filter_xss_admin($type->help), filter_xss_admin($help), $block['content']);
}
@@ -121,6 +121,8 @@ function i18n_node_i18n_translate_path($path) {
'href' => 'node/' . $node_translation->nid . $matches[2],
'title' => $node_translation->title,
'object' => $node_translation,
// Performance: for node view add access information right away.
'access' => !$matches[2] ? $node_translation->status : NULL,
);
}
return $result;
@@ -347,7 +349,7 @@ function i18n_node_textfield_process($element) {
static $sent = FALSE;
// Ensure we send the Javascript only once.
if (!$sent && isset($element['#autocomplete_path']) && !empty($element['#autocomplete_path']) && variable_get('language_negotiation', LANGUAGE_NEGOTIATION_NONE) != LANGUAGE_NEGOTIATION_NONE) {
if (!$sent && isset($element['#autocomplete_path']) && !empty($element['#autocomplete_path']) && variable_get('language_negotiation', LANGUAGE_NEGOTIATION_DEFAULT) != LANGUAGE_NEGOTIATION_DEFAULT) {
// Add a JS file for node forms.
// Determine if we are either editing or translating an existing node.
// We can't act on regular node creation because we don't have a specified
@@ -442,7 +444,7 @@ function i18n_node_form_node_form_alter(&$form, $form_state) {
*
* @see http://drupal.org/node/765860
*/
// Replace core's node submit callback with our own,
// in order to translate the node type name.
$key = array_search('node_form_submit', $form['actions']['submit']['#submit']);
@@ -464,8 +466,7 @@ function i18n_node_form_submit($form, &$form_state) {
$insert = empty($node->nid);
node_save($node);
$node_link = l(t('view'), 'node/' . $node->nid);
$type = node_type_get_type($node->type);
$type_name = i18n_node_translate_type($type);
$type_name = i18n_node_type_name($node->type);
$watchdog_args = array('@type' => $node->type, '%title' => $node->title);
$t_args = array('@type' => $type_name, '%title' => $node->title);
@@ -481,7 +482,7 @@ function i18n_node_form_submit($form, &$form_state) {
if ($node->nid) {
$form_state['values']['nid'] = $node->nid;
$form_state['nid'] = $node->nid;
$form_state['redirect'] = 'node/' . $node->nid;
$form_state['redirect'] = node_access('view', $node) ? 'node/' . $node->nid : '<front>';
}
else {
// In the unlikely case something went wrong on save, the node will be
@@ -516,12 +517,11 @@ function _i18n_node_form_node_form_alter($form, &$form_state) {
$form['body_field']['body']['#title'] = i18n_node_translate_type($node->type, 'body', $form['body_field']['body']['#title']);
}
// Translate page title for node/add/% and node/%/edit pages.
$types = node_type_get_types();
if (empty($node->nid) && strpos($_GET['q'], 'node/add/' . str_replace('_', '-', $node->type)) === 0) {
drupal_set_title(t('Create @name', array('@name' => i18n_node_translate_type($types[$node->type], 'name'))), PASS_THROUGH);
drupal_set_title(t('Create @name', array('@name' => i18n_node_type_name($node->type))), PASS_THROUGH);
}
elseif (!empty($node->nid) && $_GET['q'] == 'node/' . $node->nid . '/edit') {
drupal_set_title(t('<em>Edit @type</em> @title', array('@type' => i18n_node_translate_type($types[$node->type], 'name'), '@title' => $node->title)), PASS_THROUGH);
drupal_set_title(t('<em>Edit @type</em> @title', array('@type' => i18n_node_type_name($node->type), '@title' => $node->title)), PASS_THROUGH);
}
return $form;
}
@@ -553,10 +553,16 @@ function i18n_node_translate_type($type, $property = 'name', $source = NULL, $op
}
/**
* Translate node type name (unfiltered)
* Get translated node type name (unfiltered)
*
* @param string $type
* Node type.
* @param string $name
* Optional node type name.
*/
function i18n_node_type_name($type, $name) {
return i18n_string_translate(array('node', 'type', $type, 'name'), $name);
function i18n_node_type_name($type, $name = NULL) {
$name = isset($name) ? $name : node_type_get_name($type);
return i18n_string_translate(array('node', 'type', $type, 'name'), $name, array('sanitize' => FALSE));
}
/**

View File

@@ -25,7 +25,7 @@ function i18n_node_add_page() {
if ($type) {
// We just need to translate the description, the title is translated by the menu system
// The string will be filtered (xss_admin) on the theme layer
$item['description'] = i18n_node_translate_type($type, 'description', $item['description']);
$item['description'] = i18n_node_translate_type($type, 'description', $item['description'], array('sanitize' => FALSE));
}
}
return theme('node_add_list', array('content' => $content));
@@ -213,6 +213,7 @@ function i18n_node_select_translation_submit($form, &$form_state) {
))
->condition('nid', $add)
->execute();
entity_get_controller('node')->resetCache($add);
if (count($new)) {
drupal_set_message(format_plural(count($new), 'Added a node to the translation set.', 'Added @count nodes to the translation set.'));
}
@@ -224,6 +225,7 @@ function i18n_node_select_translation_submit($form, &$form_state) {
))
->condition('nid', $remove)
->execute();
entity_get_controller('node')->resetCache($remove);
drupal_set_message(format_plural(count($remove), 'Removed a node from the translation set.', 'Removed @count nodes from the translation set.'));
}
}
@@ -246,7 +248,7 @@ function i18n_node_autocomplete($type, $language, $string = '') {
*/
function i18n_node_nid2autocomplete($nid) {
if ($node = node_load($nid)) {
return check_plain($node->title) . ' [nid:' . $nid . ']';
return $node->title . ' [nid:' . $nid . ']';
}
else {
return t('Not found');

View File

@@ -0,0 +1,61 @@
<?php
/**
* @file
* Builds placeholder replacement tokens for content types.
*/
/**
* Implements hook_token_info().
*/
function i18n_node_token_info() {
$content_type['i18n-name'] = array(
'name' => t("Name (localized)"),
'description' => t("The name of the content type."),
);
$content_type['i18n-description'] = array(
'name' => t("Description (localized)"),
'description' => t("The optional description of the content type."),
);
return array(
'tokens' => array(
'content-type' => $content_type,
),
);
}
/**
* Implements hook_tokens().
*/
function i18n_node_tokens($type, $tokens, array $data = array(), array $options = array()) {
$replacements = array();
$sanitize = !empty($options['sanitize']) ? TRUE : FALSE;
$langcode = isset($options['language']) ? $options['language']->language : i18n_langcode();
if ($type == 'content-type' && !empty($data['node_type'])) {
$node_type = $data['node_type'];
foreach ($tokens as $name => $original) {
switch ($name) {
case 'i18n-name':
$name = array('node', 'type', $node_type->type, 'name');
$options = array('sanitize' => $sanitize, 'langcode' => $langcode);
$name = i18n_string_translate($name, $node_type->name, $options);
$replacements[$original] = $name;
break;
case 'i18n-description':
$description = array('node', 'type', $node_type->type, 'description');
$options = array('sanitize' => $sanitize, 'langcode' => $langcode);
$description = i18n_string_translate($description, $node_type->description, $options);
$replacements[$original] = $description;
break;
}
}
}
return $replacements;
}

View File

@@ -6,9 +6,9 @@ core = 7.x
files[] = i18n_path.inc
files[] = i18n_path.test
; 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

@@ -4,9 +4,9 @@ dependencies[] = i18n
package = Multilingual - Internationalization
core = 7.x
; 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

@@ -6,9 +6,9 @@ core = 7.x
configure = admin/config/regional/i18n/select
files[] = i18n_select.test
; 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

@@ -223,7 +223,7 @@ function _i18n_select_table_alias($table) {
function i18n_select_check_conditions($query, $table_alias = NULL) {
$conditions =& $query->conditions();
foreach ($conditions as $index => $condition) {
if (is_array($condition) && isset($condition['field'])) {
if (is_array($condition) && !empty($condition['field'])) {
if (strpos($condition['field'], '.') === FALSE) {
if ($table_alias) {
// Change the condition to include a table alias.

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;

View File

@@ -10,9 +10,9 @@ files[] = i18n_sync.install
files[] = i18n_sync.module.inc
files[] = i18n_sync.node.inc
files[] = i18n_sync.test
; 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

@@ -11,8 +11,6 @@
*
* Notes:
* This module needs to run after taxonomy, i18n, translation. Check module weight.
*
* @ TODO Test with CCK when possible, api may have changed.
*/
/**
@@ -71,25 +69,6 @@ function i18n_sync_field_info_alter(&$fields) {
}
}
/**
* Implements hook_form_FORM_ID_alter().
*/
function i18n_sync_form_node_admin_content_alter(&$form, &$form_state) {
if (!empty($form['operation']) && $form['operation']['#value'] == 'delete') {
$form['#submit'] = array_merge(array('i18n_sync_node_delete_submit'), $form['#submit']);
}
}
/**
* Implements hook_form_FORM_ID_alter().
*/
function i18n_sync_form_node_delete_confirm_alter(&$form, &$form_state) {
// Intercept form submission so we can handle uploads, replace callback
$form['#submit'] = array_merge(array('i18n_sync_node_delete_submit'), $form['#submit']);
}
/**
* Implements hook_form_FORM_ID_alter().
*/
@@ -145,42 +124,6 @@ function i18n_sync_form_node_type_form_alter(&$form, &$form_state) {
}
}
/**
* Submit callback for
* - node delete confirm
* - node multiple delete confirm
*/
function i18n_sync_node_delete_submit($form, $form_state) {
if ($form_state['values']['confirm']) {
if (!empty($form_state['values']['nid'])) {
// Single node
i18n_sync_node_delete_prepare($form_state['values']['nid']);
}
elseif (!empty($form_state['values']['nodes'])) {
// Multiple nodes
foreach ($form_state['values']['nodes'] as $nid => $value) {
i18n_sync_node_delete_prepare($nid);
}
}
}
// Then it will go through normal form submission
}
/**
* Prepare node for deletion, work out synchronization issues
*/
function i18n_sync_node_delete_prepare($nid) {
$node = node_load($nid);
// Delete file associations when files are shared with existing translations
// so they are not removed by upload module
if (!empty($node->tnid) && module_exists('upload')) {
$result = db_query('SELECT u.* FROM {upload} u WHERE u.nid = %d AND u.fid IN (SELECT t.fid FROM {upload} t WHERE t.fid = u.fid AND t.nid <> u.nid)', $nid);
while ($up = db_fetch_object($result)) {
db_query("DELETE FROM {upload} WHERE fid = %d AND vid = %d", $up->fid, $up->vid);
}
}
}
/**
* Check whether this node is to be synced
*/

View File

@@ -27,6 +27,7 @@
* Node operation (insert|update).
*/
function i18n_sync_node_translation($node, $translations, $field_names, $op) {
$total = count($translations);
$count = 0;
// Disable language selection and synchronization temporarily, enable it again later
$i18n_select = i18n_select(FALSE);
@@ -34,10 +35,12 @@ function i18n_sync_node_translation($node, $translations, $field_names, $op) {
foreach ($translations as $translation) {
// If translation is the same node, we cannot synchronize with itself
if ($node->nid == $translation->nid) {
$total--;
continue;
}
// Load full node, we need all data here.
$translation = node_load($translation->nid, NULL, TRUE);
entity_get_controller('node')->resetCache(array($translation->nid));
$translation = node_load($translation->nid);
$i18n_options = i18n_sync_node_options($node->type);
// Invoke callback for each field, the default is just copy over
foreach ($field_names as $field) {
@@ -53,6 +56,13 @@ function i18n_sync_node_translation($node, $translations, $field_names, $op) {
module_invoke_all('i18n_sync_translation', 'node', $translation, $translation->language, $node, $node->language, $field_names);
node_save($translation);
$count++;
// Flush each entity from the load cache after processing, to
// avoid exceeding PHP memory limits. It should be safe to keep
// at least one, however; so we retain the final translation in
// the cache after saving it.
if ($count < $total) {
entity_get_controller('node')->resetCache(array($translation->nid));
}
}
i18n_sync(TRUE);
i18n_select($i18n_select);

View File

@@ -199,7 +199,14 @@ function i18n_taxonomy_translation_term_overview($term) {
$path = 'taxonomy/term/' . $translation_term->tid;
$title = l($translation_term->name, $path);
$options[] = l(t('edit'), $path . '/edit');
$options['edit'] = array(
'#type' => 'link',
'#title' => t('edit'),
'#href' => $path . '/edit',
'#options' => array(
'query' => drupal_get_destination(),
),
);
if ($translation_term->tid == $i18n_tsid) {
$language_name = t('<strong>@language_name</strong> (source)', array('@language_name' => $language_name));
@@ -208,14 +215,25 @@ function i18n_taxonomy_translation_term_overview($term) {
else {
// No such translation in the set yet: help user to create it.
$title = t('n/a');
$options[] = l(t('add translation'), 'admin/structure/taxonomy/' . $term->vocabulary_machine_name . '/add', array('query' => array('translation' => $term->tid, 'target' => $langcode) + drupal_get_destination()));
$options['add'] = array(
'#type' => 'link',
'#title' => t('add translation'),
'#href' => 'admin/structure/taxonomy/' . $term->vocabulary_machine_name . '/add',
'#options' => array(
'query' => array('translation' => $term->tid, 'target' => $langcode) + drupal_get_destination()
),
);
}
$rows[] = array($language_name, $title, implode(" | ", $options));
$rows[$langcode] = array(
'language' => $language_name,
'title' => $title,
'operations' => array('data' => $options),
);
}
drupal_set_title(t('Translations of term %title', array('%title' => $term->name)), PASS_THROUGH);
$build['translation_node_overview'] = array(
$build['translation_overview'] = array(
'#theme' => 'table',
'#header' => $header,
'#rows' => $rows,

View File

@@ -11,9 +11,9 @@ files[] = i18n_taxonomy.pages.inc
files[] = i18n_taxonomy.admin.inc
files[] = i18n_taxonomy.test
; 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

@@ -12,7 +12,7 @@
*/
function i18n_taxonomy_install() {
module_load_install('i18n');
i18n_install_create_fields('taxonomy_vocabulary', array('language', 'i18n_mode'));
i18n_install_create_fields('taxonomy_vocabulary', array('language', 'i18n_mode'), TRUE);
i18n_install_create_fields('taxonomy_term_data', array('language', 'i18n_tsid'));
// Set module weight for it to run after core modules, but before views.
db_query("UPDATE {system} SET weight = 5 WHERE name = 'i18n_taxonomy' AND type = 'module'");

View File

@@ -184,11 +184,11 @@ function i18n_taxonomy_field_formatter_info() {
}
/**
- * Implements hook_field_formatter_prepare_view().
- *
- * This preloads all taxonomy terms for multiple loaded objects at once and
- * unsets values for invalid terms that do not exist.
- */
* Implements hook_field_formatter_prepare_view().
*
* This preloads all taxonomy terms for multiple loaded objects at once and
* unsets values for invalid terms that do not exist.
*/
function i18n_taxonomy_field_formatter_prepare_view($entity_type, $entities, $field, $instances, $langcode, &$items, $displays) {
return taxonomy_field_formatter_prepare_view($entity_type, $entities, $field, $instances, $langcode, $items, $displays);
}
@@ -318,7 +318,7 @@ function i18n_taxonomy_field_storage_details_alter(&$details, &$field) {
/**
* Implements hook_field_attach_prepare_translation_alter().
*
* Prepare and synchronize translation for term reference fields.
*/
function i18n_taxonomy_field_attach_prepare_translation_alter(&$entity, $context) {
@@ -452,23 +452,13 @@ function i18n_taxonomy_translate_path($path, $path_prefix = 'taxonomy/term/') {
return $links;
}
}
/**
* Implements hook_theme().
*/
function i18n_taxonomy_theme() {
return array(
'i18n_taxonomy_term_page' => array(
'arguments' => array('tids' => array(), 'result' => NULL),
'file' => 'i18n_taxonomy.pages.inc',
),
);
}
/**
* Get localized term name unfiltered.
*/
function i18n_taxonomy_term_name($term, $langcode = NULL) {
return i18n_taxonomy_vocabulary_mode($term->vid, I18N_MODE_LOCALIZE) ? i18n_string(array('taxonomy', 'term', $term->tid, 'name'), $term->name, array('langcode' => $langcode, 'sanitize' => FALSE)) : $term->name;
$key = i18n_object_info('taxonomy_term', 'key');
return i18n_taxonomy_vocabulary_mode($term->vid, I18N_MODE_LOCALIZE) ? i18n_string(array('taxonomy', 'term', $term->{$key}, 'name'), $term->name, array('langcode' => $langcode, 'sanitize' => FALSE)) : $term->name;
}
@@ -476,7 +466,8 @@ function i18n_taxonomy_term_name($term, $langcode = NULL) {
* Get localized term description unfiltered.
*/
function i18n_taxonomy_term_description($term, $langcode = NULL) {
return i18n_taxonomy_vocabulary_mode($term->vid, I18N_MODE_LOCALIZE) ? i18n_string(array('taxonomy', 'term', $term->tid, 'description'), $term->description, array('langcode' => $langcode, 'sanitize' => FALSE)) : $term->description;
$key = i18n_object_info('taxonomy_term', 'key');
return i18n_taxonomy_vocabulary_mode($term->vid, I18N_MODE_LOCALIZE) ? i18n_string(array('taxonomy', 'term', $term->{$key}, 'description'), $term->description, array('langcode' => $langcode, 'sanitize' => FALSE)) : $term->description;
}
/**
@@ -1229,6 +1220,9 @@ function i18n_taxonomy_entity_info_alter(&$entity_info) {
// Core doesn't provide a label callback for taxonomy terms. By setting one
// we can use it to return the correct localized term name.
$entity_info['taxonomy_term']['label callback'] = 'i18n_taxonomy_taxonomy_term_label';
// Also let core know terms have languages, now.
$entity_info['taxonomy_term']['entity keys']['language'] = 'language';
}
}
@@ -1260,10 +1254,10 @@ function i18n_taxonomy_modules_enabled($modules) {
$modules = drupal_map_assoc($modules);
if (isset($modules['i18n_taxonomy'])) {
foreach (field_info_fields() as $fieldname => $field) {
if ($field['type'] == 'taxonomy_term_reference') {
$field['settings']['options_list_callback'] = 'i18n_taxonomy_allowed_values';
field_update_field($field);
}
if ($field['type'] == 'taxonomy_term_reference') {
$field['settings']['options_list_callback'] = 'i18n_taxonomy_allowed_values';
field_update_field($field);
}
}
}
}

View File

@@ -62,37 +62,6 @@ function i18n_taxonomy_term_page($term) {
return $build;
}
/**
* Render a taxonomy term page HTML output.
*
* @param $tids
* An array of term ids.
* @param $result
* A pager_query() result, such as that performed by taxonomy_select_nodes().
*
* @ingroup themeable
*/
function theme_i18n_taxonomy_term_page($tids, $result) {
drupal_add_css(drupal_get_path('module', 'taxonomy') . '/taxonomy.css');
$output = '';
// Only display the description if we have a single term, to avoid clutter and confusion.
if (count($tids) == 1) {
$term = i18n_taxonomy_localize_terms(taxonomy_term_load($tids[0]));
// Check that a description is set.
if (!empty($term->description)) {
$output .= '<div class="taxonomy-term-description">';
$output .= filter_xss_admin($term->description);
$output .= '</div>';
}
}
$output .= taxonomy_render_nodes($result);
return $output;
}
/**
* Helper function for autocompletion. Replacement for taxonomy_autocomplete
*/

View File

@@ -6,9 +6,9 @@ core = 7.x
files[] = i18n_translation.inc
; 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

@@ -4,9 +4,9 @@ core = 7.x
package = Multilingual - Internationalization
dependencies[] = i18n_variable
; 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

@@ -10,9 +10,9 @@ configure = admin/config/regional/i18n/variable
files[] = i18n_variable.class.inc
files[] = i18n_variable.test
; 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

@@ -7,9 +7,9 @@ package = Testing
core = 6.x
hidden = TRUE
; 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

@@ -36,6 +36,12 @@ function i18n_test_i18n_string_info() {
'format' => FALSE, // This group doesn't have strings with format
'refresh callback' => 'i18n_test_i18n_string_refresh',
);
$groups['test_cached'] = array(
'title' => t('Test Cached Strings'),
'description' => t('Translatable items of a textgroup with caching enabled.'),
'format' => FALSE, // This group doesn't have strings with format
'class' => 'i18n_string_textgroup_cached_logged',
);
return $groups;
}
/**
@@ -43,4 +49,53 @@ function i18n_test_i18n_string_info() {
*/
function i18n_test_i18n_string_refresh() {
return TRUE;
}
/**
* Implements hook_menu().
*/
function i18n_test_menu() {
// Required for the i18n_string caching tests.
$items['tests/i18n/i18n_string_build/%'] = array(
'title' => 'Load string',
'access callback' => TRUE,
'page callback' => 'i18n_string_build',
'page arguments' => array(3),
'type' => MENU_CALLBACK,
'delivery callback' => 'drupal_json_output',
);
$items['tests/i18n/i18n_string_build/%/%'] = array(
'title' => 'Load string',
'access callback' => TRUE,
'page callback' => 'i18n_string_build',
'page arguments' => array(3, 4),
'type' => MENU_CALLBACK,
'delivery callback' => 'drupal_json_output',
);
$items['tests/i18n/i18n_string_translation_search/%'] = array(
'title' => 'Search string translations',
'access callback' => TRUE,
'page callback' => 'i18n_string_translation_search',
'page arguments' => array(3),
'type' => MENU_CALLBACK,
'delivery callback' => 'drupal_json_output',
);
$items['tests/i18n/i18n_string_translation_search/%/%'] = array(
'title' => 'Search string translations',
'access callback' => TRUE,
'page callback' => 'i18n_string_translation_search',
'page arguments' => array(3, 4),
'type' => MENU_CALLBACK,
'delivery callback' => 'drupal_json_output',
);
return $items;
}
class i18n_string_textgroup_cached_logged extends i18n_string_textgroup_cached {
public static function load_translation($i18nstring, $langcode) {
$strings = variable_get('i18n_loaded_translations', array());
$strings[$i18nstring->get_name()] = true;
variable_set('i18n_loaded_translations', $strings);
parent::load_translation($i18nstring, $langcode);
}
}