2012-10-11 21:30:02 +00:00
< ? php
/**
* @ file
* Batch process to check the availability of remote or local po files .
*/
2013-04-13 03:28:22 +00:00
use Guzzle\Http\Exception\BadResponseException ;
use Guzzle\Http\Exception\RequestException ;
2012-10-11 21:30:02 +00:00
/**
2012-12-07 18:17:37 +00:00
* Load the common translation API .
2012-10-11 21:30:02 +00:00
*/
2012-12-07 18:17:37 +00:00
// @todo Combine functions differently in files to avoid unnecessary includes.
// Follow-up issue http://drupal.org/node/1834298
2013-05-09 09:25:10 +00:00
require_once __DIR__ . '/locale.translation.inc' ;
2012-10-11 21:30:02 +00:00
/**
* Batch operation callback : Check the availability of a remote po file .
*
* Checks the presence and creation time of one po file per batch process . The
* file URL and timestamp are stored .
*
* @ param array $source
* A translation source object of the project for which to check the state of
* a remote po file .
* @ param array $context
* The batch context array . The collected state is stored in the 'results'
* parameter of the context .
*
* @ see locale_translation_batch_status_fetch_local ()
* @ see locale_translation_batch_status_compare ()
*/
function locale_translation_batch_status_fetch_remote ( $source , & $context ) {
// Check the translation file at the remote server and update the source
// data with the remote status.
2012-12-07 18:17:37 +00:00
if ( isset ( $source -> files [ LOCALE_TRANSLATION_REMOTE ])) {
$remote_file = $source -> files [ LOCALE_TRANSLATION_REMOTE ];
$result = locale_translation_http_check ( $remote_file -> uri );
if ( $result ) {
// Update the file object with the result data. In case of a redirect we
// store the resulting uri. If a file is not found we don't update the
// file object, and store it unchanged.
2013-04-13 03:28:22 +00:00
if ( isset ( $result [ 'last_modified' ])) {
$remote_file -> uri = isset ( $result [ 'location' ]) ? $result [ 'location' ] : $remote_file -> uri ;
$remote_file -> timestamp = $result [ 'last_modified' ];
2012-12-07 18:17:37 +00:00
$source -> files [ LOCALE_TRANSLATION_REMOTE ] = $remote_file ;
}
// Record success.
$context [ 'results' ][ 'files' ][ $source -> name ] = $source -> name ;
}
else {
// An error occured when checking the file. Record the failure for
// reporting at the end of the batch.
$context [ 'results' ][ 'failed_files' ][] = $source -> name ;
2012-10-11 21:30:02 +00:00
}
2012-12-07 18:17:37 +00:00
$context [ 'results' ][ 'sources' ][ $source -> name ][ $source -> langcode ] = $source ;
2013-06-17 13:35:07 +00:00
$context [ 'message' ] = t ( 'Checked translation for %project.' , array ( '%project' => $source -> project ));
2012-10-11 21:30:02 +00:00
}
}
/**
* Batch operation callback : Check the availability of local po files .
*
* Checks the presence and creation time of po files in the local file system .
* The file path and the timestamp are stored .
*
* @ param array $sources
* Array of translation source objects of projects for which to check the
* state of local po files .
* @ param array $context
* The batch context array . The collected state is stored in the 'results'
* parameter of the context .
*
* @ see locale_translation_batch_status_fetch_remote ()
* @ see locale_translation_batch_status_compare ()
*/
function locale_translation_batch_status_fetch_local ( $sources , & $context ) {
// Get the status of local translation files and store the result data in the
// batch results for later processing.
foreach ( $sources as $source ) {
2012-12-07 18:17:37 +00:00
if ( isset ( $source -> files [ LOCALE_TRANSLATION_LOCAL ])) {
2012-10-11 21:30:02 +00:00
locale_translation_source_check_file ( $source );
// If remote data was collected before, we merge it into the newly
// collected result.
2012-12-07 18:17:37 +00:00
if ( isset ( $context [ 'results' ][ 'sources' ][ $source -> name ][ $source -> langcode ])) {
$source -> files [ LOCALE_TRANSLATION_REMOTE ] = $context [ 'results' ][ 'sources' ][ $source -> name ][ $source -> langcode ] -> files [ LOCALE_TRANSLATION_REMOTE ];
2012-10-11 21:30:02 +00:00
}
2012-12-07 18:17:37 +00:00
// Record success and store the updated source data.
$context [ 'results' ][ 'files' ][ $source -> name ] = $source -> name ;
$context [ 'results' ][ 'sources' ][ $source -> name ][ $source -> langcode ] = $source ;
2012-10-11 21:30:02 +00:00
}
}
2013-06-17 13:35:07 +00:00
$context [ 'message' ] = t ( 'Checked all translations.' );
2012-10-11 21:30:02 +00:00
}
/**
* Batch operation callback : Compare states and store the result .
*
* In the preceding batch processes data of remote and local translation sources
* is collected . Here we compare the collected results and update the source
* object with the data of the most recent translation file . The end result is
2012-12-07 18:17:37 +00:00
* stored in the 'locale.translation_status' state variable . Other
2012-10-11 21:30:02 +00:00
* processes can collect this data after the batch process is completed .
*
* @ param array $context
* The batch context array . The 'results' element contains a structured array
* of project data with languages , local and remote source data .
*
* @ see locale_translation_batch_status_fetch_remote ()
* @ see locale_translation_batch_status_fetch_local ()
*/
function locale_translation_batch_status_compare ( & $context ) {
2012-12-07 18:17:37 +00:00
$history = locale_translation_get_file_history ();
2012-10-11 21:30:02 +00:00
$results = array ();
2012-12-07 18:17:37 +00:00
if ( isset ( $context [ 'results' ][ 'sources' ])) {
foreach ( $context [ 'results' ][ 'sources' ] as $project => $langcodes ) {
foreach ( $langcodes as $langcode => $source ) {
$local = isset ( $source -> files [ LOCALE_TRANSLATION_LOCAL ]) ? $source -> files [ LOCALE_TRANSLATION_LOCAL ] : NULL ;
$remote = isset ( $source -> files [ LOCALE_TRANSLATION_REMOTE ]) ? $source -> files [ LOCALE_TRANSLATION_REMOTE ] : NULL ;
// The available translation files are compared and data of the most
// recent file is used to update the source object.
$file = _locale_translation_source_compare ( $local , $remote ) == LOCALE_TRANSLATION_SOURCE_COMPARE_LT ? $remote : $local ;
if ( isset ( $file -> timestamp )) {
$source -> type = $file -> type ;
$source -> timestamp = $file -> timestamp ;
}
// Compare the available translation with the current translations
// status. If the project/language was translated before and it is more
// recent than the most recent translation, the translation is up to
// date. Which is marked in the source object with type "current".
if ( isset ( $history [ $source -> project ][ $source -> langcode ])) {
$current = $history [ $source -> project ][ $source -> langcode ];
// Add the current translation to the source object to save it in
// the status cache.
$source -> files [ LOCALE_TRANSLATION_CURRENT ] = $current ;
if ( isset ( $source -> type )) {
$available = $source -> files [ $source -> type ];
$result = _locale_translation_source_compare ( $current , $available ) == LOCALE_TRANSLATION_SOURCE_COMPARE_LT ? $available : $current ;
$source -> type = $result -> type ;
$source -> timestamp = $result -> timestamp ;
}
else {
$source -> type = $current -> type ;
$source -> timestamp = $current -> timestamp ;
}
}
2012-10-11 21:30:02 +00:00
$results [ $project ][ $langcode ] = $source ;
}
}
2013-06-17 13:35:07 +00:00
$context [ 'message' ] = t ( 'Updated translation status.' );
2012-10-11 21:30:02 +00:00
}
2012-12-07 18:17:37 +00:00
locale_translation_status_save ( $results );
2012-10-11 21:30:02 +00:00
}
/**
* Batch finished callback : Set result message .
*
* @ param boolean $success
* TRUE if batch succesfully completed .
* @ param array $results
* Batch results .
*/
function locale_translation_batch_status_finished ( $success , $results ) {
2012-12-07 18:17:37 +00:00
if ( $success ) {
if ( isset ( $results [ 'failed_files' ])) {
2013-06-17 06:28:58 +00:00
if ( Drupal :: moduleHandler () -> moduleExists ( 'dblog' )) {
2012-12-07 18:17:37 +00:00
$message = format_plural ( count ( $results [ 'failed_files' ]), 'One translation file could not be checked. <a href="@url">See the log</a> for details.' , '@count translation files could not be checked. <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 checked. See the log for details.' , '@count translation files could not be checked. See the log for details.' );
}
drupal_set_message ( $message , 'error' );
}
if ( isset ( $results [ 'files' ])) {
2012-10-11 21:30:02 +00:00
drupal_set_message ( format_plural (
2012-12-07 18:17:37 +00:00
count ( $results [ 'sources' ]),
2012-10-11 21:30:02 +00:00
'Checked available interface translation updates for one project.' ,
'Checked available interface translation updates for @count projects.'
));
}
2012-12-07 18:17:37 +00:00
if ( ! isset ( $results [ 'failed_files' ]) && ! isset ( $results [ 'files' ])) {
drupal_set_message ( t ( 'Nothing to check.' ));
}
2012-10-11 21:30:02 +00:00
}
else {
2013-06-17 13:35:07 +00:00
drupal_set_message ( t ( 'An error occurred trying to check available interface translation updates.' ), 'error' );
2012-10-11 21:30:02 +00:00
}
}
2012-12-07 18:17:37 +00:00
/**
* Loads translation source data for the projects to be updated .
*
* Source data is loaded from cache and stored in the context results array .
* Source data contains the translations status per project / per language
* and whether translation updates are available and where the updates can be
* retrieved from . The data is stored in the $context [ 'results' ] parameter
* so that other batch operations can take this data as input for their
* operation .
*
* @ see locale_translation_batch_fetch_download ()
* @ see locale_translation_batch_fetch_import ()
* @ see locale_translation_batch_fetch_update_status ()
* @ see locale_translation_batch_status_compare ()
*/
function locale_translation_batch_fetch_sources ( $projects , $langcodes , & $context ) {
$context [ 'results' ][ 'input' ] = locale_translation_load_sources ( $projects , $langcodes );
// If this batch operation is preceded by the status check operations, the
// results of those operation are stored in the context. We remove them here
// to keep the result records clean.
unset ( $context [ 'results' ][ 'files' ]);
unset ( $context [ 'results' ][ 'failed_files' ]);
}
/**
* Batch operation : Download a remote translation file .
*
* This operation downloads a remote gettext file and saves it in the temporary
* directory . The remote file URL is taken from the input data in
* $context [ 'results' ][ 'input' ] . The result of the operation is stored in
* $context [ 'results' ][ 'sources' ] and contains the URL of the temporary file .
*
* @ param object $project
* Source object of the translatable project .
* @ param string $langcode
* Language code .
* @ param $context
* Batch context array .
*
* @ see locale_translation_batch_fetch_sources ()
* @ see locale_translation_batch_fetch_import ()
* @ see locale_translation_batch_fetch_update_status ()
* @ see locale_translation_batch_status_compare ()
*/
function locale_translation_batch_fetch_download ( $project , $langcode , & $context ) {
$sources = $context [ 'results' ][ 'input' ];
if ( isset ( $sources [ $project . ':' . $langcode ])) {
$source = $sources [ $project . ':' . $langcode ];
if ( isset ( $source -> type ) && $source -> type == LOCALE_TRANSLATION_REMOTE ) {
if ( $file = locale_translation_download_source ( $source -> files [ LOCALE_TRANSLATION_REMOTE ])) {
2013-06-17 13:35:07 +00:00
$context [ 'message' ] = t ( 'Downloaded translation for %project.' , array ( '%project' => $source -> project ));
2012-12-07 18:17:37 +00:00
$source -> files [ LOCALE_TRANSLATION_DOWNLOADED ] = $file ;
}
else {
$context [ 'results' ][ 'failed_files' ][] = $source -> files [ LOCALE_TRANSLATION_REMOTE ];
}
$context [ 'results' ][ 'sources' ][ $project ][ $langcode ] = $source ;
}
}
}
/**
* Batch process : Import translation file .
*
* This batch operation imports either a local gettext file or a downloaded
* remote gettext file . In case of a downloaded file the location of the
* temporary file is found in the $context [ 'results' ][ 'sources' ] . The temporary
* file will be deleted after importing or will be moved to the local
* translations directory . In case of a local file the file will just be
* imported .
*
* @ param object $project
* Source object of the translatable project .
* @ param string $langcode
* Language code .
* @ param array $options
* Array of import options .
* @ param $context
* Batch context array .
*
* @ see locale_translate_batch_import_files ()
* @ see locale_translation_batch_fetch_sources ()
* @ see locale_translation_batch_fetch_download ()
* @ see locale_translation_batch_fetch_update_status ()
* @ see locale_translation_batch_status_compare ()
*/
function locale_translation_batch_fetch_import ( $project , $langcode , $options , & $context ) {
$sources = $context [ 'results' ][ 'input' ];
if ( isset ( $sources [ $project . ':' . $langcode ])) {
$source = $sources [ $project . ':' . $langcode ];
if ( isset ( $source -> type )) {
if ( $source -> type == LOCALE_TRANSLATION_REMOTE || $source -> type == LOCALE_TRANSLATION_LOCAL ) {
// If we are working on a remote file we will import the downloaded
// file. If the file was local just mark the result as such.
if ( $source -> type == LOCALE_TRANSLATION_REMOTE ) {
if ( isset ( $context [ 'results' ][ 'sources' ][ $source -> project ][ $source -> langcode ] -> files [ LOCALE_TRANSLATION_DOWNLOADED ])) {
$import_type = LOCALE_TRANSLATION_DOWNLOADED ;
$source_result = $context [ 'results' ][ 'sources' ][ $source -> project ][ $source -> langcode ];
}
}
else {
$import_type = LOCALE_TRANSLATION_LOCAL ;
$source_result = $source ;
}
$file = $source_result -> files [ $import_type ];
module_load_include ( 'bulk.inc' , 'locale' );
$options += array (
2013-06-17 13:35:07 +00:00
'message' => t ( 'Importing translation for %project.' , array ( '%project' => $source -> project )),
2012-12-07 18:17:37 +00:00
);
// Import the translation file. For large files the batch operations is
// progressive and will be called repeatedly untill finished.
locale_translate_batch_import ( $file , $options , $context );
// The import is finished.
if ( isset ( $context [ 'finished' ]) && $context [ 'finished' ] == 1 ) {
// The import is successfull.
if ( isset ( $context [ 'results' ][ 'files' ][ $file -> uri ])) {
2013-06-17 13:35:07 +00:00
$context [ 'message' ] = t ( 'Imported translation for %project.' , array ( '%project' => $source -> project ));
2012-12-07 18:17:37 +00:00
// Keep the data of imported source. In the following batch
// operation it will be saved in the {locale_file} table.
$source_result -> files [ LOCALE_TRANSLATION_IMPORTED ] = $source_result -> files [ $source -> type ];
// Downloaded files are stored in the temporary files directory. If
// files should be kept locally, they will be moved to the local
// translations after successfull import. Otherwise the temporary
// file is deleted after being imported.
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
if ( $import_type == LOCALE_TRANSLATION_DOWNLOADED && config ( 'locale.settings' ) -> get ( 'translation.path' ) && isset ( $source_result -> files [ LOCALE_TRANSLATION_LOCAL ])) {
2012-12-07 18:17:37 +00:00
if ( file_unmanaged_move ( $file -> uri , $source_result -> files [ LOCALE_TRANSLATION_LOCAL ] -> uri , FILE_EXISTS_REPLACE )) {
// The downloaded file is now moved to the local file location.
// From this point forward we can treat it as if we imported a
// local file.
$import_type = LOCALE_TRANSLATION_LOCAL ;
}
}
// The downloaded file is imported but will not be stored locally.
// Store the timestamp and delete the file.
if ( $import_type == LOCALE_TRANSLATION_DOWNLOADED ) {
$timestamp = filemtime ( $source_result -> files [ $import_type ] -> uri );
$source_result -> files [ LOCALE_TRANSLATION_IMPORTED ] -> timestamp = $timestamp ;
$source_result -> files [ LOCALE_TRANSLATION_IMPORTED ] -> last_checked = REQUEST_TIME ;
file_unmanaged_delete ( $file -> uri );
}
// If the translation file is stored in the local directory. The
// timestamp of the file is stored.
if ( $import_type == LOCALE_TRANSLATION_LOCAL ) {
$timestamp = filemtime ( $source_result -> files [ $import_type ] -> uri );
$source_result -> files [ LOCALE_TRANSLATION_LOCAL ] -> timestamp = $timestamp ;
$source_result -> files [ LOCALE_TRANSLATION_IMPORTED ] -> timestamp = $timestamp ;
$source_result -> files [ LOCALE_TRANSLATION_IMPORTED ] -> last_checked = REQUEST_TIME ;
}
}
else {
// File import failed. We can delete the temporary file.
if ( $import_type == LOCALE_TRANSLATION_DOWNLOADED ) {
file_unmanaged_delete ( $file -> uri );
}
}
}
$context [ 'results' ][ 'sources' ][ $source -> project ][ $source -> langcode ] = $source_result ;
}
}
}
}
/**
* Batch process : Update the download history table .
*
* This batch process updates the { local_file } table with the data of imported
* gettext files . Import data is taken from $context [ 'results' ][ 'sources' ] .
*
* @ param $context
* Batch context array .
*
* @ see locale_translation_batch_fetch_sources ()
* @ see locale_translation_batch_fetch_download ()
* @ see locale_translation_batch_fetch_import ()
* @ see locale_translation_batch_status_compare ()
*/
function locale_translation_batch_fetch_update_status ( & $context ) {
$results = array ();
if ( isset ( $context [ 'results' ][ 'sources' ])) {
foreach ( $context [ 'results' ][ 'sources' ] as $project => $langcodes ) {
foreach ( $langcodes as $langcode => $source ) {
// Store the state of the imported translations in {locale_file} table.
// During the batch execution the data of the imported files is
// temporary stored in $context['results']['sources']. Now it will be
// stored in the database. Afterwards the temporary import and download
// data can be deleted.
if ( isset ( $source -> files [ LOCALE_TRANSLATION_IMPORTED ])) {
$file = $source -> files [ LOCALE_TRANSLATION_IMPORTED ];
locale_translation_update_file_history ( $file );
unset ( $source -> files [ LOCALE_TRANSLATION_IMPORTED ]);
}
unset ( $source -> files [ LOCALE_TRANSLATION_DOWNLOADED ]);
// The source data is now up to date. Data of local and/or remote source
// file is up to date including an updated time stamp. In a next batch
// operation this can be used to update the translation status.
$context [ 'results' ][ 'sources' ][ $project ][ $langcode ] = $source ;
}
}
2013-06-17 13:35:07 +00:00
$context [ 'message' ] = t ( 'Updated translations.' );
2012-12-07 18:17:37 +00:00
// The file history has changed, flush the static cache now.
drupal_static_reset ( 'locale_translation_get_file_history' );
}
}
/**
* Batch finished callback : Set result message .
*
* @ param boolean $success
* TRUE if batch succesfully completed .
* @ param array
* Batch results .
*/
function locale_translation_batch_fetch_finished ( $success , $results ) {
module_load_include ( 'bulk.inc' , 'locale' );
return locale_translate_batch_finished ( $success , $results );
}
2012-10-11 21:30:02 +00:00
/**
* Check if remote file exists and when it was last updated .
*
2012-12-07 18:17:37 +00:00
* @ param string $uri
* URI of remote file .
2013-04-13 03:28:22 +00:00
*
* @ return array | boolean
* Associative array of file data with the following elements :
* - last_modified : Last modified timestamp of the translation file .
* - ( optional ) location : The location of the translation file . Is only set
* when a redirect ( 301 ) has occurred .
* TRUE if the file is not found . FALSE if a fault occurred .
2012-12-07 18:17:37 +00:00
*/
2013-04-13 03:28:22 +00:00
function locale_translation_http_check ( $uri ) {
try {
$response = Drupal :: httpClient ()
-> head ( $uri )
-> send ();
$result = array ();
// In case of a permanent redirected response, return the final location.
if ( $previous = $response -> getPreviousResponse ()) {
if ( $previous -> getStatusCode () == 301 ) {
$result [ 'location' ] = $previous -> getLocation ();
}
2012-12-07 18:17:37 +00:00
}
2013-04-13 03:28:22 +00:00
$result [ 'last_modified' ] = $response -> getLastModified () ? strtotime ( $response -> getLastModified ()) : 0 ;
2012-12-07 18:17:37 +00:00
return $result ;
}
2013-04-13 03:28:22 +00:00
catch ( BadResponseException $e ) {
// Handle 4xx and 5xx http responses.
$response = $e -> getResponse ();
if ( $response -> getStatusCode () == 404 ) {
// File not found occurs when a translation file is not yet available
// at the translation server. But also if a custom module or custom
// theme does not define the location of a translation file. By default
// the file is checked at the translation server, but it will not be
// found there.
watchdog ( 'locale' , 'Translation file not found: @uri.' , array ( '@uri' => $uri ));
return TRUE ;
2012-12-07 18:17:37 +00:00
}
2013-04-13 03:28:22 +00:00
watchdog ( 'locale' , 'HTTP request to @url failed with error: @error.' , array ( '@url' => $uri , '@error' => $response -> getStatusCode () . ' ' . $response -> getReasonPhrase ()));
}
catch ( RequestException $e ) {
// Handle connection problems and cURL specific errors (CurlException) and
// other http related errors.
watchdog ( 'locale' , 'HTTP request to @url failed with error: @error.' , array ( '@url' => $uri , '@error' => $e -> getMessage ()));
2012-12-07 18:17:37 +00:00
}
return FALSE ;
}
/**
* Downloads source file from a remote server .
*
* The downloaded file is stored in the temporary files directory .
*
* @ param object $source_file
* Source file object with at least :
* - " uri " : uri to download the file from .
* - " project " : Project name .
* - " langcode " : Translation language .
* - " version " : Project version .
* - " filename " : File name .
*
* @ return object
* File object if download was successful . FALSE on failure .
2012-10-11 21:30:02 +00:00
*/
2012-12-07 18:17:37 +00:00
function locale_translation_download_source ( $source_file ) {
if ( $uri = system_retrieve_file ( $source_file -> uri , 'temporary://' )) {
$file = new stdClass ();
$file -> project = $source_file -> project ;
$file -> langcode = $source_file -> langcode ;
$file -> version = $source_file -> version ;
$file -> type = LOCALE_TRANSLATION_DOWNLOADED ;
$file -> uri = $uri ;
$file -> filename = $source_file -> filename ;
return $file ;
2012-10-11 21:30:02 +00:00
}
2012-12-07 18:17:37 +00:00
watchdog ( 'locale' , 'Unable to download translation file @uri.' , array ( '@uri' => $source -> files [ LOCALE_TRANSLATION_REMOTE ] -> uri ), WATCHDOG_ERROR );
return FALSE ;
2012-10-11 21:30:02 +00:00
}