Commit 62b1f23e authored by webchick's avatar webchick

Issue #1627006 by Sutharsan, Berdir, lazysoundsystem, andypost, webflo,...

Issue #1627006 by Sutharsan, Berdir, lazysoundsystem, andypost, webflo, jepSter, MantasK, Gábor Hojtsy: Collect project information for translation update.
parent b93818ac
<?php
/**
* @file
* Definition of Drupal\locale\Tests\LocaleCompareTest.
*/
namespace Drupal\locale\Tests;
use Drupal\simpletest\WebTestBase;
/**
* Tests for comparing status of existing project translations with available translations.
*/
class LocaleCompareTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('update', 'locale', 'locale_test');
public static function getInfo() {
return array(
'name' => 'Compare project states',
'description' => 'Tests for comparing status of existing project translations with available translations.',
'group' => 'Locale',
);
}
/**
* Test for translation status storage and translation status comparison.
*/
function testLocaleCompare() {
// Create and login user.
$admin_user = $this->drupalCreateUser(array('administer site configuration', 'administer languages', 'access administration pages'));
$this->drupalLogin($admin_user);
module_load_include('compare.inc', 'locale');
// Check if hidden modules are not included.
$projects = locale_translation_project_list();
$this->assertFalse(isset($projects['locale_test']), 'Hidden module not found');
// Make the test modules look like a normal custom module. i.e. make the
// modules not hidden. locale_test_system_info_alter() modifies the project
// info of the locale_test and locale_test_disabled modules.
variable_set('locale_translation_test_system_info_alter', TRUE);
// Check if interface translation data is collected from hook_info.
drupal_static_reset('locale_translation_project_list');
$projects = locale_translation_project_list();
$this->assertEqual($projects['locale_test']['info']['interface translation server pattern'], 'core/modules/locale/test/modules/locale_test/%project-%version.%language.po', 'Interface translation parameter found in project info.');
$this->assertEqual($projects['locale_test']['name'] , 'locale_test', format_string('%key found in project info.', array('%key' => 'interface translation project')));
// Check if disabled modules are detected.
variable_set('locale_translation_check_disabled', TRUE);
drupal_static_reset('locale_translation_project_list');
$projects = locale_translation_project_list();
$this->assertTrue(isset($projects['locale_test_disabled']), 'Disabled module found');
// Check the fully processed list of project data of both enabled and
// disabled modules.
variable_set('locale_translation_check_disabled', TRUE);
drupal_static_reset('locale_translation_project_list');
$projects = locale_translation_get_projects();
$this->assertEqual($projects['drupal']->name, 'drupal', 'Core project found');
$this->assertEqual($projects['locale_test']->server_pattern, 'core/modules/locale/test/modules/locale_test/%project-%version.%language.po', 'Interface translation parameter found in project info.');
$this->assertEqual($projects['locale_test_disabled']->status, '0', 'Disabled module found');
variable_del('locale_translation_check_disabled');
// Return the locale test modules back to their hidden state.
variable_del('locale_translation_test_system_info_alter');
}
}
<?php
/**
* @file
* Hooks provided by the Locale module.
*/
/**
* @defgroup interface_translation_properties Interface translation properties
* @{
*
* .info file properties for interface translation settings.
*
* Modules hosted on drupal.org, a project definition is automatically added to
* the .info file. Only modules with this project definition are discovered by
* the update module and use it to check for new releases. Locale module uses
* the same data to build a list of module to check for new translations.
* Therefore modules not hosted at drupal.org, such as custom modules, custom
* themes, features and distributions, need a way to identify themselves to
* the Locale module if they have translations that require to be updated.
*
* Custom module which contain new strings should provide po file(s) containing
* source strings and string translations in gettext format. The translation
* file can be located both local and remote. Use the following .info file
* properties to inform Locale module to load and import the translations.
*
* Example .info file properties for a custom module with a po file located in
* the module's folder.
* @code
* interface translation project = example_module
* interface translation server pattern = sites/example.com/modules/custom/example_module/%project-%version.%language.po
* @endcode
*
* Multiple custom modules or themes sharing the same po file should have
* matching definitions. Such as modules and sub-modules or multiple modules in
* the same project/code tree. Both "interface translation project" and
* "interface translation server pattern" definitions of these modules should match.
*
* Example .info file properties for a custom module with a po file located on
* a remote translation server.
* @code
* interface translation project = example_module
* interface translation server pattern = http://example.com/files/translations/%core/%project/%project-%version.%language.po
* @endcode
*
* Custom themes, features and distributions can implement these .info file
* properties in their .info file too.
*
* To change the interface translation settings of modules and themes hosted at
* drupal.org use hook_locale_translation_projects_alter(). Possible changes
* include changing the po file location (server pattern) or removing the
* project from the translation update list.
*
* Available .info file properties:
* - "interface translation project": project name. Required.
* Name of the project a (sub-)module belongs to. Multiple modules sharing
* the same project name will be listed as one the translation status list.
* - "interface translation server pattern": URL of the .po translation files
* used to download the files from. The URL contains tokens which will be
* replaced by appropriate values. The file can be locate both at a local
* relative path, a local absolute path and a remote server location.
*
* The following tokens are available for the server pattern:
* - "%core": Core version. Value example: "8.x".
* - "%project": Project name. Value examples: "drupal", "media_gallery".
* - "%version": Project version release. Value examples: "8.1", "8.x-1.0".
* - "%language": Language code. Value examples: "fr", "pt-pt".
*
* @} End of "defgroup interface_translation_properties".
*/
/**
* @addtogroup hooks
* @{
*/
/**
* Alter the list of projects to be updated by locale's interface translation.
*
* Locale module attempts to update the translation of those modules returned
* by update_get_projects(). Using this hook the data returned by
* update_get_projects() can be altered or extended.
*
* Modules or distributions that use a dedicated translation server should use
* this hook to specify the interface translation server pattern.
* - "interface translation server pattern": URL of the .po translation files
* used to download the files from. The URL contains tokens which will be
* replaced by appropriate values.
* The following tokens are available for the server pattern:
* - "%core": Core version. Value example: "8.x".
* - "%project": Project name. Value examples: "drupal", "media_gallery".
* - "%version": Project version release. Value examples: "8.1", "8.x-1.0".
* - "%language": Language code. Value examples: "fr", "pt-pt".
*
* @param array $projects
* Project data as returned by update_get_projects().
*
* @see locale_project_list().
*/
function hook_locale_translation_projects_alter(&$projects) {
// The translations are located at a custom translation sever.
$projects['existing_project'] = array(
'info' => array(
'interface translation server pattern' => 'http://example.com/files/translations/%core/%project/%project-%version.%language.po',
),
);
}
/**
* @} End of "addtogroup hooks".
*/
<?php
/**
* @file
* The API for comparing project translation status with available translation.
*/
/**
* Default location of gettext file on the translation server.
*
* @see locale_translation_default_translation_server().
*/
const LOCALE_TRANSLATION_DEFAULT_SERVER_PATTERN = 'http://ftp.drupal.org/files/translations/%core/%project/%project-%version.%language.po';
use Drupal\Core\Cache;
/**
* Get array of projects which are available for interface translation.
*
* This project data contains all projects which will be checked for available
* interface translations.
*
* For full functionality this function depends on Update module.
* When Update module is enabled the project data will contain the most recent
* module status; both in enabled status as in version. When Update module is
* disabled this function will return the last known module state. The status
* will only be updated once Update module is enabled.
*
* @see locale_translation_build_projects().
*
* @return array
* Array of project data for translation update. See
* locale_translation_build_projects() for details.
*/
function locale_translation_get_projects() {
$projects = &drupal_static(__FUNCTION__, array());
if (empty($projects)) {
// Get project data from the database.
$projects = array();
$result = db_query('SELECT * FROM {locale_project}');
if ($result->rowCount() == 0 && module_exists('update')) {
// At least the core project should be in the database, so we build the
// data if none are found.
locale_translation_build_projects();
$result = db_query('SELECT * FROM {locale_project}');
}
foreach ($result as $project) {
$projects[$project->name] = $project;
}
}
return $projects;
}
/**
* Clear the project data table.
*/
function locale_translation_flush_projects() {
db_truncate('locale_project')->execute();
}
/**
* Builds list of projects and stores the result in the database.
*
* The project data is based on the project list supplied by the Update module.
* Only the properties required by Locale module is included and additional
* (custom) modules and translation server data is added.
*
* In case the Update module is disabled this function will return an empty
* array.
*
* @return array
* Array of project data:
* - "name": Project system name.
* - "project_type": Project type, e.g. 'module', 'theme'.
* - "core": Core release version, e.g. 8.x
* - "version": Project release version, e.g. 8.x-1.0
* - "server_pattern": Translation server po file pattern.
* - "status": Project status, 1 = enabled.
*/
function locale_translation_build_projects() {
// This function depends on Update module. We degrade gracefully.
if (!module_exists('update')) {
return array();
}
// Get the project list based on .info files.
$projects = locale_translation_project_list();
// Mark all previous projects as disabled and store new project data.
db_update('locale_project')
->fields(array(
'status' => 0,
))
->execute();
$default_server = locale_translation_default_translation_server();
$project_updates = update_get_available(TRUE);
foreach ($projects as $name => $data) {
if (isset($project_updates[$name]['releases']) && $project_updates[$name]['project_status'] != 'not-fetched') {
// Find out if a dev version is installed.
if (preg_match("/^[0-9]+\.x-([0-9]+)\..*-dev$/", $data['info']['version'], $matches)) {
// Find a suitable release to use as alternative translation.
foreach ($project_updates[$name]['releases'] as $project_release) {
// The first release with the same major release number which is not a
// dev release is the one. Releases are sorted the most recent first.
if ($project_release['version_major'] == $matches[1] &&
(!isset($project_release['version_extra']) || $project_release['version_extra'] != 'dev')) {
$release = $project_release;
break;
}
}
}
elseif ($name == "drupal" || preg_match("/HEAD/", $data['info']['version'], $matches)) {
// Pick latest available release.
$release = array_shift($project_updates[$name]['releases']);
}
if (!empty($release['version'])) {
$data['info']['version'] = $release['version'];
}
unset($release);
}
$data += array(
'version' => isset($data['info']['version']) ? $data['info']['version'] : '',
'core' => isset($data['info']['core']) ? $data['info']['core'] : DRUPAL_CORE_COMPATIBILITY,
// A project can provide the path and filename pattern to download the
// gettext file. Use the default if not.
'server_pattern' => isset($data['info']['interface translation server pattern']) ? $data['info']['interface translation server pattern'] : $default_server['pattern'],
'status' => $data['project_status'] ? 1 : 0,
);
$project = (object) $data;
$projects[$name] = $project;
// Create or update the project record.
db_merge('locale_project')
->key(array('name' => $project->name))
->fields(array(
'name' => $project->name,
'project_type' => $project->project_type,
'core' => $project->core,
'version' => $project->version,
'server_pattern' => $project->server_pattern,
'status' => $project->status,
))
->execute();
}
return $projects;
}
/**
* Fetch an array of projects for translation update.
*
* @return array
* Array of project data including .info file data.
*/
function locale_translation_project_list() {
// This function depends on Update module. We degrade gracefully.
if (!module_exists('update')) {
return array();
}
$projects = &drupal_static(__FUNCTION__, array());
if (empty($projects)) {
module_load_include('compare.inc', 'update');
$projects = array();
$additional_whitelist = array(
'interface translation project',
'interface translation server pattern',
);
$module_data = _locale_translation_prepare_project_list(system_rebuild_module_data(), 'module');
$theme_data = _locale_translation_prepare_project_list(system_rebuild_theme_data(), 'theme');
update_process_info_list($projects, $module_data, 'module', TRUE, $additional_whitelist);
update_process_info_list($projects, $theme_data, 'theme', TRUE, $additional_whitelist);
if (variable_get('locale_translation_check_disabled', 0)) {
update_process_info_list($projects, $module_data, 'module', FALSE, $additional_whitelist);
update_process_info_list($projects, $theme_data, 'theme', FALSE, $additional_whitelist);
}
// Allow other modules to alter projects before fetching and comparing.
drupal_alter('locale_translation_projects', $projects);
}
return $projects;
}
/**
* Prepare module and theme data.
*
* Modify .info file data before it is processed by update_process_info_list().
* In order for update_process_info_list() to recognize a project, it requires
* the 'project' parameter in the .info file data.
* Custom modules or themes can bring their own gettext translation file. To
* enable import of this file the module or theme defines "interface translation
* project = myproject" in its .info file. This function will add a project
* "myproject" to the info data.
*
* @param array $data
* Array of .info file data.
* @param string $type
* The project type. i.e. module, theme.
*
* @return array
* Array of .info file data.
*/
function _locale_translation_prepare_project_list($data, $type) {
foreach ($data as $name => $file) {
// Include interface translation projects.
// Custom modules can bring their own gettext translation file.
// To enable import of this file the module must define
// 'interface translation project = myproject' in its .info file.
// To allow update_process_info_list() to identify this as a project
// the 'project' property is filled with the 'interface translation project'
// value.
if (isset($file->info['interface translation project'])) {
$data[$name]->info['project'] = $file->info['interface translation project'];
}
}
return $data;
}
/**
* Retrieve data for default server.
*
* @return array
* Array of server parameters:
* - "server_pattern": URL containing po file pattern.
*/
function locale_translation_default_translation_server() {
return array(
'pattern' => variable_get('locale_translation_default_server_pattern', LOCALE_TRANSLATION_DEFAULT_SERVER_PATTERN),
);
}
/**
* Build path to translation source, out of a server path replacement pattern.
*
* @param stdClass $project
* Project object containing data to be inserted in the template.
* @param string $template
* String containing placeholders. Available placeholders:
* - "%project": Project name.
* - "%version": Project version.
* - "%core": Project core version.
* - "%language": Language code.
* - "%filename": Project file name.
*
* @return string
* String with replaced placeholders.
*/
function locale_translation_build_server_pattern($project, $template) {
$variables = array(
'%project' => $project->name,
'%version' => $project->version,
'%core' => $project->core,
'%language' => isset($project->language) ? $project->language : '%language',
'%filename' => isset($project->filename) ? $project->filename : '%filename',
);
return strtr($template, $variables);
}
......@@ -32,6 +32,9 @@ function locale_uninstall() {
variable_del('locale_cache_length');
variable_del('locale_translation_plurals');
variable_del('locale_translation_javascript');
variable_del('locale_translation_test_system_info_alter');
variable_del('locale_translation_check_disabled');
variable_del('locale_translation_default_server_pattern');
// Remove all node type language variables. Node module might have been
// enabled, but may be disabled, so use a wildcard delete.
......@@ -159,6 +162,55 @@ function locale_schema() {
'primary key' => array('uri', 'langcode'),
);
$schema['locale_project'] = array(
'description' => 'Translation status information for projects and server data.',
'fields' => array(
'name' => array(
'description' => 'A unique short name to identify the project.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
),
'project_type' => array(
'description' => 'Project type, may be core, module, theme',
'type' => 'varchar',
'length' => 15,
'not null' => TRUE,
),
'core' => array(
'description' => 'Core compatibility string for this project.',
'type' => 'varchar',
'length' => 4,
'not null' => TRUE,
'default' => '',
),
'version' => array(
'description' => 'The version release of the project.',
'type' => 'varchar',
'length' => 128,
'not null' => TRUE,
'default' => '',
),
'server_pattern' => array(
'description' => 'Pattern of path and name of the gettext file at the translation server.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'status' => array(
'description' => 'The update status of the project.',
'type' => 'int',
'not null' => TRUE,
'default' => 1,
),
),
'primary key' => array('name'),
);
$schema['cache_locale'] = drupal_get_schema_unprocessed('system', 'cache');
$schema['cache_locale']['description'] = 'Cache table for the locale module to store various data.';
return $schema;
}
......@@ -622,6 +674,113 @@ function locale_update_8010() {
db_create_table('locale_file', $table);
}
/**
* Add a cache table and locale_project table for the locale module.
*/
function locale_update_8011() {
// Add a 'locale' cache table.
db_create_table('cache_locale', array(
'description' => 'Cache table for the locale module to store various data.',
'fields' => array(
'cid' => array(
'description' => 'Primary Key: Unique cache ID.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'data' => array(
'description' => 'A collection of data to cache.',
'type' => 'blob',
'not null' => FALSE,
'size' => 'big',
),
'expire' => array(
'description' => 'A Unix timestamp indicating when the cache entry should expire, or 0 for never.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
'created' => array(
'description' => 'A Unix timestamp indicating when the cache entry was created.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
'serialized' => array(
'description' => 'A flag to indicate whether content is serialized (1) or not (0).',
'type' => 'int',
'size' => 'small',
'not null' => TRUE,
'default' => 0,
),
'tags' => array(
'description' => 'Space-separated list of cache tags for this entry.',
'type' => 'text',
'size' => 'big',
'not null' => FALSE,
),
'checksum' => array(
'description' => 'The tag invalidation sum when this entry was saved.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
),
'indexes' => array(
'expire' => array('expire'),
),
'primary key' => array('cid'),
));
// Add locale_project table.
db_create_table('locale_project', array(
'description' => 'Translation status information for projects and server data.',
'fields' => array(
'name' => array(
'description' => 'A unique short name to identify the project.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
),
'project_type' => array(
'description' => 'Project type, may be core, module, theme',
'type' => 'varchar',
'length' => 15,
'not null' => TRUE,
),
'core' => array(
'description' => 'Core compatibility string for this project.',
'type' => 'varchar',
'length' => 4,
'not null' => TRUE,
'default' => '',
),
'version' => array(
'description' => 'The release version of the project.',
'type' => 'varchar',
'length' => 128,
'not null' => TRUE,
'default' => '',
),
'server_pattern' => array(
'description' => 'Pattern of path and name of the gettext file at the translation server.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'status' => array(
'description' => 'The update status of the project.',
'type' => 'int',
'not null' => TRUE,
'default' => 1,
),
),
'primary key' => array('name'),
));
}
/**
* @} End of "addtogroup updates-7.x-to-8.x".
* The next series of updates should start at 9000.
......
name = Locale test
description = Support module for locale module testing.
package = Testing
version = 1.2
core = 8.x
hidden = TRUE
; Definitions for interface translations.
interface translation project = locale_test
interface translation server pattern = core/modules/locale/test/modules/locale_test/%project-%version.%language.po
<?php
/**
* @file
* Simulate a custom module with a local po file.
*/
/**
* Implements hook_system_info_alter().
*
* Make the test scripts to be believe this is not a hidden test module, but
* a regular custom module.
*/
function locale_test_system_info_alter(&$info, $file, $type) {
// Only modify the system info if required.
// By default the locale_test modules are hidden and have a project specified.
// To test the module detection proces by locale_project_list() the
// test modules should mimic a custom module. I.e. be non-hidden.
if (!variable_get('locale_translation_test_system_info_alter', FALSE)) {
return;
}
if ($file->name == 'locale_test' || $file->name == 'locale_test_disabled') {
// Make the module appear as not-disabled.
$info['hidden'] = FALSE;
}
}
name = Disabled locale test
description = Disabled support module for locale module testing.
package = Testing
version = VERSION
core = 8.x
hidden = TRUE
project = locale_test_disabled
; Definitions for interface translation.
interface translation project = locale_test_disabled
<?php
/**
* @file
* Simulate a disabled contrib for Locale test scripts.
*/
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment