2012-10-11 21:30:02 +00:00
< ? php
/**
* @ file
2014-07-21 10:52:00 +00:00
* Batch process to check the availability of remote or local po files .
2012-10-11 21:30:02 +00:00
*/
2023-06-23 00:28:41 +00:00
use Drupal\Core\File\Exception\FileException ;
use Drupal\Core\File\Exception\InvalidStreamWrapperException ;
2024-04-09 07:50:53 +00:00
use Drupal\Core\File\FileExists ;
2019-04-16 05:38:27 +00:00
use Drupal\Core\Url ;
2020-12-21 06:26:56 +00:00
use GuzzleHttp\Exception\ConnectException ;
2014-04-11 14:58:24 +00:00
use GuzzleHttp\Exception\RequestException ;
2024-01-30 23:47:41 +00:00
use Psr\Http\Client\ClientExceptionInterface ;
2015-07-21 21:33:30 +00:00
use Psr\Http\Message\RequestInterface ;
use Psr\Http\Message\ResponseInterface ;
use Psr\Http\Message\UriInterface ;
2013-04-13 03:28:22 +00:00
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.
2015-05-18 21:08:10 +00:00
// Follow-up issue: https://www.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
2023-12-22 05:04:56 +00:00
/**
* Implements callback_batch_operation () .
*
* Checks for changed project versions , and cleans - up data from the old version .
* For example when a module is updated . This will make the translation import
* system use translations that match the current version .
*
* @ param string $project
* Machine name of the project for which to check the translation status .
* @ param string $langcode
* Language code of the language for which to check the translation .
* @ param array | \ArrayAccess $context
* The batch context .
*/
function locale_translation_batch_version_check ( string $project , string $langcode , array | \ArrayAccess & $context ) {
$locale_project = \Drupal :: service ( 'locale.project' ) -> get ( $project );
if ( empty ( $locale_project )) {
return ;
}
$status = \Drupal :: keyValue ( 'locale.translation_status' ) -> get ( $project );
if ( ! isset ( $status [ $langcode ])) {
return ;
}
if ( $locale_project [ 'version' ] == $status [ $langcode ] -> version ) {
return ;
}
\Drupal :: moduleHandler () -> loadInclude ( 'locale' , 'bulk.inc' );
locale_translation_status_delete_projects ([ $project ]);
locale_translate_delete_translation_files ([ $project ]);
$context [ 'message' ] = t ( 'Checked version of %project.' , [ '%project' => $project ]);
}
2012-10-11 21:30:02 +00:00
/**
2015-02-13 19:51:15 +00:00
* Implements callback_batch_operation () .
2012-10-11 21:30:02 +00:00
*
2013-06-27 11:25:01 +00:00
* Checks the presence and creation time po translation files in located at
* remote server location and local file system .
2012-10-11 21:30:02 +00:00
*
2013-06-27 11:25:01 +00:00
* @ param string $project
* Machine name of the project for which to check the translation status .
* @ param string $langcode
* Language code of the language for which to check the translation .
* @ param array $options
2014-10-22 09:58:00 +00:00
* An array with options that can have the following elements :
2013-06-27 11:25:01 +00:00
* - 'finish_feedback' : Whether or not to give feedback to the user when the
* batch is finished . Optional , defaults to TRUE .
* - 'use_remote' : Whether or not to check the remote translation file .
* Optional , defaults to TRUE .
2017-03-18 23:33:40 +00:00
* @ param array | \ArrayAccess $context
2013-06-27 11:25:01 +00:00
* The batch context .
2014-04-24 13:06:35 +00:00
*/
2016-03-04 00:44:13 +00:00
function locale_translation_batch_status_check ( $project , $langcode , array $options , & $context ) {
2013-06-27 11:25:01 +00:00
$failure = $checked = FALSE ;
2017-03-04 01:20:24 +00:00
$options += [
2013-06-27 11:25:01 +00:00
'finish_feedback' => TRUE ,
'use_remote' => TRUE ,
2017-03-04 01:20:24 +00:00
];
$source = locale_translation_get_status ([ $project ], [ $langcode ]);
2013-06-27 11:25:01 +00:00
$source = $source [ $project ][ $langcode ];
// Check the status of local translation files.
if ( isset ( $source -> files [ LOCALE_TRANSLATION_LOCAL ])) {
if ( $file = locale_translation_source_check_file ( $source )) {
locale_translation_status_save ( $source -> name , $source -> langcode , LOCALE_TRANSLATION_LOCAL , $file );
}
$checked = TRUE ;
}
2012-12-07 18:17:37 +00:00
2013-06-27 11:25:01 +00:00
// Check the status of remote translation files.
if ( $options [ 'use_remote' ] && isset ( $source -> files [ LOCALE_TRANSLATION_REMOTE ])) {
$remote_file = $source -> files [ LOCALE_TRANSLATION_REMOTE ];
if ( $result = locale_translation_http_check ( $remote_file -> uri )) {
2012-12-07 18:17:37 +00:00
// Update the file object with the result data. In case of a redirect we
2013-06-27 11:25:01 +00:00
// store the resulting uri.
2013-04-13 03:28:22 +00:00
if ( isset ( $result [ 'last_modified' ])) {
2021-11-15 02:19:43 +00:00
$remote_file -> uri = $result [ 'location' ] ? ? $remote_file -> uri ;
2013-04-13 03:28:22 +00:00
$remote_file -> timestamp = $result [ 'last_modified' ];
2013-06-27 11:25:01 +00:00
locale_translation_status_save ( $source -> name , $source -> langcode , LOCALE_TRANSLATION_REMOTE , $remote_file );
2012-12-07 18:17:37 +00:00
}
2013-06-27 11:25:01 +00:00
// @todo What to do with when the file is not found (404)? To prevent
// re-checking within the TTL (1day, 1week) we can set a last_checked
// timestamp or cache the result.
$checked = TRUE ;
2012-12-07 18:17:37 +00:00
}
else {
2013-06-27 11:25:01 +00:00
$failure = TRUE ;
2012-10-11 21:30:02 +00:00
}
}
2013-06-27 11:25:01 +00:00
// Provide user feedback and record success or failure for reporting at the
// end of the batch.
if ( $options [ 'finish_feedback' ] && $checked ) {
$context [ 'results' ][ 'files' ][] = $source -> name ;
2012-10-11 21:30:02 +00:00
}
2013-06-27 11:25:01 +00:00
if ( $failure && ! $checked ) {
$context [ 'results' ][ 'failed_files' ][] = $source -> name ;
2012-10-11 21:30:02 +00:00
}
2020-04-20 14:30:13 +00:00
$context [ 'message' ] = t ( 'Checked %langcode translation for %project.' , [ '%langcode' => $langcode , '%project' => $source -> project ]);
2012-10-11 21:30:02 +00:00
}
/**
2015-02-13 19:51:15 +00:00
* Implements callback_batch_finished () .
*
* Set result message .
2012-10-11 21:30:02 +00:00
*
2014-07-21 10:52:00 +00:00
* @ param bool $success
2013-12-03 15:54:20 +00:00
* TRUE if batch successfully completed .
2012-10-11 21:30:02 +00:00
* @ 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' ])) {
2014-08-15 22:58:36 +00:00
if ( \Drupal :: moduleHandler () -> moduleExists ( 'dblog' ) && \Drupal :: currentUser () -> hasPermission ( 'access site reports' )) {
2019-04-16 05:38:27 +00:00
$message = \Drupal :: translation () -> formatPlural ( 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.' , [ ':url' => Url :: fromRoute ( 'dblog.overview' ) -> toString ()]);
2014-07-21 10:52:00 +00:00
}
else {
2015-01-10 13:56:47 +00:00
$message = \Drupal :: translation () -> formatPlural ( 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.' );
2014-07-21 10:52:00 +00:00
}
2018-05-01 09:15:07 +00:00
\Drupal :: messenger () -> addError ( $message );
2012-12-07 18:17:37 +00:00
}
if ( isset ( $results [ 'files' ])) {
2018-05-01 09:15:07 +00:00
\Drupal :: messenger () -> addStatus ( \Drupal :: translation () -> formatPlural (
2013-06-27 11:25:01 +00:00
count ( $results [ 'files' ]),
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' ])) {
2018-05-01 09:15:07 +00:00
\Drupal :: messenger () -> addStatus ( t ( 'Nothing to check.' ));
2012-12-07 18:17:37 +00:00
}
2022-07-04 14:26:28 +00:00
\Drupal :: state () -> set ( 'locale.translation_last_checked' , \Drupal :: time () -> getRequestTime ());
2012-10-11 21:30:02 +00:00
}
else {
2018-05-01 09:15:07 +00:00
\Drupal :: messenger () -> addError ( t ( 'An error occurred trying to check available interface translation updates.' ));
2012-10-11 21:30:02 +00:00
}
}
2012-12-07 18:17:37 +00:00
/**
2015-02-13 19:51:15 +00:00
* Implements callback_batch_operation () .
2012-12-07 18:17:37 +00:00
*
2013-06-27 11:25:01 +00:00
* Downloads a remote gettext file into the translations directory . When
* successfully the translation status is updated .
2012-12-07 18:17:37 +00:00
*
* @ param object $project
* Source object of the translatable project .
* @ param string $langcode
* Language code .
2013-06-27 11:25:01 +00:00
* @ param array $context
* The batch context .
2012-12-07 18:17:37 +00:00
*
* @ see locale_translation_batch_fetch_import ()
*/
function locale_translation_batch_fetch_download ( $project , $langcode , & $context ) {
2017-03-04 01:20:24 +00:00
$sources = locale_translation_get_status ([ $project ], [ $langcode ]);
2013-06-27 11:25:01 +00:00
if ( isset ( $sources [ $project ][ $langcode ])) {
$source = $sources [ $project ][ $langcode ];
2012-12-07 18:17:37 +00:00
if ( isset ( $source -> type ) && $source -> type == LOCALE_TRANSLATION_REMOTE ) {
2013-06-27 11:25:01 +00:00
if ( $file = locale_translation_download_source ( $source -> files [ LOCALE_TRANSLATION_REMOTE ], 'translations://' )) {
2020-04-20 14:30:13 +00:00
$context [ 'message' ] = t ( 'Downloaded %langcode translation for %project.' , [ '%langcode' => $langcode , '%project' => $source -> project ]);
2013-06-27 11:25:01 +00:00
locale_translation_status_save ( $source -> name , $source -> langcode , LOCALE_TRANSLATION_LOCAL , $file );
2012-12-07 18:17:37 +00:00
}
else {
$context [ 'results' ][ 'failed_files' ][] = $source -> files [ LOCALE_TRANSLATION_REMOTE ];
}
}
}
}
/**
2015-02-13 19:51:15 +00:00
* Implements callback_batch_operation () .
2012-12-07 18:17:37 +00:00
*
2013-06-27 11:25:01 +00:00
* Imports a gettext file from the translation directory . When successfully the
* translation status is updated .
2012-12-07 18:17:37 +00:00
*
* @ param object $project
* Source object of the translatable project .
* @ param string $langcode
* Language code .
* @ param array $options
* Array of import options .
2013-06-27 11:25:01 +00:00
* @ param array $context
* The batch context .
2012-12-07 18:17:37 +00:00
*
* @ see locale_translate_batch_import_files ()
* @ see locale_translation_batch_fetch_download ()
*/
function locale_translation_batch_fetch_import ( $project , $langcode , $options , & $context ) {
2017-03-04 01:20:24 +00:00
$sources = locale_translation_get_status ([ $project ], [ $langcode ]);
2013-06-27 11:25:01 +00:00
if ( isset ( $sources [ $project ][ $langcode ])) {
$source = $sources [ $project ][ $langcode ];
2012-12-07 18:17:37 +00:00
if ( isset ( $source -> type )) {
if ( $source -> type == LOCALE_TRANSLATION_REMOTE || $source -> type == LOCALE_TRANSLATION_LOCAL ) {
2013-06-27 11:25:01 +00:00
$file = $source -> files [ LOCALE_TRANSLATION_LOCAL ];
Issue #697946 by voleger, pguillard, pillarsdotnet, andypost, alansaviolobo, alexpott, vaibhavjain, MerryHamster, sja112, kim.pepper, shaktik, ravi.shankar, Pooja Ganjage, daffie, Mile23, legolasbo, joelpittet, almaudoh, xjm, Berdir, scor: Properly deprecate module_load_include() and move it into \Drupal::moduleHandler() service
2022-01-06 10:01:52 +00:00
\Drupal :: moduleHandler () -> loadInclude ( 'locale' , 'inc' , 'locale.bulk' );
2017-03-04 01:20:24 +00:00
$options += [
2020-04-20 14:30:13 +00:00
'message' => t ( 'Importing %langcode translation for %project.' , [ '%langcode' => $langcode , '%project' => $source -> project ]),
2017-03-04 01:20:24 +00:00
];
2012-12-07 18:17:37 +00:00
// Import the translation file. For large files the batch operations is
2013-06-27 11:25:01 +00:00
// progressive and will be called repeatedly until finished.
2012-12-07 18:17:37 +00:00
locale_translate_batch_import ( $file , $options , $context );
// The import is finished.
if ( isset ( $context [ 'finished' ]) && $context [ 'finished' ] == 1 ) {
2013-12-03 15:54:20 +00:00
// The import is successful.
2012-12-07 18:17:37 +00:00
if ( isset ( $context [ 'results' ][ 'files' ][ $file -> uri ])) {
2020-04-20 14:30:13 +00:00
$context [ 'message' ] = t ( 'Imported %langcode translation for %project.' , [ '%langcode' => $langcode , '%project' => $source -> project ]);
2012-12-07 18:17:37 +00:00
2013-06-27 11:25:01 +00:00
// Save the data of imported source into the {locale_file} table and
// update the current translation status.
locale_translation_status_save ( $project , $langcode , LOCALE_TRANSLATION_CURRENT , $source -> files [ LOCALE_TRANSLATION_LOCAL ]);
2012-12-07 18:17:37 +00:00
}
}
}
}
}
}
/**
2015-02-13 19:51:15 +00:00
* Implements callback_batch_finished () .
*
* Set result message .
2012-12-07 18:17:37 +00:00
*
2014-07-21 10:52:00 +00:00
* @ param bool $success
2013-12-03 15:54:20 +00:00
* TRUE if batch successfully completed .
2014-07-21 10:52:00 +00:00
* @ param array $results
2012-12-07 18:17:37 +00:00
* Batch results .
*/
function locale_translation_batch_fetch_finished ( $success , $results ) {
Issue #697946 by voleger, pguillard, pillarsdotnet, andypost, alansaviolobo, alexpott, vaibhavjain, MerryHamster, sja112, kim.pepper, shaktik, ravi.shankar, Pooja Ganjage, daffie, Mile23, legolasbo, joelpittet, almaudoh, xjm, Berdir, scor: Properly deprecate module_load_include() and move it into \Drupal::moduleHandler() service
2022-01-06 10:01:52 +00:00
\Drupal :: moduleHandler () -> loadInclude ( 'locale' , 'inc' , 'locale.bulk' );
2013-06-27 11:25:01 +00:00
if ( $success ) {
2022-07-04 14:26:28 +00:00
\Drupal :: state () -> set ( 'locale.translation_last_checked' , \Drupal :: time () -> getRequestTime ());
2013-06-27 11:25:01 +00:00
}
2012-12-07 18:17:37 +00:00
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
*
2014-07-21 10:52:00 +00:00
* @ return array | bool
2013-04-13 03:28:22 +00:00
* 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 ) {
2014-06-26 18:55:12 +00:00
$logger = \Drupal :: logger ( 'locale' );
2013-04-13 03:28:22 +00:00
try {
2015-07-21 21:33:30 +00:00
$actual_uri = NULL ;
2017-09-15 21:52:14 +00:00
$response = \Drupal :: service ( 'http_client_factory' ) -> fromOptions ([
'allow_redirects' => [
2017-10-10 14:43:06 +00:00
'on_redirect' => function ( RequestInterface $request , ResponseInterface $response , UriInterface $request_uri ) use ( & $actual_uri ) {
2017-09-15 21:52:14 +00:00
$actual_uri = ( string ) $request_uri ;
2018-05-11 09:40:33 +00:00
},
2017-09-15 21:52:14 +00:00
],
]) -> head ( $uri );
2017-03-04 01:20:24 +00:00
$result = [];
2013-04-13 03:28:22 +00:00
2013-08-10 07:53:20 +00:00
// Return the effective URL if it differs from the requested.
2015-07-21 21:33:30 +00:00
if ( $actual_uri && $actual_uri !== $uri ) {
$result [ 'location' ] = $actual_uri ;
2012-12-07 18:17:37 +00:00
}
2013-04-13 03:28:22 +00:00
2015-07-21 21:33:30 +00:00
$result [ 'last_modified' ] = $response -> hasHeader ( 'Last-Modified' ) ? strtotime ( $response -> getHeaderLine ( 'Last-Modified' )) : 0 ;
2012-12-07 18:17:37 +00:00
return $result ;
}
2014-04-11 14:58:24 +00:00
catch ( RequestException $e ) {
2013-04-13 03:28:22 +00:00
// Handle 4xx and 5xx http responses.
2015-03-04 15:14:39 +00:00
if ( $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.
2017-03-04 01:20:24 +00:00
$logger -> notice ( 'Translation file not found: @uri.' , [ '@uri' => $uri ]);
2015-03-04 15:14:39 +00:00
return TRUE ;
}
2017-03-04 01:20:24 +00:00
$logger -> notice ( 'HTTP request to @url failed with error: @error.' , [ '@url' => $uri , '@error' => $response -> getStatusCode () . ' ' . $response -> getReasonPhrase ()]);
2012-12-07 18:17:37 +00:00
}
2013-04-13 03:28:22 +00:00
}
2020-12-21 06:26:56 +00:00
// We need to handle ConnectException separately because in Guzzle 7 it
// doesn't have a getResponse() method, so the above will fatal.
catch ( ConnectException $e ) {
$logger -> notice ( 'HTTP request to @url failed with error: @error.' , [ '@url' => $uri , '@error' => $e -> getMessage ()]);
}
2014-04-11 14:58:24 +00:00
2012-12-07 18:17:37 +00:00
return FALSE ;
}
/**
2013-06-27 11:25:01 +00:00
* Downloads a translation file from a remote server .
2012-12-07 18:17:37 +00:00
*
* @ 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 .
2013-06-27 11:25:01 +00:00
* @ param string $directory
* Directory where the downloaded file will be saved . Defaults to the
* temporary file path .
2012-12-07 18:17:37 +00:00
*
2023-06-23 00:28:41 +00:00
* @ return object | false
2012-12-07 18:17:37 +00:00
* File object if download was successful . FALSE on failure .
2012-10-11 21:30:02 +00:00
*/
2013-06-27 11:25:01 +00:00
function locale_translation_download_source ( $source_file , $directory = 'temporary://' ) {
2023-06-23 00:28:41 +00:00
try {
$data = ( string ) \Drupal :: httpClient () -> get ( $source_file -> uri ) -> getBody ();
/** @var \Drupal\Core\File\FileSystemInterface $fileSystem */
$fileSystem = \Drupal :: service ( 'file_system' );
$filename = $fileSystem -> basename ( $source_file -> uri );
2024-04-09 07:50:53 +00:00
if ( $uri = $fileSystem -> saveData ( $data , $directory . $filename , FileExists :: Replace )) {
2023-06-23 00:28:41 +00:00
$file = clone ( $source_file );
$file -> type = LOCALE_TRANSLATION_LOCAL ;
$file -> uri = $uri ;
$file -> directory = $directory ;
$file -> timestamp = filemtime ( $uri );
return $file ;
}
}
2024-01-30 23:47:41 +00:00
catch ( ClientExceptionInterface $exception ) {
2023-06-23 00:28:41 +00:00
\Drupal :: messenger () -> addError ( t ( 'Failed to fetch file due to error "%error"' , [ '%error' => $exception -> getMessage ()]));
}
catch ( FileException | InvalidStreamWrapperException $e ) {
\Drupal :: messenger () -> addError ( t ( 'Failed to save file due to error "%error"' , [ '%error' => $e -> getMessage ()]));
2012-10-11 21:30:02 +00:00
}
2017-03-04 01:20:24 +00:00
\Drupal :: logger ( 'locale' ) -> error ( 'Unable to download translation file @uri.' , [ '@uri' => $source_file -> uri ]);
2012-12-07 18:17:37 +00:00
return FALSE ;
2012-10-11 21:30:02 +00:00
}