2011-08-27 22:04:48 +00:00
< ? php
/**
* @ file
* Mass import - export and batch import functionality for Gettext . po files .
*/
2012-07-26 22:07:25 +00:00
use Drupal\Component\Gettext\PoStreamWriter ;
use Drupal\locale\Gettext ;
use Drupal\locale\PoDatabaseReader ;
2012-08-26 17:01:29 +00:00
use Drupal\Core\Language\Language ;
2013-06-11 21:12:01 +00:00
use Symfony\Component\HttpFoundation\BinaryFileResponse ;
2013-06-15 08:46:11 +00:00
use Drupal\file\FileInterface ;
2011-08-27 22:04:48 +00:00
/**
2012-10-05 15:42:46 +00:00
* Form constructor for the translation import screen .
*
* @ see locale_translate_import_form_submit ()
* @ ingroup forms
2011-08-27 22:04:48 +00:00
*/
2011-12-22 11:26:12 +00:00
function locale_translate_import_form ( $form , & $form_state ) {
2011-08-27 22:04:48 +00:00
drupal_static_reset ( 'language_list' );
2012-04-25 23:44:20 +00:00
$languages = language_list ();
2012-01-23 15:46:29 +00:00
// Initialize a language list to the ones available, including English if we
// are to translate Drupal to English as well.
$existing_languages = array ();
foreach ( $languages as $langcode => $language ) {
if ( $langcode != 'en' || locale_translate_english ()) {
$existing_languages [ $langcode ] = $language -> name ;
}
2011-10-27 03:55:02 +00:00
}
2011-08-27 22:04:48 +00:00
2012-01-23 15:46:29 +00:00
// If we have no languages available, present the list of predefined languages
// only. If we do have already added languages, set up two option groups with
// the list of existing and then predefined languages.
2011-12-22 11:26:12 +00:00
form_load_include ( $form_state , 'inc' , 'language' , 'language.admin' );
2012-01-23 15:46:29 +00:00
if ( empty ( $existing_languages )) {
$language_options = language_admin_predefined_list ();
$default = key ( $language_options );
2011-08-27 22:04:48 +00:00
}
else {
2012-01-23 15:46:29 +00:00
$default = key ( $existing_languages );
$language_options = array (
2012-04-09 18:24:12 +00:00
t ( 'Existing languages' ) => $existing_languages ,
2011-12-22 11:26:12 +00:00
t ( 'Languages not yet added' ) => language_admin_predefined_list ()
2011-08-27 22:04:48 +00:00
);
}
2012-09-01 01:59:54 +00:00
$validators = array (
'file_validate_extensions' => array ( 'po' ),
'file_validate_size' => array ( file_upload_max_size ()),
);
2012-04-09 18:24:12 +00:00
$form [ 'file' ] = array (
'#type' => 'file' ,
'#title' => t ( 'Translation file' ),
2012-09-01 01:59:54 +00:00
'#description' => theme ( 'file_upload_help' , array ( 'description' => t ( 'A Gettext Portable Object file.' ), 'upload_validators' => $validators )),
2011-08-27 22:04:48 +00:00
'#size' => 50 ,
2012-09-01 01:59:54 +00:00
'#upload_validators' => $validators ,
'#attributes' => array ( 'class' => array ( 'file-import-input' )),
'#attached' => array (
'js' => array (
drupal_get_path ( 'module' , 'locale' ) . '/locale.bulk.js' => array (),
),
),
2011-08-27 22:04:48 +00:00
);
2012-04-09 18:24:12 +00:00
$form [ 'langcode' ] = array (
'#type' => 'select' ,
'#title' => t ( 'Language' ),
2012-01-23 15:46:29 +00:00
'#options' => $language_options ,
2011-08-27 22:04:48 +00:00
'#default_value' => $default ,
2012-09-01 01:59:54 +00:00
'#attributes' => array ( 'class' => array ( 'langcode-input' )),
2011-08-27 22:04:48 +00:00
);
2012-04-09 18:24:12 +00:00
$form [ 'customized' ] = array (
'#title' => t ( 'Treat imported strings as custom translations' ),
'#type' => 'checkbox' ,
);
$form [ 'overwrite_options' ] = array (
'#type' => 'container' ,
'#tree' => TRUE ,
);
$form [ 'overwrite_options' ][ 'not_customized' ] = array (
'#title' => t ( 'Overwrite non-customized translations' ),
'#type' => 'checkbox' ,
'#states' => array (
'checked' => array (
2013-04-23 07:19:41 +00:00
':input[name="customized"]' => array ( 'checked' => TRUE ),
2012-04-09 18:24:12 +00:00
),
2011-08-27 22:04:48 +00:00
),
);
2012-04-09 18:24:12 +00:00
$form [ 'overwrite_options' ][ 'customized' ] = array (
'#title' => t ( 'Overwrite existing customized translations' ),
'#type' => 'checkbox' ,
);
2011-08-27 22:04:48 +00:00
2012-04-09 18:24:12 +00:00
$form [ 'actions' ] = array (
'#type' => 'actions'
);
$form [ 'actions' ][ 'submit' ] = array (
'#type' => 'submit' ,
'#value' => t ( 'Import' )
);
2011-08-27 22:04:48 +00:00
return $form ;
}
/**
2012-10-05 15:42:46 +00:00
* Form submission handler for locale_translate_import_form () .
2011-08-27 22:04:48 +00:00
*/
function locale_translate_import_form_submit ( $form , & $form_state ) {
2012-04-09 18:24:12 +00:00
// Ensure we have the file uploaded.
2013-04-20 03:34:14 +00:00
if ( $file = file_save_upload ( 'file' , $form [ 'file' ][ '#upload_validators' ], 'translations://' , 0 )) {
2011-08-27 22:04:48 +00:00
2012-04-09 18:24:12 +00:00
// Add language, if not yet supported.
$language = language_load ( $form_state [ 'values' ][ 'langcode' ]);
if ( empty ( $language )) {
2012-08-26 17:01:29 +00:00
$language = new Language ( array (
'langcode' => $form_state [ 'values' ][ 'langcode' ]
));
2012-04-09 18:24:12 +00:00
$language = language_save ( $language );
drupal_set_message ( t ( 'The language %language has been created.' , array ( '%language' => t ( $language -> name ))));
2011-08-27 22:04:48 +00:00
}
2012-08-19 10:46:48 +00:00
$options = array (
'langcode' => $form_state [ 'values' ][ 'langcode' ],
'overwrite_options' => $form_state [ 'values' ][ 'overwrite_options' ],
'customized' => $form_state [ 'values' ][ 'customized' ] ? LOCALE_CUSTOMIZED : LOCALE_NOT_CUSTOMIZED ,
);
2012-12-07 18:17:37 +00:00
$file = locale_translate_file_attach_properties ( $file , $options );
2012-08-19 10:46:48 +00:00
$batch = locale_translate_batch_build ( array ( $file -> uri => $file ), $options );
batch_set ( $batch );
2011-08-27 22:04:48 +00:00
}
else {
2012-09-01 01:59:54 +00:00
form_set_error ( 'file' , t ( 'File to import not found.' ));
$form_state [ 'rebuild' ] = TRUE ;
2011-08-27 22:04:48 +00:00
return ;
}
$form_state [ 'redirect' ] = 'admin/config/regional/translate' ;
return ;
}
/**
2012-10-05 15:42:46 +00:00
* Form constructor for the Gettext translation files export form .
*
* @ see locale_translate_export_form_submit ()
* @ ingroup forms
2011-08-27 22:04:48 +00:00
*/
2012-04-09 18:24:12 +00:00
function locale_translate_export_form ( $form , & $form_state ) {
2012-04-25 23:44:20 +00:00
$languages = language_list ();
2012-01-23 15:46:29 +00:00
$language_options = array ();
foreach ( $languages as $langcode => $language ) {
if ( $langcode != 'en' || locale_translate_english ()) {
$language_options [ $langcode ] = $language -> name ;
}
2011-10-27 03:55:02 +00:00
}
2012-04-09 18:24:12 +00:00
$language_default = language_default ();
2012-01-23 15:46:29 +00:00
2012-04-09 18:24:12 +00:00
if ( empty ( $language_options )) {
$form [ 'langcode' ] = array (
'#type' => 'value' ,
2013-05-25 20:12:45 +00:00
'#value' => Language :: LANGCODE_SYSTEM ,
2012-04-09 18:24:12 +00:00
);
$form [ 'langcode_text' ] = array (
'#type' => 'item' ,
'#title' => t ( 'Language' ),
'#markup' => t ( 'No language available. The export will only contain source strings.' ),
);
}
else {
$form [ 'langcode' ] = array (
'#type' => 'select' ,
'#title' => t ( 'Language' ),
'#options' => $language_options ,
'#default_value' => $language_default -> langcode ,
'#empty_option' => t ( 'Source text only, no translations' ),
2013-05-25 20:12:45 +00:00
'#empty_value' => Language :: LANGCODE_SYSTEM ,
2012-04-09 18:24:12 +00:00
);
$form [ 'content_options' ] = array (
2012-11-27 07:06:47 +00:00
'#type' => 'details' ,
2012-04-09 18:24:12 +00:00
'#title' => t ( 'Export options' ),
2013-05-29 21:08:39 +00:00
'#collapsed' => TRUE ,
2012-04-09 18:24:12 +00:00
'#tree' => TRUE ,
'#states' => array (
'invisible' => array (
2013-05-25 20:12:45 +00:00
':input[name="langcode"]' => array ( 'value' => Language :: LANGCODE_SYSTEM ),
2012-04-09 18:24:12 +00:00
),
),
);
$form [ 'content_options' ][ 'not_customized' ] = array (
'#type' => 'checkbox' ,
'#title' => t ( 'Include non-customized translations' ),
'#default_value' => TRUE ,
);
$form [ 'content_options' ][ 'customized' ] = array (
'#type' => 'checkbox' ,
'#title' => t ( 'Include customized translations' ),
'#default_value' => TRUE ,
);
$form [ 'content_options' ][ 'not_translated' ] = array (
'#type' => 'checkbox' ,
'#title' => t ( 'Include untranslated text' ),
'#default_value' => TRUE ,
);
2011-08-27 22:04:48 +00:00
}
2012-04-09 18:24:12 +00:00
$form [ 'actions' ] = array (
'#type' => 'actions'
2011-08-27 22:04:48 +00:00
);
2012-04-09 18:24:12 +00:00
$form [ 'actions' ][ 'submit' ] = array (
'#type' => 'submit' ,
'#value' => t ( 'Export' )
2011-08-27 22:04:48 +00:00
);
return $form ;
}
/**
2012-10-05 15:42:46 +00:00
* Form submission handler for locale_translate_export_form () .
2011-08-27 22:04:48 +00:00
*/
2012-04-09 18:24:12 +00:00
function locale_translate_export_form_submit ( $form , & $form_state ) {
2011-08-27 22:04:48 +00:00
// If template is required, language code is not given.
2013-05-25 20:12:45 +00:00
if ( $form_state [ 'values' ][ 'langcode' ] != Language :: LANGCODE_SYSTEM ) {
2012-04-09 18:24:12 +00:00
$language = language_load ( $form_state [ 'values' ][ 'langcode' ]);
}
else {
$language = NULL ;
2011-08-27 22:04:48 +00:00
}
2012-04-09 18:24:12 +00:00
$content_options = isset ( $form_state [ 'values' ][ 'content_options' ]) ? $form_state [ 'values' ][ 'content_options' ] : array ();
2012-07-26 22:07:25 +00:00
$reader = new PoDatabaseReader ();
$languageName = '' ;
if ( $language != NULL ) {
$reader -> setLangcode ( $language -> langcode );
$reader -> setOptions ( $content_options );
$languages = language_list ();
$languageName = isset ( $languages [ $language -> langcode ]) ? $languages [ $language -> langcode ] -> name : '' ;
$filename = $language -> langcode . '.po' ;
}
else {
// Template required.
$filename = 'drupal.pot' ;
}
$item = $reader -> readItem ();
if ( ! empty ( $item )) {
$uri = tempnam ( 'temporary://' , 'po_' );
$header = $reader -> getHeader ();
2012-08-26 16:56:24 +00:00
$header -> setProjectName ( config ( 'system.site' ) -> get ( 'name' ));
2012-07-26 22:07:25 +00:00
$header -> setLanguageName ( $languageName );
$writer = new PoStreamWriter ;
$writer -> setUri ( $uri );
$writer -> setHeader ( $header );
$writer -> open ();
$writer -> writeItem ( $item );
$writer -> writeItems ( $reader );
$writer -> close ();
2013-06-11 21:12:01 +00:00
$response = new BinaryFileResponse ( $uri );
$response -> setContentDisposition ( 'attachment' , $filename );
// @todo remove lines below once converted to new routing system.
$response -> prepare ( Drupal :: request ())
-> send ();
2012-07-26 22:07:25 +00:00
}
else {
drupal_set_message ( 'Nothing to export.' );
}
2011-08-27 22:04:48 +00:00
}
/**
2011-10-29 11:40:09 +00:00
* Prepare a batch to import all translations .
2011-08-27 22:04:48 +00:00
*
2012-08-19 10:46:48 +00:00
* @ param array $options
* An array with options that can have the following elements :
* - 'langcode' : The language code . Optional , defaults to NULL , which means
* that the language will be detected from the name of the files .
* - 'overwrite_options' : Overwrite options array as defined in
* Drupal\locale\PoDatabaseWriter . Optional , defaults to an empty array .
* - 'customized' : Flag indicating whether the strings imported from $file
* are customized translations or come from a community source . Use
* LOCALE_CUSTOMIZED or LOCALE_NOT_CUSTOMIZED . Optional , defaults to
* LOCALE_NOT_CUSTOMIZED .
* - 'finish_feedback' : Whether or not to give feedback to the user when the
* batch is finished . Optional , defaults to TRUE .
*
2012-06-16 15:16:07 +00:00
* @ param $force
* ( optional ) Import all available files , even if they were imported before .
2011-08-27 22:04:48 +00:00
*
2011-10-29 11:40:09 +00:00
* @ todo
* Integrate with update status to identify projects needed and integrate
* l10n_update functionality to feed in translation files alike .
* See http :// drupal . org / node / 1191488.
2011-08-27 22:04:48 +00:00
*/
2012-08-19 10:46:48 +00:00
function locale_translate_batch_import_files ( $options , $force = FALSE ) {
$options += array (
'overwrite_options' => array (),
'customized' => LOCALE_NOT_CUSTOMIZED ,
'finish_feedback' => TRUE ,
);
2012-12-07 18:17:37 +00:00
2012-08-19 10:46:48 +00:00
if ( ! empty ( $options [ 'langcode' ])) {
$langcodes = array ( $options [ 'langcode' ]);
2011-11-25 06:22:29 +00:00
}
else {
// If langcode was not provided, make sure to only import files for the
// languages we have enabled.
$langcodes = array_keys ( language_list ());
}
2012-12-07 18:17:37 +00:00
$files = locale_translate_get_interface_translation_files ( array (), $langcodes );
2012-06-16 15:16:07 +00:00
if ( ! $force ) {
$result = db_select ( 'locale_file' , 'lf' )
-> fields ( 'lf' , array ( 'langcode' , 'uri' , 'timestamp' ))
-> condition ( 'langcode' , $langcodes )
-> execute ()
-> fetchAllAssoc ( 'uri' );
foreach ( $result as $uri => $info ) {
if ( isset ( $files [ $uri ]) && filemtime ( $uri ) <= $info -> timestamp ) {
2012-07-26 22:07:25 +00:00
// The file is already imported and not changed since the last import.
2012-06-16 15:16:07 +00:00
// Remove it from file list and don't import it again.
unset ( $files [ $uri ]);
}
}
2011-11-25 06:22:29 +00:00
}
2012-08-19 10:46:48 +00:00
return locale_translate_batch_build ( $files , $options );
2011-08-27 22:04:48 +00:00
}
2012-06-16 15:16:07 +00:00
/**
2012-12-07 18:17:37 +00:00
* Get interface translation files present in the translations directory .
2012-06-16 15:16:07 +00:00
*
2012-12-07 18:17:37 +00:00
* @ param array $projects
* Project names from which to get the translation files and history .
* Defaults to all projects .
* @ param array $langcodes
* Language codes from which to get the translation files and history .
* Defaults to all languagues
2012-06-16 15:16:07 +00:00
*
* @ return array
2012-12-07 18:17:37 +00:00
* An array of interface translation files keyed by their URI .
2012-06-16 15:16:07 +00:00
*/
2012-12-07 18:17:37 +00:00
function locale_translate_get_interface_translation_files ( $projects = array (), $langcodes = array ()) {
module_load_include ( 'compare.inc' , 'locale' );
$files = array ();
$projects = $projects ? $projects : array_keys ( locale_translation_get_projects ());
$langcodes = $langcodes ? $langcodes : array_keys ( locale_translatable_language_list ());
// Scan the translations directory for files matching a name pattern
// containing a project name and language code: {project}.{langcode}.po or
// {project}-{version}.{langcode}.po.
// Only files of known projects and languages will be returned.
Issue #1496480 by mrf, disasm, kbasarab, pfrenssen, cam8001, typhonius, Letharion, ACF, vijaycs85, heyrocker, Berdir, alexpott: Convert file system settings to the new configuration system.
2013-02-08 23:36:06 +00:00
$directory = config ( 'locale.settings' ) -> get ( 'translation.path' );
$result = file_scan_directory ( $directory , '![a-z_]+(\-[0-9a-z\.\-\+]+|)\.[^\./]+\.po$!' , array ( 'recurse' => FALSE ));
2012-12-07 18:17:37 +00:00
Issue #1496480 by mrf, disasm, kbasarab, pfrenssen, cam8001, typhonius, Letharion, ACF, vijaycs85, heyrocker, Berdir, alexpott: Convert file system settings to the new configuration system.
2013-02-08 23:36:06 +00:00
foreach ( $result as $file ) {
// Update the file object with project name and version from the file name.
$file = locale_translate_file_attach_properties ( $file );
if ( in_array ( $file -> project , $projects )) {
if ( in_array ( $file -> langcode , $langcodes )) {
$files [ $file -> uri ] = $file ;
2012-12-07 18:17:37 +00:00
}
}
2012-09-07 06:26:10 +00:00
}
Issue #1496480 by mrf, disasm, kbasarab, pfrenssen, cam8001, typhonius, Letharion, ACF, vijaycs85, heyrocker, Berdir, alexpott: Convert file system settings to the new configuration system.
2013-02-08 23:36:06 +00:00
2012-12-07 18:17:37 +00:00
return $files ;
2012-06-16 15:16:07 +00:00
}
2011-08-27 22:04:48 +00:00
/**
* Build a locale batch from an array of files .
*
* @ param $files
2011-10-29 11:40:09 +00:00
* Array of file objects to import .
2012-08-19 10:46:48 +00:00
*
* @ param array $options
* An array with options that can have the following elements :
* - 'langcode' : The language code . Optional , defaults to NULL , which means
* that the language will be detected from the name of the files .
* - 'overwrite_options' : Overwrite options array as defined in
* Drupal\locale\PoDatabaseWriter . Optional , defaults to an empty array .
* - 'customized' : Flag indicating whether the strings imported from $file
* are customized translations or come from a community source . Use
* LOCALE_CUSTOMIZED or LOCALE_NOT_CUSTOMIZED . Optional , defaults to
* LOCALE_NOT_CUSTOMIZED .
* - 'finish_feedback' : Whether or not to give feedback to the user when the
* batch is finished . Optional , defaults to TRUE .
2011-08-27 22:04:48 +00:00
*
* @ return
2011-10-29 11:40:09 +00:00
* A batch structure or FALSE if $files was empty .
2011-08-27 22:04:48 +00:00
*/
2012-08-19 10:46:48 +00:00
function locale_translate_batch_build ( $files , $options ) {
$options += array (
'overwrite_options' => array (),
'customized' => LOCALE_NOT_CUSTOMIZED ,
'finish_feedback' => TRUE ,
);
2011-08-27 22:04:48 +00:00
$t = get_t ();
if ( count ( $files )) {
$operations = array ();
foreach ( $files as $file ) {
2011-10-29 11:40:09 +00:00
// We call locale_translate_batch_import for every batch operation.
2012-12-07 18:17:37 +00:00
$operations [] = array ( 'locale_translate_batch_import' , array ( $file , $options ));
2011-08-27 22:04:48 +00:00
}
2012-12-07 18:17:37 +00:00
// Save the translation status of all files.
$operations [] = array ( 'locale_translate_batch_import_save' , array ());
2013-04-23 07:19:41 +00:00
// Add a final step to refresh JavaScript and configuration strings.
$operations [] = array ( 'locale_translate_batch_refresh' , array ());
2011-08-27 22:04:48 +00:00
$batch = array (
'operations' => $operations ,
'title' => $t ( 'Importing interface translations' ),
2012-12-17 21:45:18 +00:00
'progress_message' => '' ,
2011-08-27 22:04:48 +00:00
'error_message' => $t ( 'Error importing interface translations' ),
'file' => drupal_get_path ( 'module' , 'locale' ) . '/locale.bulk.inc' ,
);
2012-08-19 10:46:48 +00:00
if ( $options [ 'finish_feedback' ]) {
2011-10-29 11:40:09 +00:00
$batch [ 'finished' ] = 'locale_translate_batch_finished' ;
2011-08-27 22:04:48 +00:00
}
return $batch ;
}
return FALSE ;
}
/**
* Perform interface translation import as a batch step .
*
2012-12-07 18:17:37 +00:00
* @ param object $file
* A file object of the gettext file to be imported . The file object must
2013-05-25 20:12:45 +00:00
* contain a language parameter ( other than Language :: LANGCODE_NOT_SPECIFIED ) . This
2012-12-07 18:17:37 +00:00
* is used as the language of the import .
2012-08-19 10:46:48 +00:00
*
* @ param array $options
* An array with options that can have the following elements :
2012-12-07 18:17:37 +00:00
* - 'langcode' : The language code .
2012-08-19 10:46:48 +00:00
* - 'overwrite_options' : Overwrite options array as defined in
* Drupal\locale\PoDatabaseWriter . Optional , defaults to an empty array .
* - 'customized' : Flag indicating whether the strings imported from $file
* are customized translations or come from a community source . Use
* LOCALE_CUSTOMIZED or LOCALE_NOT_CUSTOMIZED . Optional , defaults to
* LOCALE_NOT_CUSTOMIZED .
2012-12-17 21:45:18 +00:00
* - 'message' : Alternative message to display during import . Note , this must
* be sanitized text .
2012-08-19 10:46:48 +00:00
*
2012-07-26 22:07:25 +00:00
* @ param $context
2011-08-27 22:04:48 +00:00
* Contains a list of files imported .
*/
2012-12-07 18:17:37 +00:00
function locale_translate_batch_import ( $file , $options , & $context ) {
2012-08-19 10:46:48 +00:00
// Merge the default values in the $options array.
$options += array (
'overwrite_options' => array (),
'customized' => LOCALE_NOT_CUSTOMIZED ,
);
2012-12-07 18:17:37 +00:00
2013-05-25 20:12:45 +00:00
if ( isset ( $file -> langcode ) && $file -> langcode != Language :: LANGCODE_NOT_SPECIFIED ) {
2012-12-07 18:17:37 +00:00
2012-07-26 22:07:25 +00:00
try {
2012-08-19 10:46:48 +00:00
if ( empty ( $context [ 'sandbox' ])) {
$context [ 'sandbox' ][ 'parse_state' ] = array (
2012-09-07 06:26:10 +00:00
'filesize' => filesize ( drupal_realpath ( $file -> uri )),
2012-08-19 10:46:48 +00:00
'chunk_size' => 200 ,
'seek' => 0 ,
);
}
// Update the seek and the number of items in the $options array().
$options [ 'seek' ] = $context [ 'sandbox' ][ 'parse_state' ][ 'seek' ];
$options [ 'items' ] = $context [ 'sandbox' ][ 'parse_state' ][ 'chunk_size' ];
$report = GetText :: fileToDatabase ( $file , $options );
// If not yet finished with reading, mark progress based on size and
// position.
if ( $report [ 'seek' ] < filesize ( $file -> uri )) {
2012-12-07 18:17:37 +00:00
2012-08-19 10:46:48 +00:00
$context [ 'sandbox' ][ 'parse_state' ][ 'seek' ] = $report [ 'seek' ];
// Maximize the progress bar at 95% before completion, the batch API
// could trigger the end of the operation before file reading is done,
// because of floating point inaccuracies. See
// http://drupal.org/node/1089472
$context [ 'finished' ] = min ( 0.95 , $report [ 'seek' ] / filesize ( $file -> uri ));
2012-12-07 18:17:37 +00:00
if ( isset ( $options [ 'message' ])) {
2012-12-17 21:45:18 +00:00
$context [ 'message' ] = t ( '!message (@percent%).' , array ( '!message' => $options [ 'message' ], '@percent' => ( int ) ( $context [ 'finished' ] * 100 )));
2012-12-07 18:17:37 +00:00
}
else {
$context [ 'message' ] = t ( 'Importing translation file: %filename (@percent%).' , array ( '%filename' => $file -> filename , '@percent' => ( int ) ( $context [ 'finished' ] * 100 )));
}
2012-08-19 10:46:48 +00:00
}
else {
// We are finished here.
$context [ 'finished' ] = 1 ;
2012-12-07 18:17:37 +00:00
// Store the file data for processing by the next batch operation.
2012-08-19 10:46:48 +00:00
$file -> timestamp = filemtime ( $file -> uri );
2012-12-07 18:17:37 +00:00
$context [ 'results' ][ 'files' ][ $file -> uri ] = $file ;
$context [ 'results' ][ 'languages' ][ $file -> uri ] = $file -> langcode ;
2012-08-19 10:46:48 +00:00
}
2012-12-07 18:17:37 +00:00
// Add the reported values to the statistics for this file.
// Each import iteration reports statistics in an array. The results of
// each iteration are added and merged here and stored per file.
if ( ! isset ( $context [ 'results' ][ 'stats' ]) || ! isset ( $context [ 'results' ][ 'stats' ][ $file -> uri ])) {
$context [ 'results' ][ 'stats' ][ $file -> uri ] = array ();
2012-08-19 10:46:48 +00:00
}
foreach ( $report as $key => $value ) {
2012-12-07 18:17:37 +00:00
if ( is_numeric ( $report [ $key ])) {
if ( ! isset ( $context [ 'results' ][ 'stats' ][ $file -> uri ][ $key ])) {
$context [ 'results' ][ 'stats' ][ $file -> uri ][ $key ] = 0 ;
}
$context [ 'results' ][ 'stats' ][ $file -> uri ][ $key ] += $report [ $key ];
2012-10-23 10:25:45 +00:00
}
elseif ( is_array ( $value )) {
2012-12-07 18:17:37 +00:00
$context [ 'results' ][ 'stats' ][ $file -> uri ] += array ( $key => array ());
$context [ 'results' ][ 'stats' ][ $file -> uri ][ $key ] = array_merge ( $context [ 'results' ][ 'stats' ][ $file -> uri ][ $key ], $value );
2012-08-19 10:46:48 +00:00
}
}
2012-08-18 10:36:45 +00:00
}
catch ( Exception $exception ) {
2012-12-07 18:17:37 +00:00
// Import failed. Store the data of the failing file.
$context [ 'results' ][ 'failed_files' ][] = $file ;
watchdog ( 'locale' , 'Unable to import translations file: @file' , array ( '@file' => $file -> uri ));
}
}
}
/**
* Batch callback : Save data of imported files .
*
* @ param $context
* Contains a list of imported files .
*/
function locale_translate_batch_import_save ( $context ) {
if ( isset ( $context [ 'results' ][ 'files' ])) {
foreach ( $context [ 'results' ][ 'files' ] as $file ) {
// Update the file history if both project and version are known. This
// table is used by the automated translation update function which tracks
// translation status of module and themes in the system. Other
// translation files are not tracked and are therefore not stored in this
// table.
if ( $file -> project && $file -> version ) {
$file -> last_checked = REQUEST_TIME ;
locale_translation_update_file_history ( $file );
}
2012-06-16 15:16:07 +00:00
}
2012-12-17 21:45:18 +00:00
$context [ 'message' ] = t ( 'Translations imported.' );
2011-08-27 22:04:48 +00:00
}
}
2013-04-23 07:19:41 +00:00
/**
* Refreshs translations after importing strings .
*
* @ param array $context
* Contains a list of strings updated and information about the progress .
*/
function locale_translate_batch_refresh ( array & $context ) {
if ( ! isset ( $context [ 'sandbox' ][ 'refresh' ])) {
$strings = $langcodes = array ();
if ( isset ( $context [ 'results' ][ 'stats' ])) {
// Get list of unique string identifiers and language codes updated.
$langcodes = array_unique ( array_values ( $context [ 'results' ][ 'languages' ]));
foreach ( $context [ 'results' ][ 'stats' ] as $filepath => $report ) {
$strings = array_merge ( $strings , $report [ 'strings' ]);
}
}
if ( $strings ) {
// Initialize multi-step string refresh.
$context [ 'message' ] = t ( 'Updating translations for JavaScript and configuration strings.' );
$context [ 'sandbox' ][ 'refresh' ][ 'strings' ] = array_unique ( $strings );
$context [ 'sandbox' ][ 'refresh' ][ 'languages' ] = $langcodes ;
$context [ 'sandbox' ][ 'refresh' ][ 'names' ] = array ();
$context [ 'results' ][ 'stats' ][ 'config' ] = 0 ;
$context [ 'sandbox' ][ 'refresh' ][ 'count' ] = count ( $strings );
// We will update strings on later steps.
$context [ 'finished' ] = 1 - 1 / $context [ 'sandbox' ][ 'refresh' ][ 'count' ];
}
else {
$context [ 'finished' ] = 1 ;
}
}
elseif ( $name = array_shift ( $context [ 'sandbox' ][ 'refresh' ][ 'names' ])) {
// Refresh all languages for one object at a time.
$count = locale_config_update_multiple ( array ( $name ), $context [ 'sandbox' ][ 'refresh' ][ 'languages' ]);
$context [ 'results' ][ 'stats' ][ 'config' ] += $count ;
}
elseif ( ! empty ( $context [ 'sandbox' ][ 'refresh' ][ 'strings' ])) {
// Not perfect but will give some indication of progress.
$context [ 'finished' ] = 1 - count ( $context [ 'sandbox' ][ 'refresh' ][ 'strings' ]) / $context [ 'sandbox' ][ 'refresh' ][ 'count' ];
// Pending strings, refresh 100 at a time, get next pack.
$next = array_slice ( $context [ 'sandbox' ][ 'refresh' ][ 'strings' ], 0 , 100 );
array_splice ( $context [ 'sandbox' ][ 'refresh' ][ 'strings' ], 0 , count ( $next ));
// Clear cache and force refresh of JavaScript translations.
_locale_refresh_translations ( $context [ 'sandbox' ][ 'refresh' ][ 'languages' ], $next );
// Check whether we need to refresh configuration objects.
if ( $names = \Drupal\locale\Locale :: config () -> getStringNames ( $next )) {
$context [ 'sandbox' ][ 'refresh' ][ 'names' ] = $names ;
}
}
else {
$context [ 'finished' ] = 1 ;
}
}
2011-08-27 22:04:48 +00:00
/**
* Finished callback of system page locale import batch .
*/
2011-10-29 11:40:09 +00:00
function locale_translate_batch_finished ( $success , $results ) {
2011-08-27 22:04:48 +00:00
if ( $success ) {
2013-04-23 07:19:41 +00:00
$additions = $updates = $deletes = $skips = $config = 0 ;
2012-12-07 18:17:37 +00:00
if ( isset ( $results [ 'failed_files' ])) {
2013-04-23 07:19:41 +00:00
if ( Drupal :: moduleHandler () -> moduleExists ( 'dblog' )) {
$message = format_plural ( count ( $results [ 'failed_files' ]), 'One translation file could not be imported. <a href="@url">See the log</a> for details.' , '@count translation files could not be imported. <a href="@url">See the log</a> for details.' , array ( '@url' => url ( 'admin/reports/dblog' )));
}
else {
$message = format_plural ( count ( $results [ 'failed_files' ]), 'One translation files could not be imported. See the log for details.' , '@count translation files could not be imported. See the log for details.' );
}
drupal_set_message ( $message , 'error' );
2012-07-26 22:07:25 +00:00
}
2012-12-07 18:17:37 +00:00
if ( isset ( $results [ 'files' ])) {
$skipped_files = array ();
// If there are no results and/or no stats (eg. coping with an empty .po
// file), simply do nothing.
if ( $results && isset ( $results [ 'stats' ])) {
foreach ( $results [ 'stats' ] as $filepath => $report ) {
$additions += $report [ 'additions' ];
$updates += $report [ 'updates' ];
$deletes += $report [ 'deletes' ];
$skips += $report [ 'skips' ];
if ( $report [ 'skips' ] > 0 ) {
$skipped_files [] = $filepath ;
}
}
2012-07-26 22:07:25 +00:00
}
2012-12-07 18:17:37 +00:00
drupal_set_message ( format_plural ( count ( $results [ 'files' ]),
'One translation file imported. %number translations were added, %update translations were updated and %delete translations were removed.' ,
'@count translation files imported. %number translations were added, %update translations were updated and %delete translations were removed.' ,
array ( '%number' => $additions , '%update' => $updates , '%delete' => $deletes )
));
watchdog ( 'locale' , 'Translations imported: %number added, %update updated, %delete removed.' , array ( '%number' => $additions , '%update' => $updates , '%delete' => $deletes ));
if ( $skips ) {
if ( module_exists ( 'dblog' )) {
$message = format_plural ( $skips , 'One translation string was skipped because of disallowed or malformed HTML. <a href="@url">See the log</a> for details.' , '@count translation strings were skipped because of disallowed or malformed HTML. <a href="@url">See the log</a> for details.' , array ( '@url' => url ( 'admin/reports/dblog' )));
}
else {
$message = format_plural ( $skips , 'One translation string was skipped because of disallowed or malformed HTML. See the log for details.' , '@count translation strings were skipped because of disallowed or malformed HTML. See the log for details.' );
}
drupal_set_message ( $message , 'warning' );
watchdog ( 'locale' , '@count disallowed HTML string(s) in files: @files.' , array ( '@count' => $skips , '@files' => implode ( ',' , $skipped_files )), WATCHDOG_WARNING );
2012-07-26 22:07:25 +00:00
}
2012-10-23 10:25:45 +00:00
}
2011-08-27 22:04:48 +00:00
}
2013-04-23 07:19:41 +00:00
// Add messages for configuration too.
if ( isset ( $results [ 'stats' ][ 'config' ])) {
locale_config_batch_finished ( $success , $results );
}
2011-08-27 22:04:48 +00:00
}
2012-06-16 15:16:07 +00:00
/**
* Creates a file object and populates the timestamp property .
*
* @ param $filepath
* The filepath of a file to import .
*
* @ return
* An object representing the file .
*/
function locale_translate_file_create ( $filepath ) {
$file = new stdClass ();
$file -> filename = drupal_basename ( $filepath );
$file -> uri = $filepath ;
2012-09-17 23:16:44 +00:00
$file -> timestamp = filemtime ( $file -> uri );
2012-06-16 15:16:07 +00:00
return $file ;
}
/**
2013-04-23 07:19:41 +00:00
* Generates file properties from filename and options .
2012-06-16 15:16:07 +00:00
*
2012-12-07 18:17:37 +00:00
* An attempt is made to determine the translation language , project name and
* project version from the file name . Supported file name patterns are :
* { project } - { version } . { langcode } . po , { prefix } . { langcode } . po or { langcode } . po .
* Alternatively the translation language can be set using the $options .
2012-06-16 15:16:07 +00:00
*
2012-12-07 18:17:37 +00:00
* @ param object $file
* A file object of the gettext file to be imported .
* @ param array $options
* An array with options :
* - 'langcode' : The language code . Overrides the file language .
2012-06-16 15:16:07 +00:00
*
2012-12-07 18:17:37 +00:00
* @ return object
* Modified file object .
2012-06-16 15:16:07 +00:00
*/
2012-12-07 18:17:37 +00:00
function locale_translate_file_attach_properties ( $file , $options = array ()) {
2013-06-15 08:46:11 +00:00
// If $file is a file entity, convert it to a stdClass.
if ( $file instanceof FileInterface ) {
$file = ( object ) array (
'filename' => $file -> getFilename (),
'uri' => $file -> getFileUri (),
);
}
2012-12-07 18:17:37 +00:00
// Extract project, verion and language code from the file name. Supported:
// {project}-{version}.{langcode}.po, {prefix}.{langcode}.po or {langcode}.po
preg_match ( ' !
( # project OR project and version OR emtpy (group 1)
([ a - z_ ] + ) # project name (group 2)
\ . # .
| # OR
([ a - z_ ] + ) # project name (group 3)
\ - # -
([ 0 - 9 a - z\ . \ - \ + ] + ) # version (group 4)
\ . # .
| # OR
) # (empty)
([ ^ \ ./ ] + ) # language code (group 5)
\ . # .
po # po extension
$ ! x ' , $file -> filename , $matches );
if ( isset ( $matches [ 5 ])) {
$file -> project = $matches [ 2 ] . $matches [ 3 ];
$file -> version = $matches [ 4 ];
$file -> langcode = isset ( $options [ 'langcode' ]) ? $options [ 'langcode' ] : $matches [ 5 ];
2012-06-16 15:16:07 +00:00
}
else {
2013-05-25 20:12:45 +00:00
$file -> langcode = Language :: LANGCODE_NOT_SPECIFIED ;
2012-06-16 15:16:07 +00:00
}
2012-12-07 18:17:37 +00:00
return $file ;
2012-06-16 15:16:07 +00:00
}
/**
2012-12-07 18:17:37 +00:00
* Deletes interface translation files and translation history records .
2012-06-16 15:16:07 +00:00
*
2012-12-07 18:17:37 +00:00
* @ param array $projects
* Project names from which to delete the translation files and history .
* Defaults to all projects .
* @ param array $langcodes
* Language codes from which to delete the translation files and history .
* Defaults to all languagues
*
* @ return boolean
* TRUE if files are removed sucessfully . FALSE if one or more files could
* not be deleted .
2012-06-16 15:16:07 +00:00
*/
2012-12-07 18:17:37 +00:00
function locale_translate_delete_translation_files ( $projects = array (), $langcodes = array ()) {
$fail = FALSE ;
locale_translation_file_history_delete ( $projects , $langcodes );
// Delete all translation files from the translations directory.
if ( $files = locale_translate_get_interface_translation_files ( $projects , $langcodes )) {
2012-06-16 15:16:07 +00:00
foreach ( $files as $file ) {
$success = file_unmanaged_delete ( $file -> uri );
if ( ! $success ) {
2012-12-07 18:17:37 +00:00
$fail = TRUE ;
2012-06-16 15:16:07 +00:00
}
}
}
2012-12-07 18:17:37 +00:00
return ! $fail ;
2012-06-16 15:16:07 +00:00
}
2013-04-23 07:19:41 +00:00
/**
* Builds a locale batch to refresh configuration .
*
* @ param array $options
* An array with options that can have the following elements :
* - 'finish_feedback' : ( optional ) Whether or not to give feedback to the user
* when the batch is finished . Defaults to TRUE .
* @ param array $langcodes
* ( optional ) Array of language codes . Defaults to all translatable languages .
* @ param array $components
* ( optional ) Array of component lists indexed by type . If not present or it
* is an empty array , it will update all components .
*
* @ return array
* The batch definition .
*/
function locale_config_batch_update_components ( array $options , $langcodes = array (), $components = array ()) {
$langcodes = $langcodes ? $langcodes : array_keys ( locale_translatable_language_list ());
if ( $langcodes && $names = \Drupal\locale\Locale :: config () -> getComponentNames ( $components )) {
return locale_config_batch_build ( $names , $langcodes , $options );
}
}
/**
* Creates a locale batch to refresh specific configuration .
*
* @ param array $names
* List of configuration object names ( which are strings ) to update .
* @ param array $langcodes
* List of language codes to refresh .
* @ param array $options
* ( optional ) An array with options that can have the following elements :
* - 'finish_feedback' : Whether or not to give feedback to the user when the
* batch is finished . Defaults to TRUE .
*
* @ return array
* The batch definition .
*
* @ see locale_config_batch_refresh_name ()
*/
function locale_config_batch_build ( array $names , array $langcodes , $options = array ()) {
$options += array ( 'finish_feedback' => TRUE );
$t = get_t ();
foreach ( $names as $name ) {
$operations [] = array ( 'locale_config_batch_refresh_name' , array ( $name , $langcodes ));
}
$batch = array (
'operations' => $operations ,
'title' => $t ( 'Updating configuration translations' ),
'init_message' => $t ( 'Starting configuration update' ),
'error_message' => $t ( 'Error updating configuration translations' ),
'file' => drupal_get_path ( 'module' , 'locale' ) . '/locale.bulk.inc' ,
);
if ( ! empty ( $options [ 'finish_feedback' ])) {
$batch [ 'completed' ] = 'locale_config_batch_finished' ;
}
return $batch ;
}
/**
* Performs configuration translation refresh as a batch step .
*
* @ param string $name
* Name of configuration object to update .
* @ param array $langcodes
* ( optional ) Array of language codes to update . Defaults to all languages .
* @ param array $context
* Contains a list of files imported .
*
* @ see locale_config_batch_build ()
*/
function locale_config_batch_refresh_name ( $name , array $langcodes , array & $context ) {
if ( ! isset ( $context [ 'result' ][ 'stats' ][ 'config' ])) {
$context [ 'result' ][ 'stats' ][ 'config' ] = 0 ;
}
$context [ 'result' ][ 'stats' ][ 'config' ] += locale_config_update_multiple ( array ( $name ), $langcodes );
$context [ 'result' ][ 'names' ][] = $name ;
$context [ 'result' ][ 'langcodes' ] = $langcodes ;
$context [ 'finished' ] = 1 ;
}
/**
* Finishes callback of system page locale import batch .
*
* @ see locale_config_batch_build ()
*
* @ param bool $success
* Information about the success of the batch import .
* @ param array $results
* Information about the results of the batch import .
*/
function locale_config_batch_finished ( $success , array $results ) {
if ( $success ) {
$configuration = isset ( $results [ 'stats' ][ 'config' ]) ? $results [ 'stats' ][ 'config' ] : 0 ;
if ( $configuration ) {
drupal_set_message ( t ( 'The configuration was successfully updated. There are %number configuration objects updated.' , array ( '%number' => $configuration )));
watchdog ( 'locale' , 'The configuration was successfully updated. %number configuration objects updated.' , array ( '%number' => $configuration ));
}
else {
drupal_set_message ( t ( 'No configuration objects have been updated.' ));
watchdog ( 'locale' , 'No configuration objects have been updated.' , array (), WATCHDOG_WARNING );
}
}
}
/**
* Updates all configuration for names / languages .
*
* @ param array $names
* Array of names of configuration objects to update .
* @ param array $langcodes
* ( optional ) Array of language codes to update . Defaults to all languages .
*
* @ return int
* Number of configuration objects retranslated .
*/
function locale_config_update_multiple ( array $names , $langcodes = array ()) {
$langcodes = $langcodes ? $langcodes : array_keys ( locale_translatable_language_list ());
$count = 0 ;
foreach ( $names as $name ) {
$wrapper = \Drupal\locale\Locale :: config () -> get ( $name );
foreach ( $langcodes as $langcode ) {
$translation = $wrapper -> getValue () ? $wrapper -> getTranslation ( $langcode ) -> getValue () : NULL ;
if ( $translation ) {
\Drupal\locale\Locale :: config () -> saveTranslationData ( $name , $langcode , $translation );
$count ++ ;
}
else {
\Drupal\locale\Locale :: config () -> deleteTranslationData ( $name , $langcode );
}
}
}
return $count ;
}