function drush_localize_fields in Localize Fields 7
Parameters
string $path:
File
- ./
localize_fields.drush.inc, line 41 - Drupal Localize Fields module
Code
function drush_localize_fields($path = '') {
$success = TRUE;
$overwrite = FALSE;
drush_print(' ');
// Get context delimiter.
if (!class_exists('LocalizeFields')) {
if (!file_exists('LocalizeFields.inc')) {
drush_log(dt('Can\'t find class !class nor file !file.', array(
'!class' => 'LocalizeFields',
'!file' => 'LocalizeFields.inc',
)), 'error');
return;
}
require 'LocalizeFields.inc';
if (!class_exists('LocalizeFields')) {
drush_log(dt('Can\'t find file !file.', array(
'!file' => 'LocalizeFields.inc',
)), 'error');
return;
}
}
$cntxtDelim = LocalizeFields::CONTEXT_DELIMITER;
if (!module_exists('locale')) {
drush_log(dt('The locale module must be installed.'), 'error');
return;
}
$test = drush_get_option('test', FALSE);
if ($test) {
drush_print(dt('In test mode') . ':', 2);
drush_print(dt('- converted copies of the Features files will be written to private://drush/localize_fields'), 2);
drush_print(dt('- all database changes are simulations') . "\n", 2);
}
else {
$overwrite = TRUE;
}
$output_dir = '';
if (!$overwrite) {
// Set up output dir, if it doesnt exist.
if (!drupal_strlen($output_dir = variable_get('file_private_path', ''))) {
drush_log(dt('Private files directory not defined, check variable file_private_path, or in GUI: /admin/config/media/file-system'), 'error');
return;
}
if (!is_dir($output_dir .= '/drush/localize_fields') && !mkdir($output_dir, 0775, TRUE)) {
drush_log(dt('Output dir [!dir] doesnt exist and cannot be created.', array(
'!dir' => $output_dir,
)), 'error');
return;
}
}
// Establish and check path to find Features files in.
$dir = $path;
$document_root = DRUPAL_ROOT;
$cli_working_dir = !empty($_SERVER['PWD']) ? $_SERVER['PWD'] : '';
if (!($os_nix = DIRECTORY_SEPARATOR == '/')) {
// Replace Windows directory separators.
$dir = str_replace('\\', '/', $dir);
$document_root = str_replace('\\', '/', $document_root);
$cli_working_dir = str_replace('\\', '/', $cli_working_dir);
}
// Remove trailing slash.
if ($dir[strlen($dir) - 1] == '/') {
$dir = substr($dir, 0, strlen($dir) - 1);
}
// Absolute?
if ($dir[0] === '/' || !$os_nix && preg_match('/^[a-zA-Z]:\\//', $dir)) {
if (!file_exists($dir)) {
drush_log(dt('Bad path argument, that absolute dir doesnt exist - saw path[' . $dir . '].'), 'error');
return;
}
}
else {
if (!($resolved = realpath($cli_working_dir . '/' . $dir))) {
drush_log(dt('Bad path argument, that relative dir doesnt exist - saw path[' . $dir . '].'), 'error');
return;
}
$dir = $os_nix ? $resolved : str_replace('\\', '/', $resolved);
}
// Working outside current site's document root is forbidden.
if (stripos($dir, $document_root . '/') !== 0) {
drush_log(dt('This script is not allowed to work outside current site\'s document root - saw path[' . $dir . '].'), 'error');
return;
}
if (!is_dir($dir)) {
drush_log(dt('Bad path argument, that file isnt a directory - saw path[' . $dir . '].'), 'error');
return;
}
drush_log(dt('Will work in dir and sub dirs of: !dir', array(
'!dir' => $dir,
)), 'status');
$use_context = drush_get_option('usecontext', 'nully');
$remove_empty = FALSE;
if ($use_context === 'nully') {
$use_context = variable_get('localize_fields_usecontext', LocalizeFields::USE_CONTEXT_NONCONTEXT);
}
if ($use_context) {
drush_log(dt('Will use translation context - t()s will get context argument.'), 'status');
}
else {
drush_log(dt('Will NOT use translation context. You may override by using the --usecontext option.'), 'warning');
if (!($remove_empty = drush_get_option('removeempty', 0))) {
drush_log(dt('And will NOT remove possibly overshadowing sources that equals fields\' sources, but have no translations. You may override by using the --removeempty option.'), 'status');
}
else {
drush_log(dt('However - will remove possibly overshadowing sources that equals fields\' sources, but have no translations.'), 'status');
}
if (!drush_confirm(dt('Do you really want to continue, without translation context?'))) {
drush_log(dt('Aborting.'));
}
}
// Escaping.
$escape_needles = array(
"\n",
"'",
);
$escape_substitutes = array(
'\\n',
'\\\'',
);
// Find all the Features files under path.
// Details:
// - skip .features.inc (the content type file): Features automatically insert t()s directly around the sentences, and it translation works.
// - .field_group.inc: Find label and description, and add them t()'d to bottom of file (we don't translate field_group).
// - .features.field_base.inc: Find list_ type options, and add them t()'d to bottom of file.
// - .features.field_instance.inc: Find number_ type prefix/suffix, and add them t()'d to bottom of file.
// Find all the features files under path.
$list = array();
_localize_fields_list_files($list, $dir);
if (!$list) {
drush_log(dt('Aborted: Found no .features.field_base.inc, .features.field_instance.inc, .field_group.inc, .features.field_group.inc files.'), 'warning');
return;
}
drush_print(' ');
drush_print('Will update database tables locales_source and locales_target.');
drush_print('A list of database changes will be logged to watchdog.');
drush_print(' ');
// List files for confirmation.
$filenames = array_keys($list);
$confirm_list = array();
$current_module = '_impossible_.';
$index_modules = -1;
foreach ($filenames as $filename) {
if (strpos($filename, $current_module) !== 0) {
$current_module = substr($filename, 0, strpos($filename, '.')) . '.';
$confirm_list[++$index_modules] = $filename;
}
else {
$confirm_list[$index_modules] .= ' ..' . substr($filename, strpos($filename, '.'));
}
}
unset($filenames);
drush_print(dt('Will work on these files:'));
foreach ($confirm_list as $filenames) {
drush_print($filenames, 2);
}
drush_print(' ');
if (!$test) {
if (!drush_confirm(dt('Do you really want to continue?'))) {
drush_log(dt('Aborting.'));
return;
}
drush_print(' ');
}
else {
drush_print(dt('Do you really want to continue?'));
drush_print(dt('- Aye, it\'s just a test...' . "\n"));
}
// Use db.locales_target.l10n_status column if it exists.
$column_l10n_status = module_exists('features_translations');
$no_work_files = array();
$identical_files = array();
$work_files = array();
$files_completed = array();
$inspections = array();
try {
foreach ($list as $filename => $path) {
// Find type.
if (strpos($filename, '.field_base.inc')) {
$type = 'field_base';
}
elseif (strpos($filename, '.field_instance.inc')) {
$type = 'field_instance';
}
elseif (strpos($filename, '.field_group.inc')) {
$type = 'field_group';
}
else {
drush_log(dt('Unsupported Features file[' . $path . '].'), 'error');
break;
}
// Read file and get rid of carriage returns.
$original = file_get_contents($path);
$output = str_replace("\r", '', $original);
// Find first function.
$matches = array();
if (!preg_match('/\\nfunction\\ ?([a-z\\d\\_]+)[\\ \\(]/', $output, $matches) || empty($matches[1])) {
drush_log(dt('Cant find any function in file[' . $path . '].'), 'error');
return;
}
$function = $matches[1];
// Find sources.
switch ($type) {
case 'field_base':
$sources = _localize_fields_type_field_base($path, $function);
break;
case 'field_instance':
$sources = _localize_fields_type_field_instance($path, $function);
break;
case 'field_group':
$sources = _localize_fields_type_field_group($path, $function);
break;
default:
drush_log(dt('Unsupported Features file type[' . $type . '].'), 'error');
return;
}
if ($sources) {
$ts = array();
$sources_distinct = array();
$sources_to_db = array();
foreach ($sources as $props) {
// NB: field_group items have no context.
$context = $props['context'];
foreach ($props['sources'] as $key => $src) {
if (is_array($src)) {
// List options.
foreach ($src as $item) {
// We don't translate integers, whereas decimals may have to be translated because of the decimal separator.
if ($item && !ctype_digit('' . $item)) {
$source = str_replace($escape_needles, $escape_substitutes, $item);
// Don't update in db if no context at all (field_group).
if ($context) {
$sources_distinct[] = $source;
$sources_to_db[] = array(
$source,
!$context ? '' : $context . $cntxtDelim . $key,
);
}
$ts[] = 't' . "('" . $source . "'" . (!$use_context || !$context ? '' : ", array(), array('context' => '" . $context . $cntxtDelim . $key . "')") . ');';
}
}
}
elseif ($src && !ctype_digit('' . $src)) {
$source = str_replace($escape_needles, $escape_substitutes, $src);
// Don't update in db if no context at all (field_group).
if ($context) {
$sources_distinct[] = $source;
$sources_to_db[] = array(
$source,
!$context ? '' : $context . $cntxtDelim . $key,
);
}
$ts[] = 't' . "('" . $source . "'" . (!$use_context || !$context ? '' : ", array(), array('context' => '" . $context . $cntxtDelim . $key . "')") . ');';
}
}
}
// DB operations.
// Not using context: remove context, if/when matching sources with matching context exist.
// Using context: clone a source + target and add context to source (and user must clean up afterwards using the Potx module).
// Location is unreliable, so we don't search by that.
if ($sources_to_db) {
$existing_sources = db_select('locales_source', 'src')
->fields('src', array(
'lid',
'location',
'source',
'context',
))
->condition('textgroup', 'default')
->condition('source', $sources_distinct, 'IN')
->orderBy('source')
->orderBy('context')
->execute()
->fetchAllAssoc('lid');
if ($existing_sources) {
$inspections[] = 'Sources exist in database for file: ' . $filename . '.';
$existing_source_ids = array_keys($existing_sources);
// Using context, or remove possibly overshadowing empty sources: we need the targets too.
$existing_targets = array();
// IDE.
if ($use_context || $remove_empty) {
$existing_targets = db_select('locales_target', 'trg')
->fields('trg', array(
'lid',
'translation',
'language',
))
->condition('lid', $existing_source_ids, 'IN')
->condition('plural', 0)
->execute()
->fetchAll();
if ($existing_targets) {
$inspections[] = '...and targets exist.';
}
}
// Make a copy of db sources that is searchable by source.
$existing_sources_search = array();
foreach ($existing_sources as $source_id => $props) {
$existing_sources_search[$source_id] = $props->source;
}
foreach ($sources_to_db as $src_cntxt) {
$source = $src_cntxt[0];
$context = $src_cntxt[1];
// There may be more db sources matching a source, if context was used previously.
$matching_source_ids = array();
$match_found = FALSE;
if ($source_id = array_search($source, $existing_sources_search, TRUE)) {
// Find all db sources that match source.
$copy_sources_search = $existing_sources_search;
$matching_source_ids[] = $source_id;
unset($copy_sources_search[$source_id]);
while ($source_id = array_search($source, $copy_sources_search, TRUE)) {
$matching_source_ids[] = $source_id;
unset($copy_sources_search[$source_id]);
}
foreach ($matching_source_ids as $source_id) {
if ($existing_sources[$source_id]->context === $context) {
$match_found = $source_id;
// Save it for !$use_context && $remove_empty.
if (!$use_context) {
// Remove context.
if (!$test) {
db_update('locales_source')
->fields(array(
'context' => '',
))
->condition('lid', $source_id, '=')
->execute();
}
$inspections[] = '- updated (cleared) context of db source: context [' . $context . '], source >>' . $source . '<<.';
}
else {
// Use context: found a match, no more work to do, continue to next source.
continue 2;
}
break;
}
}
// Get rid of overshadowing empty translations.
if ($remove_empty) {
foreach ($matching_source_ids as $source_id) {
if ((!$match_found || $source_id != $match_found) && $existing_sources[$source_id]->context === '' && $existing_sources[$source_id]->location !== '' && $existing_sources[$source_id]->location[0] == '/') {
$removable_source = TRUE;
foreach ($existing_targets as $props) {
// A translation exists, thus we can't remove source.
if ($props->lid == $source_id && $props->translation !== '') {
$removable_source = FALSE;
break;
}
}
if ($removable_source) {
if (!$test) {
db_delete('locales_source')
->condition('lid', $source_id, '=')
->execute();
}
$inspections[] = '- deleted possibly overshadowing (no translation) for db source: context [' . $context . '], source >>' . $source . '<<.';
}
// There may actually be more than one (sic!).
}
}
}
elseif (!$match_found && $use_context) {
// Find first having same location (file only, disregarding line number), and having only one location.
foreach ($matching_source_ids as $source_id) {
if ($existing_sources[$source_id]->context === '' && strpos($existing_sources[$source_id]->location, $filename . ':') === 0 && strpos($existing_sources[$source_id]->location, ' ') === FALSE) {
$match_found = TRUE;
// Set context.
if (!$test) {
db_update('locales_source')
->fields(array(
'context' => $context,
))
->condition('lid', $source_id, '=')
->execute();
}
$inspections[] = '- updated (set) context of db source: context [' . $context . '], source >>' . $source . '<<.';
// We only do this once.
break;
}
}
if (!$match_found) {
// Find the first having a translation, and preferring empty context (see $existing_targets orderBy('context') ascending).
foreach ($matching_source_ids as $source_id) {
// Check if there's any targets at all.
$any_matching_targets = FALSE;
foreach ($existing_targets as $props) {
if ($props->lid == $source_id && $props->translation !== '') {
$any_matching_targets = TRUE;
break;
}
}
if ($any_matching_targets) {
// Insert dupe source, that has context.
$new_source_id = '';
// IDE.
if (!$test) {
$new_source_id = db_insert('locales_source')
->fields(array(
'location' => $filename . ':0',
'source' => $source,
'context' => $context,
))
->execute();
}
$inspections[] = '- new source: context [' . $context . '], source >>' . $source . '<<.';
// Insert dupe targets, for every language
foreach ($existing_targets as $props) {
if ($props->lid == $source_id && $props->translation !== '') {
if (!$test) {
$fields = array(
'lid' => $new_source_id,
'translation' => $props->translation,
'language' => $props->language,
);
if ($column_l10n_status) {
$fields['l10n_status'] = 1;
}
db_insert('locales_target')
->fields($fields)
->execute();
}
$inspections[] = '- new (cloned) target: language [' . $props->language . '], source >>' . $props->translation . '<<.';
}
}
break;
}
}
}
}
}
}
}
else {
// Do nothing.
$inspections[] = 'No sources exist in database for file: ' . $filename . '.';
}
}
// Filing.
if ($ts) {
$comment = '// Translatables created/updated by the Localize Fields (localize_fields) module.';
array_unshift($ts, $comment);
// Remove this module's comment.
$needle = '/\\n\\n?\\ *' . preg_quote($comment, '/') . '/';
// Double? \n fixes that our lines tend to move down one line for every file write.
if (preg_match($needle, $output)) {
$output = preg_replace($needle, '', $output);
}
// Remove existing t()'s at end of file.
$needle = '/\\n\\ *t\\(\'.+\'\\)?\\);/ms';
// Multiline and lazy.
if (preg_match($needle, $output)) {
$output = preg_replace($needle, '', $output);
}
// Find end of file.
$needle = '/(\\n\\ *)return \\$([a-zA-Z\\d_]+);\\ *\\n\\n?\\}\\ *\\n?/';
if (!preg_match($needle, $output)) {
drush_log(dt('Can\'t find return/end of file in file[' . $path . '].'), 'error');
continue;
// Skip.
}
else {
$output = preg_replace($needle, ' ' . join("\n ", $ts) . "\n\n" . '$1return \\$$2;' . "\n}\n", $output);
}
// No actual changes?
if (strlen($output) == strlen($original) && $output == $original) {
$identical_files[] = $filename;
}
else {
$work_files[] = $filename;
if (!$overwrite) {
file_put_contents($output_dir . '/' . $filename, $output);
}
else {
file_put_contents($path, $output);
}
}
}
else {
$no_work_files[] = $filename;
}
}
$files_completed[] = $filename;
}
} catch (Exception $xc) {
$success = FALSE;
watchdog('localize_fields', 'Extraction of localization sources failed: @error_message', array(
'@error_message' => $xc
->getMessage(),
), WATCHDOG_ERROR);
drush_log(dt('Failed, error: @error', array(
'@error' => $xc
->getMessage(),
)), 'error');
}
if ($inspections) {
$nl = module_exists('dblog') ? '<br>' : ' ';
watchdog('localize_fields', 'Extraction of localization sources, db operations:' . $nl . join($nl, $inspections), array(), WATCHDOG_INFO);
}
if ($no_work_files) {
drush_print(dt('These !n files contained no translatable labels:!files', array(
'!n' => count($no_work_files),
'!files' => "\n " . join("\n ", $no_work_files),
)) . "\n");
}
if ($identical_files) {
drush_print(dt('These !n files needed no file changes, but may have resulted in database changes (check log):!files', array(
'!n' => count($identical_files),
'!files' => "\n " . join("\n ", $identical_files),
)) . "\n");
}
if ($work_files) {
if (!$overwrite) {
drush_print(dt('These !n files got t()ed in copies filed to private files sub dir [!dir]:!files', array(
'!n' => count($work_files),
'!files' => "\n " . join("\n ", $work_files),
'!dir' => count($output_dir),
)) . "\n");
}
else {
drush_print(dt('These !n files got t()ed and overwritten!files', array(
'!n' => count($work_files),
'!files' => "\n " . join("\n ", $work_files),
)) . "\n");
}
}
if (!$success) {
$files_remaining = array_diff(array_keys($list), $files_completed);
drush_log(dt('These !n files were not completed:!files', array(
'!n' => count($files_remaining),
'!files' => "\n " . join("\n ", $files_remaining),
)), 'warning');
}
if ($success) {
if (!$test) {
cache_clear_all('locale:', 'cache', TRUE);
}
drush_log(dt('Cleared translation cache for all languages.'), 'ok');
}
drush_log(dt('!testFeatures files translation extraction !succeeded.', array(
'!test' => !$test ? '' : '(test) ',
'!succeeded' => $success ? 'succeeded' : 'failed',
)), $success ? 'success' : 'failed');
if (!module_exists('localize_fields')) {
drush_log(dt('Field label translation will only work if the localize_fields module is enabled.'), 'warning');
}
}