diff --git a/i18n.test b/i18n.test
index ca01e6d..af03dab 100644
--- a/i18n.test
+++ b/i18n.test
@@ -11,8 +11,8 @@ class Drupali18nTestCase extends DrupalWebTestCase {
function setUpLanguages($admin_permissions = array()) {
// Setup users.
- $this->admin_user = $this->drupalCreateUser(array_merge(array('bypass node access', 'administer nodes', 'administer languages', 'administer content types', 'administer blocks', 'access administration pages'), $admin_permissions));
- $this->translator = $this->drupalCreateUser(array('translate interface'));
+ $this->admin_user = $this->drupalCreateUser(array_merge(array('bypass node access', 'administer nodes', 'administer languages', 'administer content types', 'administer blocks', 'access administration pages', 'translate interface', 'translate user-defined strings'), $admin_permissions));
+ $this->translator = $this->drupalCreateUser(array('translate interface', 'translate user-defined strings'));
$this->drupalLogin($this->admin_user);
@@ -49,6 +49,7 @@ class Drupali18nTestCase extends DrupalWebTestCase {
'edit own ' . $type->type . ' content',
'translate content',
'translate interface',
+ 'translate user-defined strings',
));
$this->drupalLogin($this->admin_user);
diff --git a/i18n_block/i18n_block.test b/i18n_block/i18n_block.test
index 5cee201..33a8bed 100644
--- a/i18n_block/i18n_block.test
+++ b/i18n_block/i18n_block.test
@@ -25,7 +25,7 @@ class i18nBlocksTestCase extends Drupali18nTestCase {
function testBlockTranslation() {
- $block_translater = $this->drupalCreateUser(array('administer blocks', 'translate interface'));
+ $block_translater = $this->drupalCreateUser(array('administer blocks', 'translate interface', 'translate user-defined strings'));
// Display Language switcher block
$switcher = array('module' => 'locale', 'delta' => 'language', 'title' => t('Languages'));
@@ -70,7 +70,7 @@ class i18nBlocksTestCase extends Drupali18nTestCase {
$this->assertText(t('translated'));
$this->clickLink(t('translate'));
- debug($translations);
+
$this->assertFieldByName('strings[blocks:block:' . $box2['delta'] . ':title]', $translations['title']['es']);
$this->assertFieldByName('strings[blocks:block:' . $box2['delta'] . ':body]', $translations['body']['es']);
diff --git a/i18n_string/i18n_string.admin.inc b/i18n_string/i18n_string.admin.inc
index bb5bb5a..6d2c290 100644
--- a/i18n_string/i18n_string.admin.inc
+++ b/i18n_string/i18n_string.admin.inc
@@ -12,7 +12,6 @@ include_once './includes/locale.inc';
* Form callback. Refresh textgroups.
*/
function i18n_string_admin_refresh_form() {
- module_load_include('inc', 'i18n_string');
// Select textgroup/s. Just the ones that have a 'refresh callback'
$groups = array();
foreach (i18n_string_group_info() as $name => $info) {
@@ -63,7 +62,7 @@ function i18n_string_admin_refresh_form_submit($form, &$form_state) {
*/
function i18n_string_refresh_group($group, $delete = FALSE) {
$result = FALSE;
-
+
// Compile all strings for this group
if ($strings = i18n_string_group_string_list($group)) {
i18n_string_refresh_string_list($strings);
@@ -179,7 +178,7 @@ function i18n_string_refresh_enabled_modules($modules) {
foreach ($modules as $module) {
if ($strings = i18n_string_module_string_list($module)) {
$count += i18n_string_refresh_string_list($strings);
-
+
}
// Call module refresh if exists
module_invoke($module, 'i18n_string_refresh', 'all');
@@ -287,7 +286,7 @@ function i18n_string_module_string_list($module) {
if ($groups = module_invoke($module, 'i18n_string_info')) {
foreach ($groups as $group) {
$strings = i18n_string_array_merge($strings, i18n_string_group_string_list($group));
- }
+ }
}
else {
$groups = array();
@@ -312,136 +311,3 @@ function i18n_string_module_string_list($module) {
}
return $strings;
}
-
-/**
- * User interface for string editing.
- */
-function i18n_string_locale_translate_edit_form($form, &$form_state, $lid) {
- // Fetch source string, if possible.
- $source = db_query('SELECT source, context, textgroup, location FROM {locales_source} WHERE lid = :lid', array(':lid' => $lid))->fetchObject();
- if (!$source) {
- drupal_set_message(t('String not found.'), 'error');
- drupal_goto('admin/config/regional/translate/translate');
- }
-
- // Add original text to the top and some values for form altering.
- $form['original'] = array(
- '#type' => 'item',
- '#title' => t('Original text'),
- '#markup' => check_plain(wordwrap($source->source, 0)),
- );
- if (!empty($source->context)) {
- $form['context'] = array(
- '#type' => 'item',
- '#title' => t('Context'),
- '#markup' => check_plain($source->context),
- );
- }
- $form['lid'] = array(
- '#type' => 'value',
- '#value' => $lid
- );
- $form['textgroup'] = array(
- '#type' => 'value',
- '#value' => $source->textgroup,
- );
- $form['location'] = array(
- '#type' => 'value',
- '#value' => $source->location
- );
-
- // Include default form controls with empty values for all languages.
- // This ensures that the languages are always in the same order in forms.
- $languages = language_list();
-
- // We don't need the default language value, that value is in $source.
- $omit = $source->textgroup == 'default' ? 'en' : i18n_string_source_language();
- unset($languages[($omit)]);
- $form['translations'] = array('#tree' => TRUE);
- // Approximate the number of rows to use in the default textarea.
- $rows = min(ceil(str_word_count($source->source) / 12), 10);
- foreach ($languages as $langcode => $language) {
- $form['translations'][$langcode] = array(
- '#type' => 'textarea',
- '#title' => t($language->name),
- '#rows' => $rows,
- '#default_value' => '',
- );
- }
-
- // Fetch translations and fill in default values in the form.
- $result = db_query("SELECT DISTINCT translation, language FROM {locales_target} WHERE lid = :lid AND language <> :omit", array(':lid' => $lid, ':omit' => $omit));
- foreach ($result as $translation) {
- $form['translations'][$translation->language]['#default_value'] = $translation->translation;
- }
-
- $form['actions'] = array('#type' => 'actions');
- $form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Save translations'));
-
- // Restrict filter permissions and handle validation and submission for i18n strings
- $context = db_select('i18n_string', 'i18ns')
- ->fields('i18ns')
- ->condition('lid', $form['lid']['#value'])
- ->execute()
- ->fetchObject();
- if ($source->textgroup != 'default' && $context) {
- $form['i18n_string_context'] = array('#type' => 'value', '#value' => $context);
- if ($context->format) {
- $formats = filter_formats();
- $format = $formats[$context->format];
- $disabled = !filter_access($format);
- if ($disabled) {
- drupal_set_message(t('This string uses the %name text format. You are not allowed to translate or edit texts with this format.', array('%name' => $format->name)), 'warning');
- }
- foreach (element_children($form['translations']) as $langcode) {
- $form['translations'][$langcode]['#disabled'] = $disabled;
- }
- $form['translations']['format_help'] = array(
- '#type' => 'markup',
- '#markup' => '
' . t('Text format: @name', array('@name' => $format->name)) . '
' . theme('filter_tips', array('tips' => _filter_tips($context->format, FALSE))),
- );
- $form['submit']['#disabled'] = $disabled;
- }
- }
- return $form;
-}
-
-/**
- * Process string editing form validations.
- *
- * If it is an allowed format, skip default validation, the text will be filtered later
- */
-function i18n_string_locale_translate_edit_form_validate($form, &$form_state) {
- if (empty($form_state['values']['i18n_string_context']) || empty($form_state['values']['i18n_string_context']->format)) {
- // If not text format use regular validation for all strings
- $copy_state = $form_state;
- $copy_state['values']['textgroup'] = 'default';
- module_load_include('admin.inc', 'locale');
- locale_translate_edit_form_validate($form, $copy_state);
- }
- elseif (!i18n_string_translate_access($form_state['values']['i18n_string_context'])) {
- form_set_error('translations', t('You are not allowed to translate or edit texts with this text format.'));
- }
-}
-
-/**
- * Process string editing form submissions.
- *
- * Mark translations as current.
- */
-function i18n_string_locale_translate_edit_form_submit($form, &$form_state) {
- // Invoke locale submission.
- module_load_include('admin.inc', 'locale');
- locale_translate_edit_form_submit($form, $form_state);
- $lid = $form_state['values']['lid'];
- foreach ($form_state['values']['translations'] as $key => $value) {
- if (!empty($value)) {
- // An update has been made, so we assume the translation is now current.
- db_update('locales_target')
- ->fields(array('i18n_status' => I18N_STRING_STATUS_CURRENT))
- ->condition('lid', $lid)
- ->condition('language', $key)
- ->execute();
- }
- }
-}
diff --git a/i18n_string/i18n_string.inc b/i18n_string/i18n_string.inc
index 9fd3a90..7d71326 100644
--- a/i18n_string/i18n_string.inc
+++ b/i18n_string/i18n_string.inc
@@ -157,7 +157,7 @@ class i18n_string_object {
* There's a hidden variable, 'i18n_string_debug', that when set to TRUE will display additional info
*/
public function format_translation($langcode, $options = array()) {
- $options += array('langcode' => $langcode, 'sanitize' => TRUE, 'sanitize default' => FALSE, 'cache' => FALSE, 'debug' => $this->textgroup()->debug);
+ $options += array('langcode' => $langcode, 'sanitize' => TRUE, 'cache' => FALSE, 'debug' => $this->textgroup()->debug);
if ($translation = $this->get_translation($langcode)) {
$string = $translation;
if (isset($options['filter'])) {
@@ -167,15 +167,11 @@ class i18n_string_object {
else {
// Get default source string if no translation.
$string = $this->get_string();
- $options['sanitize'] = $options['sanitize default'];
+ $options['sanitize'] = !empty($options['sanitize default']);
}
if (!empty($this->format)) {
$options += array('format' => $this->format);
}
- elseif (empty($translation) && $options['sanitize']) {
- // We are bout to display the source string without text format, force check_plain.
- $string = check_plain($string);
- }
// Add debug information if enabled
if ($options['debug']) {
$info = array($langcode, $this->textgroup, $this->context);
@@ -259,6 +255,19 @@ class i18n_string_object {
public function remove($options = array()) {
return $this->textgroup()->string_remove($this, $options);
}
+ /**
+ * Check whether there is any problem for the user to translate a this string.
+ *
+ * @param $account
+ * Optional user account, defaults to current user.
+ *
+ * @return
+ * None if the user has access to translate the string.
+ * Error message if the user cannot translate that string.
+ */
+ public function check_translate_access($account = NULL) {
+ return i18n_string_translate_check_string($this, $account);
+ }
}
/**
@@ -441,7 +450,7 @@ class i18n_string_textgroup_default {
/**
* Remove string object.
- *
+ *
* @return
* SAVED_DELETED | FALSE (If the operation failed because no source)
*/
@@ -749,9 +758,9 @@ class i18n_string_textgroup_default {
*
* @param $translations
* Array of translation objects as loaded from the db.
- * @param $langcode
- * Language code, array of language codes or * to search all translations.
- *
+ * @param $langcode
+ * Language code, array of language codes or * to search all translations.
+ *
* @return array
* Array of i18n string objects.
*/
@@ -1024,11 +1033,11 @@ class i18n_string_object_wrapper extends i18n_object_wrapper {
/**
* Get object strings for translation
*
- * This will return a simple array of string objects, indexed by full string name.
- *
- * @param $options
- * Array with processing options.
- * - 'empty', whether to return empty strings, defaults to FALSE.
+ * This will return a simple array of string objects, indexed by full string name.
+ *
+ * @param $options
+ * Array with processing options.
+ * - 'empty', whether to return empty strings, defaults to FALSE.
*/
public function get_strings($options = array()) {
$options += array('empty' => FALSE);
@@ -1064,7 +1073,7 @@ class i18n_string_object_wrapper extends i18n_object_wrapper {
$this->properties = $this->build_properties();
// Call hook_i18n_string_list_TEXTGROUP_alter(), last chance for modules
drupal_alter('i18n_string_list_' . $this->get_textgroup(), $this->properties, $this->type, $this->object);
-
+
}
return $this->properties;
}
@@ -1162,7 +1171,7 @@ class i18n_string_object_wrapper extends i18n_object_wrapper {
* Translate access (localize strings)
*/
protected function localize_access() {
- return user_access('translate interface');
+ return user_access('translate interface') && user_access('translate user-defined strings');
}
/**
diff --git a/i18n_string/i18n_string.module b/i18n_string/i18n_string.module
index 236951c..3e31360 100644
--- a/i18n_string/i18n_string.module
+++ b/i18n_string/i18n_string.module
@@ -149,7 +149,7 @@ function i18n_string_menu_alter(&$items) {
'page callback' => 'drupal_get_form',
'page arguments' => array('i18n_string_locale_translate_edit_form', 5),
'access arguments' => array('translate interface'),
- 'file' => 'i18n_string.admin.inc',
+ 'file' => 'i18n_string.pages.inc',
'file path' => drupal_get_path('module', 'i18n_string'),
);
}
@@ -160,7 +160,7 @@ function i18n_string_menu_alter(&$items) {
function i18n_string_hook_info() {
$hooks['i18n_string_info'] =
$hooks['i18n_string_list'] =
- $hooks['i18n_string_refresh'] =
+ $hooks['i18n_string_refresh'] =
$hooks['i18n_string_objects'] = array(
'group' => 'i18n',
);
@@ -183,6 +183,24 @@ function i18n_string_locale($op = 'groups') {
}
/**
+ * Implements hook_permission().
+ */
+function i18n_string_permission() {
+ return array(
+ 'translate user-defined strings' => array(
+ 'title' => t('Translate user-defined strings'),
+ 'description' => t('Translate user-defined strings that are created as part of content or configuration.'),
+ 'restrict access' => TRUE,
+ ),
+ 'translate admin strings' => array(
+ 'title' => t('Translate admin strings'),
+ 'description' => t('Translate administrative strings with a very permissive XSS/HTML filter that allows all HTML tags.'),
+ 'restrict access' => TRUE,
+ ),
+ );
+}
+
+/**
* Implements hook_modules_enabled().
*/
function i18n_string_modules_enabled($modules) {
@@ -335,7 +353,8 @@ function i18n_string_allowed_format($format_id = NULL) {
return TRUE;
}
else {
- return in_array($format_id, variable_get('i18n_string_allowed_formats', array(filter_fallback_format())), TRUE);
+ // Check the format still exists an it is in the allowed formats list.
+ return filter_format_load($format_id) && in_array($format_id, variable_get('i18n_string_allowed_formats', array(filter_fallback_format())), TRUE);
}
}
@@ -384,10 +403,42 @@ function i18n_string_element_mark(&$element) {
/**
* Get source string object.
+ *
+ * This returns the i18nstring object only when it has a source.
+ *
+ * @return i18n_string_object
*/
function i18n_string_get_source($name) {
- list ($textgroup, $context) = i18n_string_context($name);
- return i18n_string_textgroup($textgroup)->build_string($context)->get_source();
+ return i18n_string_build($name)->get_source();
+}
+
+/**
+ * Get full string object.
+ *
+ * Builds string and loads the source if available.
+ *
+ * @return i18n_string_object
+ */
+function i18n_string_get_string($name, $default = NULL) {
+ $i18nstring = i18n_string_build($name, $default);
+ $i18nstring->get_source();
+ return $i18nstring;
+}
+
+/**
+ * Get full string object by lid.
+ */
+function i18n_string_get_by_lid($lid) {
+ $string = db_select('i18n_string', 's')
+ ->fields('s', array('textgroup', 'context'))
+ ->condition('lid', $lid)
+ ->execute()
+ ->fetchObject();
+ if ($string) {
+ $i18nstring = i18n_string_textgroup($string->textgroup)->build_string($string->context);
+ $i18nstring->get_source();
+ return $i18nstring;
+ }
}
/**
@@ -476,9 +527,7 @@ function i18n_string_translate($name, $string, $options = array()) {
}
else {
// If we don't want to translate to this language, format and return
- if (!empty($options['sanitize default']) && empty($options['format'])) {
- $string = check_plain($string);
- }
+ $options['sanitize'] = !empty($options['sanitize default']);
return i18n_string_format($string, $options);
}
}
@@ -486,15 +535,57 @@ function i18n_string_translate($name, $string, $options = array()) {
/**
* Check user access to translate a specific string.
- *
+ *
* If the string has a format the user is not allowed to edit, it will return FALSE
- *
+ *
* @param $string_format;
* String object or $format_id
*/
function i18n_string_translate_access($string_format, $account = NULL) {
$format_id = is_object($string_format) ? i18n_object_field($string_format, 'format') : $string_format;
- return empty($format_id) || i18n_string_allowed_format($format_id) && ($format = filter_format_load($format_id)) && filter_access($format, $account) ;
+ return user_access('translate interface', $account) &&
+ (empty($format_id) || i18n_string_allowed_format($format_id) && ($format = filter_format_load($format_id)) && filter_access($format, $account));
+}
+
+/**
+ * Check whether there is any problem for the user to translate a specific string.
+ *
+ * Here we assume the user has 'translate interface' access that should have
+ * been checked for the page. Possible reasons a user cannot translate a string:
+ *
+ * @param $i18nstring
+ * String object.
+ * @param $account
+ * Optional user account, defaults to current user.
+ *
+ * @return
+ * None or empty string if the user has access to translate the string.
+ * Message if the user cannot translate that string.
+ */
+function i18n_string_translate_check_string($i18nstring, $account = NULL) {
+ if (!user_access('translate interface', $account) || !user_access('translate user-defined strings', $account)) {
+ return t('This is a user-defined string. You are not allowed to translate these strings.');
+ }
+ elseif (!empty($i18nstring->format)) {
+ if (!i18n_string_allowed_format($i18nstring->format)) {
+ $format = filter_format_load($i18nstring->format);
+ return t('This string uses the %name text format. Strings with this format are not allowed for translation.', array('%name' => $format->name));
+ }
+ elseif ($format = filter_format_load($i18nstring->format)) {
+ // It is a text format, check user access to that text format.
+ if (!filter_access($format, $account)) {
+ return t('This string uses the %name text format. You are not allowed to translate or edit texts with this format.', array('%name' => $format->name));
+ }
+ }
+ else {
+ // This is one of our special formats, I18N_STRING_FILTER_*
+ if ($i18nstring->format == I18N_STRING_FILTER_XSS_ADMIN && !user_access('translate admin strings', $account)) {
+ return t('The source string is an administrative string. You are not allowed to translate these strings.');
+ }
+ }
+ }
+ // No error message, it should be OK to translate.
+ return '';
}
/**
@@ -512,19 +603,24 @@ function i18n_string_translate_access($string_format, $account = NULL) {
*/
function i18n_string_format($string, $options = array()) {
$options += array('langcode' => i18n_langcode(), 'format' => FALSE, 'sanitize' => TRUE, 'cache' => FALSE);
- // Apply format and callback
+ // Apply format and callback
if ($string) {
- if ($options['format'] && $options['sanitize']) {
- // Handle special format values (xss, xss_admin)
- switch ($options['format']) {
- case I18N_STRING_FILTER_XSS:
- $string = !empty($options['allowed_tags']) ? filter_xss($string, $options['allowed_tags']) : filter_xss($string);
- break;
- case I18N_STRING_FILTER_XSS_ADMIN:
- $string = filter_xss_admin($string);
- break;
- default:
- $string = check_markup($string, $options['format'], $options['langcode'], $options['cache']);
+ if ($options['sanitize']) {
+ if ($options['format']) {
+ // Handle special format values (xss, xss_admin)
+ switch ($options['format']) {
+ case I18N_STRING_FILTER_XSS:
+ $string = !empty($options['allowed_tags']) ? filter_xss($string, $options['allowed_tags']) : filter_xss($string);
+ break;
+ case I18N_STRING_FILTER_XSS_ADMIN:
+ $string = filter_xss_admin($string);
+ break;
+ default:
+ $string = check_markup($string, $options['format'], $options['langcode'], $options['cache']);
+ }
+ }
+ else {
+ $string = check_plain($string);
}
}
if (isset($options['callback'])) {
@@ -642,10 +738,12 @@ function i18n_string_remove($name, $string = NULL, $options = array()) {
function i18n_string_l10n_client_add($string, $langcode) {
// If current language add to l10n client list for later on page translation.
// If langcode translation was disabled we are not supossed to reach here.
- if (($langcode == i18n_langcode()) && function_exists('l10_client_add_string_to_page') && i18n_string_translate_access($string)) {
- $translation = $string->get_translation($langcode);
- $source = !empty($string->source) ? $string->source : $string->string;
- l10_client_add_string_to_page($source, $translation ? $translation : TRUE, $string->textgroup, $string->context);
+ if (($langcode == i18n_langcode()) && function_exists('l10_client_add_string_to_page') && user_access('translate interface')) {
+ if (!$string->check_translate_access()) {
+ $translation = $string->get_translation($langcode);
+ $source = !empty($string->source) ? $string->source : $string->string;
+ l10_client_add_string_to_page($source, $translation ? $translation : TRUE, $string->textgroup, $string->context);
+ }
}
}
@@ -710,7 +808,7 @@ function i18n_string_object_remove($type, $object, $options = array()) {
/**
* Update object properties.
- *
+ *
* @param $type
* Object type
* @param $object
@@ -730,20 +828,20 @@ function i18n_string_object_translate_page($object_type, $object_value, $languag
/**
* Preload all strings for this textroup/context.
- *
+ *
* This is a performance optimization to load all needed strings with a single query.
- *
+ *
* Examples of valid string name to search are:
* - 'taxonomy:term:*:title'
* This will find all titles for taxonomy terms
* - array('taxonomy', 'term', array(1,2), '*')
* This will find all properties for taxonomy terms 1 and 2
- *
+ *
* @param $name
* Specially crafted string name, it may take '*' and array parameters for each element.
* @param $langcode
* Language code to search translations. Defaults to current language.
- *
+ *
* @return array()
* String objects indexed by context.
*/
@@ -764,7 +862,7 @@ function i18n_string_translation_search($name, $langcode = NULL) {
* The language code to translate to a language other than what is used to display the page.
* @param $source_string
* Optional source string, just in case it needs to be created.
- *
+ *
* @return mixed
* Source string object if update was successful.
* Null if source string not found.
@@ -775,21 +873,15 @@ function i18n_string_translation_update($name, $translation, $langcode, $source_
return i18n_string_multiple('translation_update', $name, $translation, $langcode);
}
elseif ($source = i18n_string_get_source($name)) {
- if (i18n_string_translation_validate($source, $translation)) {
- if ($langcode == i18n_string_source_language()) {
- // It's the default language so we should update the string source as well.
- i18n_string_update($name, $translation);
- }
- else {
- list ($textgroup, $context) = i18n_string_context($name);
- i18n_string_textgroup($textgroup)->update_translation($context, $langcode, $translation);
- }
- return $source;
+ if ($langcode == i18n_string_source_language()) {
+ // It's the default language so we should update the string source as well.
+ i18n_string_update($name, $translation);
}
else {
- // We cannot update this string because of its input format.
- return FALSE;
+ list ($textgroup, $context) = i18n_string_context($name);
+ i18n_string_textgroup($textgroup)->update_translation($context, $langcode, $translation);
}
+ return $source;
}
elseif ($source_string) {
// We don't have a source in the database, so we need to create it, but only if we've got the source too.
@@ -801,18 +893,3 @@ function i18n_string_translation_update($name, $translation, $langcode, $source_
return NULL;
}
}
-
-/**
- * Validate translation and check user access to input format
- */
-function i18n_string_translation_validate($i18nstring, $translation) {
- if (!empty($i18nstring->format)) {
- // If we've got a text format, just need to check user access to it.
- return i18n_string_translate_access($i18nstring);
- }
- else {
- // If not text format use standard locale validation.
- // Note: looks like locale.inc is included by locale_init() ?!
- return locale_string_is_safe($translation);
- }
-}
\ No newline at end of file
diff --git a/i18n_string/i18n_string.pages.inc b/i18n_string/i18n_string.pages.inc
index 7ca4289..dcab466 100644
--- a/i18n_string/i18n_string.pages.inc
+++ b/i18n_string/i18n_string.pages.inc
@@ -7,6 +7,10 @@
* @author Jose A. Reyero, 2007
*/
+// Load locale libraries
+require_once './includes/locale.inc';
+require_once drupal_get_path('module', 'locale') . '/locale.admin.inc';
+
/**
* Generate translate page from object
*/
@@ -44,9 +48,9 @@ function i18n_string_translate_page_overview_form($form, &$form_state, $object,
// Set the default item key, assume it's the first.
$item_title = reset($strings);
$header = array(
- 'language' => t('Language'),
- 'title' => t('Title'),
- 'status' => t('Status'),
+ 'language' => t('Language'),
+ 'title' => t('Title'),
+ 'status' => t('Status'),
'operations' => t('Operations')
);
$source_language = variable_get_value('i18n_string_source_language');
@@ -96,7 +100,7 @@ function i18n_string_translate_page_overview_form($form, &$form_state, $object,
/**
* Form builder callback for in-place string translation.
- *
+ *
* @param $strings
* Array of strings indexed by string name (may be indexed by group key too if $groups is present)
* @param $langcode
@@ -107,7 +111,8 @@ function i18n_string_translate_page_overview_form($form, &$form_state, $object,
function i18n_string_translate_page_form($form, &$form_state, $strings, $langcode, $groups = NULL) {
$form = i18n_string_translate_page_form_base($form, $langcode);
if ($groups) {
- $form['string_groups'] = array('#type' => 'value', '#value' => $groups);
+ // I we've got groups, string list is grouped by group key.
+ $form['string_groups'] = array('#type' => 'value', '#value' => $strings);
foreach ($groups as $key => $title) {
$form['display'] = array(
'#type' => 'vertical_tabs',
@@ -117,9 +122,12 @@ function i18n_string_translate_page_form($form, &$form_state, $strings, $langcod
'#title' => $title,
'#type' => 'fieldset',
) + i18n_string_translate_page_form_strings($strings[$key], $langcode);
+ $form['string_list']['#value'] += $strings[$key];
}
}
else {
+ // We add a single group with key 'all', but no tabs.
+ $form['string_groups'] = array('#type' => 'value', '#value' => array('all' => $strings));
$form['strings']['all'] = i18n_string_translate_page_form_strings($strings, $langcode);
}
return $form;
@@ -144,9 +152,8 @@ function i18n_string_translate_page_form_base($form, $langcode, $redirect = NULL
);
}
// Add explicit validate and submit hooks so this can be used from inside any form.
- $form['#validate'] = array('i18n_string_translate_page_form_validate');
$form['#submit'] = array('i18n_string_translate_page_form_submit');
- return $form;
+ return $form;
}
/**
@@ -155,88 +162,72 @@ function i18n_string_translate_page_form_base($form, $langcode, $redirect = NULL
function i18n_string_translate_page_form_strings($strings, $langcode) {
$formats = filter_formats();
foreach ($strings as $item) {
- $disabled = FALSE;
- $description = '';
- // We may have a source or not. Maybe the format is disallowed for all.
+ // We may have a source or not. Load it, our string may get the format from it.
$source = $item->get_source();
$format_id = $source ? $source->format : $item->format;
- if ($format_id) {
- $format = filter_format_load($format_id);
- $disabled = !i18n_string_translate_access($item);
- if ($disabled) {
- $description = t('This string uses the %name text format. You are not allowed to translate or edit texts with this format.', array('%name' => $format->name));
- }
- else {
- $description = '' . t('Text format: @name', array('@name' => $format->name)) . '
' . theme('filter_tips', array('tips' => _filter_tips($format->format, FALSE)));
- }
+ $description = '';
+ // Check permissions to translate this string, depends on format, etc..
+ if ($message = $item->check_translate_access()) {
+ // We'll display a disabled element with the reason it cannot be translated.
+ $disabled = TRUE;
+ $description = $message;
}
- // If we don't have a source we create it.
- if (!$source && !$disabled) {
- // Enable messages just as a reminder these strings are not being updated properly.
- $status = $item->update(array('messages' => TRUE));
- if ($status === FALSE || $status === SAVED_DELETED) {
- // We don't have a source string so nothing to translate here
- $disabled = TRUE;
- }
- else {
- $source = $item->get_source();
+ else {
+ $disabled = FALSE;
+ $description = '';
+ // If we don't have a source and it can be translated, we create it.
+ if (!$source) {
+ // Enable messages just as a reminder these strings are not being updated properly.
+ $status = $item->update(array('messages' => TRUE));
+ if ($status === FALSE || $status === SAVED_DELETED) {
+ // We don't have a source string so nothing to translate here
+ $disabled = TRUE;
+ }
+ else {
+ $source = $item->get_source();
+ }
}
}
+
$default_value = $item->format_translation($langcode, array('langcode' => $langcode, 'sanitize' => FALSE, 'debug' => FALSE));
$form[$item->get_name()] = array(
'#title' => $item->get_title(),
'#type' => 'textarea',
'#default_value' => $default_value,
'#disabled' => $disabled,
- '#description' => $description,
- '#i18n_string_format' => $source ? $source->format : 0,
+ '#description' => $description . _i18n_string_translate_format_help($format_id),
+ //'#i18n_string_format' => $source ? $source->format : 0,
// If disabled, provide smaller textarea (that can be expanded anyway).
'#rows' => $disabled ? 1 : min(ceil(str_word_count($default_value) / 12), 10),
'#parents' => array('strings', $item->get_name()),
);
}
- return $form;
-}
-
-/**
- * Validation submission callback for in-place string translation.
- */
-function i18n_string_translate_page_form_validate($form, &$form_state) {
- foreach ($form_state['values']['strings'] as $key => $value) {
- // We don't need to validate disabled form fields because those are already
- // validated by the FormAPI.
- if (empty($form['strings'][$key]['#i18n_string_format'])) {
- i18n_string_validate_submission("strings][$key", $value);
- }
- }
+ return $form;
}
/**
* Form submission callback for in-place string translation.
*/
function i18n_string_translate_page_form_submit($form, &$form_state) {
- foreach ($form_state['values']['strings'] as $key => $value) {
- list($textgroup, $context) = i18n_string_context(explode(':', $key));
- i18n_string_textgroup($textgroup)->update_translation($context, $form_state['values']['langcode'], $value);
+ $count = $success = 0;
+ foreach ($form_state['values']['strings'] as $name => $value) {
+ $count++;
+ list($textgroup, $context) = i18n_string_context(explode(':', $name));
+ $result = i18n_string_textgroup($textgroup)->update_translation($context, $form_state['values']['langcode'], $value);
+ $success += ($result ? 1 : 0);
+ }
+ if ($success) {
+ drupal_set_message(format_plural($success, 'A translation was saved successfully.', '@count translations were saved successfully.'));
+ }
+ if ($error = $count - $success) {
+ drupal_set_message(format_plural($error, 'A translation could not be saved.', '@count translations could not be saved.'), 'warning');
}
- drupal_set_message(t('Translations saved.'));
if (isset($form['#redirect'])) {
$form_state['redirect'] = $form['#redirect'];
}
}
/**
- * String submission validation callback.
- */
-function i18n_string_validate_submission($formkey, $value) {
- // Validation based on locale_translate_edit_form_validate.
- if (!locale_string_is_safe($value)) {
- form_set_error($formkey, t('The submitted string contains disallowed HTML: %string', array('%string' => $value)));
- watchdog('locale', 'Attempted submission of a translation string with disallowed HTML: %string', array('%string' => $value), WATCHDOG_WARNING);
- }
-}
-
-/**
* Menu callback. Saves a string translation coming as POST data.
*/
function i18n_string_l10n_client_save_string() {
@@ -244,18 +235,29 @@ function i18n_string_l10n_client_save_string() {
if (user_access('use on-page translation')) {
$textgroup = !empty($_POST['textgroup']) ? $_POST['textgroup'] : 'default';
- // Default textgroup will be handled by l10n_client module
- if ($textgroup == 'default') {
+ // Other textgroups will be handled by l10n_client module
+ if (!i18n_string_group_info($textgroup)) {
return l10n_client_save_string();
}
elseif (isset($_POST['source']) && isset($_POST['target']) && !empty($_POST['context']) && !empty($_POST['form_token']) && drupal_valid_token($_POST['form_token'], 'l10n_client_form')) {
$name = $textgroup . ':' . $_POST['context'];
- $result = i18n_string_translation_update($name, $_POST['target'], $language->language, $_POST['source']);
- if ($result) {
- $message = theme('l10n_client_message', array('message' => t('Translation saved locally for user defined string.'), 'level' => WATCHDOG_INFO));
- }
- elseif ($result === FALSE) {
- $message = theme('l10n_client_message', array('message' => t('Not saved due to insufficient permissions.')));
+ if ($i18nstring = i18n_string_get_source($name)) {
+ // Since this is not a real form, we double check access permissions here too.
+ if ($error = $i18nstring->check_translate_access()) {
+ $message = theme('l10n_client_message', array('message' => t('Not saved due to: !reason', array('!reason' => $error)), 'level' => WATCHDOG_WARNING));
+ }
+ else {
+ $result = i18n_string_translation_update($name, $_POST['target'], $language->language, $_POST['source']);
+ if ($result) {
+ $message = theme('l10n_client_message', array('message' => t('Translation saved locally for user defined string.'), 'level' => WATCHDOG_INFO));
+ }
+ elseif ($result === FALSE) {
+ $message = theme('l10n_client_message', array('message' => t('Not saved due to insufficient permissions.')));
+ }
+ else {
+ $message = theme('l10n_client_message', array('message' => t('Not saved due to unknown reason.')));
+ }
+ }
}
else {
$message = theme('l10n_client_message', array('message' => t('Not saved due to source string missing.')));
@@ -268,3 +270,166 @@ function i18n_string_l10n_client_save_string() {
exit;
}
}
+
+
+/**
+ * User interface for string editing.
+ */
+function i18n_string_locale_translate_edit_form($form, &$form_state, $lid) {
+ // Fetch source string, if possible.
+ $source = db_query('SELECT source, context, textgroup, location FROM {locales_source} WHERE lid = :lid', array(':lid' => $lid))->fetchObject();
+ if (!$source) {
+ drupal_set_message(t('String not found.'), 'error');
+ drupal_goto('admin/config/regional/translate/translate');
+ }
+
+ // Add original text to the top and some values for form altering.
+ $form['original'] = array(
+ '#type' => 'item',
+ '#title' => t('Original text'),
+ '#markup' => check_plain(wordwrap($source->source, 0)),
+ );
+ if (!empty($source->context)) {
+ $form['context'] = array(
+ '#type' => 'item',
+ '#title' => t('Context'),
+ '#markup' => check_plain($source->context),
+ );
+ }
+ $form['lid'] = array(
+ '#type' => 'value',
+ '#value' => $lid
+ );
+ $form['textgroup'] = array(
+ '#type' => 'value',
+ '#value' => $source->textgroup,
+ );
+ $form['location'] = array(
+ '#type' => 'value',
+ '#value' => $source->location
+ );
+
+ // Include default form controls with empty values for all languages.
+ // This ensures that the languages are always in the same order in forms.
+ $languages = language_list();
+
+ // We don't need the default language value, that value is in $source.
+ $omit = $source->textgroup == 'default' ? 'en' : i18n_string_source_language();
+ unset($languages[($omit)]);
+ $form['translations'] = array('#tree' => TRUE);
+ // Approximate the number of rows to use in the default textarea.
+ $rows = min(ceil(str_word_count($source->source) / 12), 10);
+ foreach ($languages as $langcode => $language) {
+ $form['translations'][$langcode] = array(
+ '#type' => 'textarea',
+ '#title' => t($language->name),
+ '#rows' => $rows,
+ '#default_value' => '',
+ );
+ }
+
+ // Fetch translations and fill in default values in the form.
+ $result = db_query("SELECT DISTINCT translation, language FROM {locales_target} WHERE lid = :lid AND language <> :omit", array(':lid' => $lid, ':omit' => $omit));
+ foreach ($result as $translation) {
+ $form['translations'][$translation->language]['#default_value'] = $translation->translation;
+ }
+
+ $form['actions'] = array('#type' => 'actions');
+ $form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Save translations'));
+
+ // Restrict filter permissions and handle validation and submission for i18n strings
+ $context = db_select('i18n_string', 'i18ns')
+ ->fields('i18ns')
+ ->condition('lid', $form['lid']['#value'])
+ ->execute()
+ ->fetchObject();
+
+ if (i18n_string_group_info($source->textgroup)) {
+ if ($i18nstring = i18n_string_get_by_lid($form['lid']['#value'])) {
+ $form['i18n_string'] = array('#type' => 'value', '#value' => $i18nstring);
+ if ($message = $i18nstring->check_translate_access()) {
+ drupal_set_message($message);
+ $disabled = TRUE;
+ }
+ // Add format help anyway, though the form may be disabled.
+ $form['translations']['format_help']['#markup'] = _i18n_string_translate_format_help($i18nstring->format);
+ }
+ else {
+ drupal_set_message(t('Source string not found.'), 'warning');
+ $disabled = TRUE;
+ }
+ if (!empty($disabled)) {
+ // Disable all form elements
+ $form['submit']['#disabled'] = TRUE;
+ foreach (element_children($form['translations']) as $langcode) {
+ $form['translations'][$langcode]['#disabled'] = TRUE;
+ }
+ }
+ }
+ return $form;
+}
+
+/**
+ * Process string editing form validations.
+ *
+ * If it is an allowed format, skip default validation, the text will be filtered later
+ */
+function i18n_string_locale_translate_edit_form_validate($form, &$form_state) {
+ if (empty($form_state['values']['i18n_string'])) {
+ // If not i18n string use regular locale validation.
+ $copy_state = $form_state;
+ locale_translate_edit_form_validate($form, $copy_state);
+ }
+}
+
+/**
+ * Process string editing form submissions.
+ *
+ * Mark translations as current.
+ */
+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'];
+ foreach ($form_state['values']['translations'] as $key => $value) {
+ if (!empty($value)) {
+ // An update has been made, so we assume the translation is now current.
+ db_update('locales_target')
+ ->fields(array('i18n_status' => I18N_STRING_STATUS_CURRENT))
+ ->condition('lid', $lid)
+ ->condition('language', $key)
+ ->execute();
+ }
+ }
+}
+
+/**
+ * Help for text format.
+ */
+function _i18n_string_translate_format_help($format_id) {
+ $output = '';
+ if ($format = filter_format_load($format_id)) {
+ $title = t('Text format: @name', array('@name' => $format->name));
+ $tips = theme('filter_tips', array('tips' => _filter_tips($format_id, FALSE)));
+ }
+ elseif ($format_id == I18N_STRING_FILTER_XSS) {
+ $title = t('Standard XSS filter.');
+ $allowed_html = ' -
-
- ';
+ $tips[] = t('Allowed HTML tags: @tags', array('@tags' => $allowed_html));
+ }
+ elseif ($format_id == I18N_STRING_FILTER_XSS_ADMIN) {
+ $title = t('Administration XSS filter.');
+ $tips[] = t('It will allow most HTML tags but not scripts nor styles.');
+ }
+ elseif ($format_id) {
+ $title = t('Unknown filter: @name', array('@name' => $format_id));
+ }
+
+ if (!empty($title)) {
+ $output .= '
' . $title . '
';
+ }
+ if (!empty($tips)) {
+ $output .= is_array($tips) ? theme('item_list', array('items' => $tips)) : $tips;
+ }
+ return $output;
+}
\ No newline at end of file