diff --git a/potx.inc b/potx.inc index 6ee0900..06e3e76 100644 --- a/potx.inc +++ b/potx.inc @@ -1984,34 +1984,46 @@ function _potx_load_yaml_translation_patterns($path) { */ function _potx_parse_yaml_file($code, $file_name, $file_path, $save_callback) { global $yaml_translation_patterns; - global $_potx_config_set; + global $_potx_module_metadata; if (!is_array($yaml_translation_patterns)) { _potx_init_yaml_translation_patterns(); } + try { + $yaml = Yaml::parse($code); + } catch (ParseException $e) { + watchdog('potx', "YAML parseing error on file @path: @error", array( + '@path' => $file_path, + '@error' => $e->getMessage(), + )); + + return; + } + foreach ($yaml_translation_patterns as $pattern => $trans_list) { if (fnmatch($pattern, $file_name) || fnmatch('*/' . $pattern, $file_name)) { - try { - $yaml = Yaml::parse($code); - _potx_find_yaml_translatables($yaml, $trans_list, $file_name, $save_callback); - } catch (ParseException $e) { - watchdog('potx', "YAML parseing error on file @path: @error", array( - '@path' => $file_path, - '@error' => $e->getMessage(), - )); - } + _potx_find_yaml_translatables($yaml, $trans_list, $file_name, $save_callback); } } if (preg_match('~config/schema/[^/]+\.yml$~', $file_name)) { - $schema = Yaml::parse($code); - foreach ($schema as $key => $element) { - _potx_process_config_schema($key, $element); - } + + $module_name = basename(dirname(dirname(dirname($file_name)))); + $_potx_module_metadata[$module_name]['schemas'][] = $file_name; } elseif (preg_match('~config/(install|optional)/[^/]+\.yml$~', $file_name)) { - $_potx_config_set[] = $file_path; + $module_name = basename(dirname(dirname(dirname($file_name)))); + $_potx_module_metadata[$module_name]['configs'][] = $file_name; + } + elseif (preg_match('~[^/]+.info.yml~', $file_name)) { + if (!is_array($module_set)) { + $module_set = array(); + } + $module_name = substr(basename($file_name), 0, -9); + $_potx_module_metadata[$module_name]['path'] = dirname(realpath($file_name)); + $_potx_module_metadata[$module_name]['dependencies'] = isset($yaml['dependencies']) ? $yaml['dependencies'] : array(); + $_potx_module_metadata[$module_name]['version'] = isset($yaml['version']) && $yaml['version'] != 'VERSION' ? $yaml['version'] : NULL; } } @@ -2131,18 +2143,7 @@ function _potx_find_yaml_translatables($yaml, $trans_list, $file_name, $save_cal */ function _potx_process_config_schema($schema_prefix, $schema_data) { global $_potx_processed_schema; - - if (!isset($_potx_processed_schema)) { - // The initial values for 'translatables' allows a limited support for - // extracting translatable strings from contrib projects, until - // https://www.drupal.org/node/1933988 gets in. - $_potx_processed_schema = array( - 'translatables' => array('label', 'text', 'date_format'), - 'types' => array(), - 'mappings' => array(), - 'contexts' => array('date_format' => 'PHP date format'), - ); - } + global $_potx_module_schema; // Elements can opt out of translation with a 'translatable: false' key. if (isset($schema_data['translatable']) && $schema_data['translatable'] === FALSE) { @@ -2167,14 +2168,17 @@ function _potx_process_config_schema($schema_prefix, $schema_data) { if ($type != 'mapping') { $_potx_processed_schema['types'][$schema_prefix] = $type; + $_potx_module_schema['types'][$schema_prefix] = $type; } $_potx_processed_schema['mappings'][] = $schema_prefix; + $_potx_module_schema['mappings'][] = $schema_prefix; } else { if ($type == 'sequence') { _potx_process_config_schema($schema_prefix . '+sequence', $schema_data['sequence'][0]); $_potx_processed_schema['types'][$schema_prefix] = 'sequence'; + $_potx_module_schema['types'][$schema_prefix] = 'sequence'; } // If the element's type is in the "translatables" list, or it has a // 'translatable: true' key, then it is translatable. @@ -2182,18 +2186,22 @@ function _potx_process_config_schema($schema_prefix, $schema_data) { || (isset($schema_data['translatable']) && $schema_data['translatable'] === TRUE)) { $_potx_processed_schema['translatables'][] = $schema_prefix; + $_potx_module_schema['translatables'][] = $schema_prefix; // Elements can define a context string, or inherit the context from their // defined type. if (isset($schema_data['translation context'])) { $_potx_processed_schema['contexts'][$schema_prefix] = $schema_data['translation context']; + $_potx_module_schema['contexts'][$schema_prefix] = $schema_data['translation context']; } elseif (isset($_potx_processed_schema['contexts'][$type])) { $_potx_processed_schema['contexts'][$schema_prefix] = $_potx_processed_schema['contexts'][$type]; + $_potx_module_schema['contexts'][$schema_prefix] = $_potx_processed_schema['contexts'][$type]; } } else { $_potx_processed_schema['types'][$schema_prefix] = $type; + $_potx_module_schema['types'][$schema_prefix] = $type; } } } @@ -2356,27 +2364,167 @@ function _potx_replace_variable($value, $data) { * @param string $api_version */ function _potx_parse_shipped_configuration($save_callback = '_potx_save_string', $api_version = POTX_API_CURRENT) { - global $_potx_config_set; - if (!is_array($_potx_config_set)) { + global $_potx_module_metadata; + global $_potx_schema_cache; + global $_potx_processed_schema; + global $_potx_processed_modules; + + $_potx_schema_cache = array(); + + foreach ($_potx_module_metadata as $module_name => $module_metadata) { + if (!isset($module_metadata['configs'])) { + continue; + } + + $_potx_processed_modules = array(); + $_potx_processed_schema = array( + 'translatables' => array(), + 'types' => array(), + 'mappings' => array(), + 'contexts' => array() + ); + + _potx_process_module_schemas(array('core')); + if (!isset($_potx_schema_cache['core'])) { + $_potx_schema_cache['core'] = $_potx_processed_schema; + } + _potx_process_module_schemas(array($module_name)); + if (!isset($_potx_schema_cache[$module_name])) { + $_potx_schema_cache[$module_name] = $_potx_processed_schema; + } + + foreach ($module_metadata['configs'] as $config_path) { + + $module_path = dirname(dirname(dirname($config_path))); + + // Find the schema that matches the config file. + $path_info = pathinfo($config_path); + $config_name = $path_info['filename']; + $schema = _potx_find_matching_schema($config_name); + + if ($schema == '') { + // No schema was found matching this config. + continue; + } + + $config_code = file_get_contents($config_path); + $parsed_config = Yaml::parse($config_code); + unset($config_code); + + _potx_find_shipped_config_translatables($parsed_config, $schema, NULL, NULL, $config_path, $save_callback); + } + } +} + +function _potx_process_module_schemas($module_list) { + global $_potx_module_metadata; + global $_potx_schema_cache; + global $_potx_processed_modules; + + $module_list = array_diff($module_list, $_potx_processed_modules); + + foreach ($module_list as $module_name) { + if (isset($_potx_schema_cache[$module_name])) { + unset($module_list[array_search($module_name, $module_list)]); + _potx_merge_processed_schema($_potx_schema_cache[$module_name]); + } + } + + if (count($module_list) == 0) { return; } - foreach ($_potx_config_set as $config_path) { - // Find the schema that matches the config file. - $path_info = pathinfo($config_path); - $config_name = $path_info['filename']; - $schema = _potx_find_matching_schema($config_name); + // Mark as processed early, to prevent a loop while traversing dependency graph. + $_potx_processed_modules = array_merge($_potx_processed_modules, $module_list); + + $dependencies = array(); + $result = db_query("SELECT * FROM {potx_parsed_data} WHERE module_name IN (:module_names)", array(':module_names' => $module_list)); + foreach ($result as $record) { + $record->dependencies = unserialize($record->dependencies); + if (is_array($record->dependencies)) { + $dependencies = array_merge($dependencies, $record->dependencies); + } + if (_potx_schema_needs_rebuild($record)) { + _potx_store_module_schema($record->module_name, $record); + } + else { + $record->parsed_schema = unserialize($record->parsed_schema); + _potx_merge_processed_schema($record->parsed_schema); + } - if ($schema == '') { - // No schema was found matching this config. + unset($module_list[array_search($record->module_name, $module_list)]); + } + + $dependencies = array_unique($dependencies); + _potx_process_module_schemas($dependencies); + + foreach ($module_list as $module_name) { + if (!isset($_potx_module_metadata[$module_name])) { + $message = t('Module @module not found. Some shipped configuration translatable strings might be missed.', array('@module' => $module_name)); + potx_status('error', $message); continue; } - $config_code = file_get_contents($config_path); - $parsed_config = Yaml::parse($config_code); - unset($config_code); + _potx_store_module_schema($module_name); + } +} + +function _potx_schema_needs_rebuild($record) { + global $_potx_module_metadata; + + // If the module is found in the filesystem, but it's a different version, or a dev version, it needs a rebuild. + return isset($_potx_module_metadata[$record->module_name]) && + (!isset($_potx_module_metadata[$record->module_name]['version']) || + ($_potx_module_metadata[$record->module_name]['version'] != $record->module_version)); +} + +function _potx_merge_processed_schema($schema) { + global $_potx_processed_schema; + + $_potx_processed_schema['translatables'] = array_merge($_potx_processed_schema['translatables'], $schema['translatables']); + $_potx_processed_schema['types'] = array_merge($_potx_processed_schema['types'], $schema['types']); + $_potx_processed_schema['mappings'] = array_merge($_potx_processed_schema['mappings'], $schema['mappings']); + $_potx_processed_schema['contexts'] = array_merge($_potx_processed_schema['contexts'], $schema['contexts']); +} + +function _potx_store_module_schema($module_name, $record = NULL) { + global $_potx_module_metadata; + global $_potx_module_schema; + + $_potx_module_schema = array( + 'translatables' => array(), + 'types' => array(), + 'mappings' => array(), + 'contexts' => array() + ); + + if (is_array($_potx_module_metadata[$module_name]['schemas'])) { + foreach ($_potx_module_metadata[$module_name]['schemas'] as $file_path) { + $code = file_get_contents($file_path); + $yaml = Yaml::parse($code); + foreach ($yaml as $key => $element) { + _potx_process_config_schema($key, $element); + } + } + } + + + if ($record == NULL) { + $record = new stdClass(); + $record->module_name = $module_name; + if ($module_name != 'core') { + $record->module_version = $_potx_module_metadata[$module_name]['version']; + $record->dependencies = $_potx_module_metadata[$module_name]['dependencies']; + } + + $record->parsed_schema = $_potx_module_schema; + + drupal_write_record('potx_parsed_data', $record); + } + else { + $record->parsed_schema = $_potx_module_schema; - _potx_find_shipped_config_translatables($parsed_config, $schema, NULL, NULL, $config_path, $save_callback); + drupal_write_record('potx_parsed_data', $record, array('pid')); } } diff --git a/potx.install b/potx.install index 0cbf947..0454936 100644 --- a/potx.install +++ b/potx.install @@ -29,3 +29,47 @@ function potx_requirements($phase) { return $requirements; } + +/** + * Implements hook_schema(). + */ +function potx_schema() { + $schema = array(); + + $schema['potx_parsed_data'] = array( + 'description' => 'Parsed data for modules (Drupal 8+)', + 'fields' => array( + 'pid' => array( + 'description' => 'Parse ID.', + 'type' => 'serial', + 'not null' => TRUE, + ), + 'module_name' => array( + 'description' => 'Module name.', + 'type' => 'varchar', + 'length' => '255', + 'not null' => TRUE, + ), + 'module_version' => array( + 'description' => 'Module version.', + 'type' => 'varchar', + 'length' => '32', + ), + 'dependencies' => array( + 'description' => 'The module\'s dependencies.', + 'type' => 'text', + 'size' => 'medium', + 'serialize' => TRUE + ), + 'parsed_schema' => array( + 'description' => 'The module\'s parsed schema.', + 'type' => 'text', + 'size' => 'big', + 'serialize' => TRUE + ), + ), + 'primary key' => array('pid'), + ); + + return $schema; +}