diff --git a/core/modules/locale/lib/Drupal/locale/Tests/LocaleCompareUnitTest.php b/core/modules/locale/lib/Drupal/locale/Tests/LocaleCompareUnitTest.php new file mode 100644 index 0000000..2ab1959 --- /dev/null +++ b/core/modules/locale/lib/Drupal/locale/Tests/LocaleCompareUnitTest.php @@ -0,0 +1,72 @@ + 'Locale Compare', + 'description' => 'Test handling of translation comparison.', + 'group' => 'Locale', + ); + } + + function setUp() { + parent::setUp(array('locale', 'locale_test')); + + // Create Article node type. + $this->drupalCreateContentType(array('type' => 'article', 'name' => 'Article')); + + // Create and login user. + $admin_user = $this->drupalCreateUser(array('administer site configuration', 'administer languages', 'access administration pages', 'create article content')); + $this->drupalLogin($admin_user); + } + + /** + * Functional tests for localizing date formats. + */ + function testLocaleCompare() { + module_load_include('compare.inc', 'locale'); + variable_set('locale_check_disabled', 1); + + + // Check if hidden modules are not included. + $projects = locale_project_list(); + $this->assertFalse(isset($projects['locale_test']), t('Hidden module not found')); + + // Make the test module look like a normale custom module. i.e. the module + // is not hidden. This variable is used in locale_test_system_info_alter to + // modify the project info of the locale_test module. + variable_set('locale_test_system_info_alter', TRUE); + + // Check if interface translation data is collected from .info file. + variable_set('locale_check_disabled', 1); + drupal_static_reset('locale_project_list'); + $projects = locale_project_list(); + $this->assertEqual($projects['locale_test']['info']['interface translation url'], 'http://locale_example.com/files/translations/l10n_server.xml', t('%key found in project info.', array('%key' => 'interface translation url'))); + $this->assertEqual($projects['locale_test']['info']['interface translation pattern'], 'http://locale_example.com/files/translations/%core/%project/%project-%release.%language.po', t('%key found in project info.', array('%key' => 'interface translation pattern'))); + $this->assertEqual($projects['locale_test']['name'] , 'locale_test', t('%key found in project info.', array('%key' => 'interface translation project'))); + + // Check if disabled modules are detected. + // @todo A real disabled module would be better. But if no second + // locale test module is available, this will do. + variable_set('locale_test_disable_locale_test', TRUE); + drupal_static('locale_project_list', array(), TRUE); + $projects = locale_project_list(); + $this->assertTrue(isset($projects['locale_test']), t('Disabled module found')); + variable_del('locale_test_disable_locale_test'); + + variable_del('locale_test_system_info_alter'); + } + +} diff --git a/core/modules/locale/locale.api.php b/core/modules/locale/locale.api.php new file mode 100644 index 0000000..50f6fb6 --- /dev/null +++ b/core/modules/locale/locale.api.php @@ -0,0 +1,10 @@ +name] = $project; + } + } + return $projects; +} + +/** + * Build list of projects and stores the result in the database. + */ +function locale_build_projects() { + // Get all currently stored projects. + // @todo put this in a helper function to cache the db results? + // Or not if this function is called multiple times. For example + // when installing an installation profile. + $current = array(); + $result = db_query('SELECT name FROM {locale_project}'); + foreach ($result as $project) { + $current[$project->name] = $project->name; + } + + // Get the project list based on .info files. + $projects = locale_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_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, + // The project can have its own interface translation server, we use default if not. + 'interface_translation_server' => isset($data['info']['interface translation server']) ? $data['info']['interface translation server'] : NULL, + // A project can provide the server url to fetch metadata, or the path and filename pattern to download the gettext file. + 'interface_translation_url' => isset($data['info']['interface translation url']) ? $data['info']['interface translation url'] : NULL, + 'interface_translation_pattern' => isset($data['info']['interface translation pattern']) ? $data['info']['interface translation pattern'] : NULL, + 'status' => 1, + ); + $project = (object) $data; + // Unless the project provides a full filename pattern, we try to build one. + if (!isset($project->interface_translation_pattern)) { + $server = NULL; + if ($project->interface_translation_server || $project->interface_translation_url) { + $server = locale_translation_server($project->interface_translation_server, $project->interface_translation_url); + } + else { + // Use the default server. + $server = locale_translation_server($default_server['name'], $default_server['server_url']); + } + if ($server) { + // Build the update path for this project, with project name and release replaced. + $project->interface_translation_pattern = locale_build_translation_pattern($project, $server['update_url']); + } + } + // Create / update project record. + $update = empty($current[$name]) ? array() : array('name'); + drupal_write_record('locale_project', $project, $update); + $projects[$name] = $project; + } + return $projects; +} + +/** + * @todo + * Based on l10n_update_project_list(). + */ +function locale_project_list() { + // @todo Can/should we cache this data? + $projects = &drupal_static(__FUNCTION__, array()); + if (empty($projects)) { + module_load_include('compare.inc', 'update'); + $projects = array(); + + $additional_whitelist = array( + 'interface translation server', + 'interface translation url', + 'interface translation pattern', + 'interface translation project', + ); + $module_data = _locale_prepare_project_list($foo = system_rebuild_module_data()); + $theme_data = _locale_prepare_project_list(system_rebuild_theme_data()); + 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_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_projects', $projects); + } + return $projects; +} + +/** + * Prepare module and theme data. + * @todo + */ +function _locale_prepare_project_list($data) { + foreach ($data as $name => $file) { + // @todo Do we need to add hidden projects for interface translation? + // Hidden projects are mainly used in automated tests. + + // 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; +} + +/** + * @todo + * based on l10n_update_default_server(). + */ +function locale_default_translation_server() { + return array( + // @todo Review the naming! + 'name' => variable_get('locale_default_server', LOCALE_DEFAULT_SERVER), + 'server_url' => variable_get('locale_default_server_url', LOCALE_DEFAULT_SERVER_URL), + 'update_url' => variable_get('locale_default_pattern', LOCALE_DEFAULT_PATTERN), + ); +} + +/** + * @todo + * Based on l10n_update_server(). + */ +function locale_translation_server($name = NULL, $url = NULL, $refresh = FALSE) { + $info = &drupal_static(__FUNCTION__, array()); + $server_list = &drupal_static(__FUNCTION__ . ':server_list', array()); + + // Retrieve server list from modules + if (!isset($server_list) || $refresh) { + $server_list = module_invoke_all('locale_translation_servers'); + } + // We need at least the server url to fetch all the information + if (!$url && $name && isset($server_list[$name])) { + $url = $server_list[$name]['server_url']; + } + // If we still don't have an url, cannot find this server, return false + if (!$url) { + return FALSE; + } + // Cache server information based on the url, refresh if asked + $cid = 'interface_translation_server:' . $url; + // @todo Clear cache using a separate function? + if ($refresh) { + unset($info); + cache('locale')->delete($cid); + } + if (!isset($info[$url])) { + if ($cache = cache('locale')->get($cid)) { + $info[$url] = $cache->data; + } + else { + module_load_include('fetch.inc', 'locale'); + if ($name && !empty($server_list[$name])) { + // The name is in our list, it can be full data or just an url + $server = $server_list[$name]; + } + else { + // This may be a new server provided by a module / package + $server = array('name' => $name, 'server_url' => $url); + // If searching by name, store the name => url mapping + if ($name) { + $server_list[$name] = $server; + } + } + // Now fetch server meta information form the server itself + if ($server = locale_get_server($server)) { + cache('locale')->set($cid, $server); + $info[$url] = $server; + } + else { + // If no server information, this will be FALSE. We won't search a server twice + $info[$url] = FALSE; + } + } + } + return $info[$url]; +} + +/** + * @todo + * Based on l10n_update_build_string(). + */ +function locale_build_translation_pattern($project, $template) { + $variables = array( + '%project' => $project->name, + '%release' => $project->version, + '%core' => $project->core, + '%language' => isset($project->language) ? $project->language : '%language', + '%filename' => isset($project->filename) ? $project->filename : '%filename', + ); + return strtr($template, $variables); +} diff --git a/core/modules/locale/locale.fetch.inc b/core/modules/locale/locale.fetch.inc new file mode 100644 index 0000000..8f6d01c --- /dev/null +++ b/core/modules/locale/locale.fetch.inc @@ -0,0 +1,9 @@ +name == 'locale_test') { + $info['hidden'] = FALSE; + } + + // Mark the locale_test module as disabled. + if (variable_get('locale_test_disable_locale_test', FALSE)) { + $info['project_status'] = FALSE; + } +}