2022-03-08 15:55:41 +01:00
|
|
|
<?php namespace ProcessWire;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* ProcessWire Language Process, displays languages in Setup > Languages >
|
|
|
|
*
|
|
|
|
* It also contains the hooks for altering the output of the InputfieldFile to hold language info and links.
|
|
|
|
* This is the process assigned to processwire/setup/languages/.
|
|
|
|
*
|
|
|
|
* ProcessWire 3.x, Copyright 2019 by Ryan Cramer
|
|
|
|
* https://processwire.com
|
|
|
|
*
|
2022-11-05 18:32:48 +01:00
|
|
|
* @method string execute()
|
|
|
|
* @method string executeDownload()
|
|
|
|
* @method bool|int processCSV($csvFile, Language $language, array $options = array())
|
2022-03-08 15:55:41 +01:00
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
class ProcessLanguage extends ProcessPageType {
|
|
|
|
|
|
|
|
static public function getModuleInfo() {
|
|
|
|
return array(
|
|
|
|
'title' => __('Languages', __FILE__),
|
|
|
|
'version' => 103,
|
|
|
|
'summary' => __('Manage system languages', __FILE__),
|
|
|
|
'author' => 'Ryan Cramer',
|
|
|
|
'requires' => 'LanguageSupport',
|
|
|
|
'icon' => 'language',
|
|
|
|
'useNavJSON' => true,
|
|
|
|
'permission' => 'lang-edit',
|
|
|
|
'permissions' => array(
|
|
|
|
'lang-edit' => 'Administer languages and static translation files'
|
2023-03-10 19:41:40 +01:00
|
|
|
)
|
|
|
|
);
|
2022-03-08 15:55:41 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The URL to the language-translator page (typically admin/setup/language-translator/)
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
protected $translationUrl = '';
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Array of messages for language_files, indexed by file basename
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
protected $fileMessages = array();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* CSV file to process, if present
|
|
|
|
*
|
|
|
|
* @var Pagefile|null
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
protected $csvImportLabel = '';
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Populate the fields shown in the default language list output
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public function __construct() {
|
|
|
|
parent::__construct();
|
|
|
|
|
|
|
|
$showFields = array('name', 'title', 'language_files', 'language_files_site');
|
|
|
|
$this->set('showFields', $showFields);
|
|
|
|
$this->set('jsonListLabel', 'title|name');
|
|
|
|
require_once(dirname(__FILE__) . '/LanguageParser.php');
|
2024-04-04 14:37:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Wired to ProcessWire instance
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public function wired() {
|
|
|
|
parent::wired();
|
|
|
|
$fields = $this->wire()->fields;
|
|
|
|
|
2022-03-08 15:55:41 +01:00
|
|
|
// make sure our files fields have CSV support
|
|
|
|
foreach(array('language_files', 'language_files_site') as $fieldName) {
|
2024-04-04 14:37:20 +02:00
|
|
|
$field = $fields->get($fieldName);
|
2022-03-08 15:55:41 +01:00
|
|
|
if(!$field) continue;
|
2024-04-04 14:37:20 +02:00
|
|
|
$extensions = $field->get('extensions');
|
2022-03-08 15:55:41 +01:00
|
|
|
if(strpos($extensions, 'csv') === false) {
|
|
|
|
$field->set('extensions', "$extensions csv");
|
|
|
|
$field->save();
|
|
|
|
$this->message("Added CSV support to field $fieldName", Notice::debug);
|
|
|
|
}
|
|
|
|
}
|
2024-04-04 14:37:20 +02:00
|
|
|
|
2022-03-08 15:55:41 +01:00
|
|
|
$this->csvImportLabel = $this->_('CSV Import:') . ' ';
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Add InputfieldFile hooks
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public function init() {
|
2024-04-04 14:37:20 +02:00
|
|
|
$this->addHookBefore('InputfieldFile(name^=language_files)::render', $this, 'renderInputfieldFile');
|
2022-03-08 15:55:41 +01:00
|
|
|
$this->addHookAfter('InputfieldFile::renderItem', $this, 'renderInputfieldFileItem');
|
|
|
|
$this->addHookAfter('InputfieldFile::renderUpload', $this, 'renderInputfieldFileUpload');
|
|
|
|
$this->addHookBefore('InputfieldFile::processInput', $this, 'processInputfieldFileInput');
|
|
|
|
|
2023-03-10 19:41:40 +01:00
|
|
|
if(!$this->wire()->config->ajax) {
|
2022-03-08 15:55:41 +01:00
|
|
|
$this->addHookBefore('InputfieldForm::render', $this, 'renderInputfieldForm');
|
|
|
|
}
|
|
|
|
parent::init();
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function translationUrl() {
|
|
|
|
if(!$this->translationUrl) {
|
2023-03-10 19:41:40 +01:00
|
|
|
/** @var LanguageSupport $support */
|
|
|
|
$support = $this->wire()->modules->get('LanguageSupport');
|
|
|
|
$this->translationUrl = $this->wire()->pages->get($support->languageTranslatorPageID)->url;
|
2022-03-08 15:55:41 +01:00
|
|
|
}
|
|
|
|
return $this->translationUrl;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function processInputfieldFileInput(HookEvent $event) {
|
|
|
|
/** @var InputfieldFile $inputfield */
|
|
|
|
$inputfield = $event->object;
|
|
|
|
$inputfield->overwrite = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Hook for before InputfieldFile::render
|
|
|
|
*
|
|
|
|
* In this case we add an 'edit' link to the translator and some info about the translation file.
|
|
|
|
*
|
|
|
|
* @param HookEvent $event
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public function renderInputfieldFile(HookEvent $event) {
|
|
|
|
|
2023-03-10 19:41:40 +01:00
|
|
|
$inputfield = $event->object; /** @var InputfieldFile $inputfield */
|
|
|
|
$language = $this->wire()->process->getPage(); /** @var Language $language */
|
2022-03-08 15:55:41 +01:00
|
|
|
|
|
|
|
/** @var Pagefiles $pagefiles */
|
|
|
|
$pagefiles = $inputfield->attr('value');
|
|
|
|
|
|
|
|
foreach($pagefiles as $pagefile) {
|
2023-03-10 19:41:40 +01:00
|
|
|
/** @var Pagefile $pagefile */
|
2022-03-08 15:55:41 +01:00
|
|
|
if($pagefile->ext() != 'csv') continue;
|
|
|
|
$pagefiles->remove($pagefile);
|
|
|
|
$this->processCSV($pagefile->filename(), $language);
|
|
|
|
}
|
|
|
|
|
|
|
|
$inputfield->descriptionRows = 0;
|
|
|
|
$inputfield->overwrite = true;
|
|
|
|
$inputfield->noCollapseItem = true;
|
|
|
|
$inputfield->noShortName = true;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
public function renderInputfieldForm(HookEvent $event) {
|
|
|
|
|
|
|
|
/** @var InputfieldForm $form */
|
|
|
|
$form = $event->object;
|
|
|
|
$language = $this->getPage();
|
2023-03-10 19:41:40 +01:00
|
|
|
|
2022-03-08 15:55:41 +01:00
|
|
|
if(!$language->id) return;
|
2023-03-10 19:41:40 +01:00
|
|
|
|
2022-03-08 15:55:41 +01:00
|
|
|
$file = $language->filesManager()->path . '.phrase-index.txt';
|
2023-03-10 19:41:40 +01:00
|
|
|
|
|
|
|
/** @var InputfieldMarkup $inputfield */
|
|
|
|
$inputfield = $this->wire()->modules->get('InputfieldMarkup');
|
2022-03-08 15:55:41 +01:00
|
|
|
$inputfield->label = $this->_('Live Search');
|
|
|
|
$inputfield->icon = 'search';
|
|
|
|
$placeholder = $this->_('Text to search for');
|
|
|
|
$refreshUrl = "../../language-translator/add/?language_id=$language->id&refresh=1";
|
|
|
|
$refreshLabel = $this->_('Refresh search phrase index');
|
|
|
|
|
|
|
|
if(!is_file($file)) {
|
|
|
|
$inputfield->value = "<p><a href='$refreshUrl'>" .
|
|
|
|
$this->_('Click here to build search phrase index') . "</a></p>";
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
$phrases = file_get_contents($file);
|
|
|
|
$phrases = str_replace(array('"', "\n", "<", ">"), ' ', $phrases);
|
2023-03-10 19:41:40 +01:00
|
|
|
$script = 'script';
|
2022-03-08 15:55:41 +01:00
|
|
|
|
|
|
|
$inputfield->value =
|
2023-03-10 19:41:40 +01:00
|
|
|
"<$script>" .
|
2022-03-08 15:55:41 +01:00
|
|
|
"var phraseIndex = \"$phrases\"; " .
|
|
|
|
"var phraseLanguageID = $language->id;" .
|
2023-03-10 19:41:40 +01:00
|
|
|
"</$script>" .
|
2022-03-08 15:55:41 +01:00
|
|
|
"<p class='description' style='margin:0'>" .
|
|
|
|
$this->_('Search all translatable files for specific text/phrase.') . ' ' .
|
|
|
|
$this->_('Click found matches to edit translation or add file (if not already present).') .
|
|
|
|
"<p>" .
|
|
|
|
"<p>" .
|
|
|
|
"<input type='text' class='language-phrase-search InputfieldIgnoreChanges' style='width:50%' name='_q' placeholder='$placeholder' /> " .
|
|
|
|
"<span class='detail language-phrase-search-cnt'></span> " .
|
|
|
|
"<a class='pw-tooltip' title='$refreshLabel' href='$refreshUrl'>" . wireIconMarkup('refresh') . "</a>" .
|
|
|
|
"</p>";
|
|
|
|
}
|
|
|
|
|
|
|
|
$field = $form->getChildByName('language_files_site');
|
|
|
|
if($field) $form->insertBefore($inputfield, $field);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Hook for InputfieldFile::renderItem
|
|
|
|
*
|
|
|
|
* In this case we add an 'edit' link to the translator and some info about the translation file.
|
|
|
|
*
|
|
|
|
* @param HookEvent $event
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public function renderInputfieldFileItem(HookEvent $event) {
|
|
|
|
|
|
|
|
$translationUrl = $this->translationUrl();
|
2023-03-10 19:41:40 +01:00
|
|
|
$pagefile = $event->arguments[0]; /** @var Pagefile $pagefile */
|
|
|
|
$page = $pagefile->get('page'); /** @var Language $page */
|
2022-03-08 15:55:41 +01:00
|
|
|
|
|
|
|
if($pagefile->ext() == 'csv') {
|
|
|
|
$event->return .=
|
|
|
|
"<div class='InputfieldFileData InputfieldFileLanguageInfo'>" .
|
|
|
|
"<span class='InputfieldFileLanguageFilename description'>" .
|
|
|
|
$this->_('CSV translation file to be imported after save') .
|
|
|
|
"</span>" .
|
|
|
|
"</div>";
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$textdomain = basename($pagefile->basename, '.json');
|
|
|
|
|
|
|
|
$data = $page->translator->getTextdomain($textdomain);
|
|
|
|
$file = $data['file'];
|
2023-03-10 19:41:40 +01:00
|
|
|
$pathname = $this->wire()->config->paths->root . $file;
|
2022-03-08 15:55:41 +01:00
|
|
|
$translations =& $data['translations'];
|
|
|
|
$total = count($translations);
|
2023-03-10 19:41:40 +01:00
|
|
|
|
|
|
|
/** @var LanguageParser $parser */
|
2022-03-08 15:55:41 +01:00
|
|
|
$parser = $this->wire(new LanguageParser($page->translator, $pathname));
|
|
|
|
$untranslated = $parser->getUntranslated();
|
|
|
|
$alternates = $parser->getAlternates();
|
|
|
|
$numPending = 0;
|
|
|
|
$numAbandoned = 0;
|
|
|
|
$numFallback = 0;
|
|
|
|
|
|
|
|
foreach($untranslated as $hash => $text) {
|
|
|
|
if(!isset($translations[$hash]) || !strlen($translations[$hash]['text'])) $numPending++;
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach($translations as $hash => $translation) {
|
|
|
|
if(isset($untranslated[$hash])) continue;
|
|
|
|
$numAbandoned++;
|
|
|
|
if($page->isDefault()) continue;
|
|
|
|
foreach($alternates as $srcHash => $values) {
|
|
|
|
if(isset($values[$hash]) && isset($untranslated[$srcHash])) {
|
|
|
|
$numFallback++;
|
|
|
|
$numAbandoned--;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$total += $numAbandoned;
|
|
|
|
$message = sprintf($this->_n("%d phrase", "%d phrases", $total), $total);
|
|
|
|
|
|
|
|
if($numAbandoned || $numPending || $numFallback) {
|
|
|
|
$a = array();
|
|
|
|
if($numAbandoned) $a[] = sprintf($this->_('%d abandoned'), $numAbandoned);
|
|
|
|
if($numPending) $a[] = sprintf($this->_('%d blank'), $numPending);
|
|
|
|
if($numFallback) $a[] = sprintf($this->_('%d fallback'), $numFallback);
|
|
|
|
$message = " <span class='ui-state-error-text'>(" . implode(' / ', $a) . ")</span>";
|
|
|
|
}
|
|
|
|
|
|
|
|
$editLabel = $this->_x('Edit', 'edit-language-file');
|
|
|
|
|
|
|
|
$out =
|
|
|
|
"<div class='InputfieldFileData InputfieldFileLanguageInfo'>" .
|
|
|
|
"<span class='InputfieldFileLanguageFilename description'>/$file —</span> <span class='notes'>$message</span> " .
|
|
|
|
"<a class='action' href='{$translationUrl}edit/?language_id={$page->id}&textdomain=$textdomain'> " .
|
|
|
|
"<i class='fa fa-edit'></i> $editLabel <i class='fa fa-angle-double-right hover-only'></i></a>" .
|
|
|
|
"</div>";
|
|
|
|
|
|
|
|
$page->translator->unloadTextdomain($textdomain);
|
|
|
|
|
|
|
|
$event->return .= $out;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Hook for InputfieldFile::renderUpload
|
|
|
|
*
|
|
|
|
* This just adds a 'new' link to add a new translation file.
|
|
|
|
*
|
|
|
|
* @param HookEvent $event
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public function renderInputfieldFileUpload(HookEvent $event) {
|
2023-03-10 19:41:40 +01:00
|
|
|
|
|
|
|
$modules = $this->wire()->modules;
|
2022-03-08 15:55:41 +01:00
|
|
|
$translationUrl = $this->translationUrl();
|
2023-03-10 19:41:40 +01:00
|
|
|
$pagefiles = $event->arguments(0); /** @var Pagefiles $pagefiles */
|
|
|
|
$page = $pagefiles->get('page'); /** @var Page $page */
|
|
|
|
$inputfield = $event->object; /** @var InputfieldFile $inputfield */
|
2022-03-08 15:55:41 +01:00
|
|
|
$out = '';
|
|
|
|
|
|
|
|
/** @var InputfieldButton $btn1 */
|
2023-03-10 19:41:40 +01:00
|
|
|
$btn1 = $modules->get('InputfieldButton');
|
2022-03-08 15:55:41 +01:00
|
|
|
$btn1->href = "{$translationUrl}add/?language_id={$page->id}";
|
|
|
|
$btn1->value = $this->_('Find Files to Translate');
|
|
|
|
$btn1->icon = 'plane';
|
|
|
|
if($inputfield->name == 'language_files') $btn1->showInHeader();
|
|
|
|
$out .= $btn1->render();
|
|
|
|
|
|
|
|
if(count($inputfield->attr('value'))) {
|
|
|
|
/** @var InputfieldButton $btn2 */
|
2023-03-10 19:41:40 +01:00
|
|
|
$btn2 = $modules->get('InputfieldButton');
|
2022-03-08 15:55:41 +01:00
|
|
|
$btn2->href = "../download/?language_id={$page->id}&field=" . $inputfield->attr('name');
|
|
|
|
$btn2->value = $this->_('Download ZIP');
|
|
|
|
$btn2->icon = 'file-zip-o';
|
|
|
|
$btn2->setSecondary();
|
|
|
|
$btn2->addClass('download-button');
|
|
|
|
$out .= $btn2->render();
|
|
|
|
|
|
|
|
$btn2->href .= '&csv=1';
|
|
|
|
$btn2->value = $this->_('Download CSV');
|
|
|
|
$btn2->icon = 'file-excel-o';
|
|
|
|
$out .= $btn2->render();
|
|
|
|
}
|
|
|
|
|
|
|
|
$event->return .= "<p>$out</p>";
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Modify the output per-field in the PageType list (template-method)
|
|
|
|
*
|
|
|
|
* In this case we make it return a count for the language_files
|
|
|
|
*
|
|
|
|
* @param string $name
|
|
|
|
* @param mixed $value
|
|
|
|
* @return string
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
protected function renderListFieldValue($name, $value) {
|
|
|
|
if($name == 'language_files' || $name == 'language_files_site') {
|
|
|
|
return count($value);
|
|
|
|
} else if($name == 'title') {
|
|
|
|
if(!$value) return '(blank)';
|
|
|
|
return (string) $value;
|
|
|
|
} else {
|
|
|
|
return parent::renderListFieldValue($name, $value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public function ___execute() {
|
|
|
|
// check if 2.5 update needed to add new language_files_site field
|
2023-03-10 19:41:40 +01:00
|
|
|
if(!$this->wire()->fields->get('language_files_site')) {
|
2022-03-08 15:55:41 +01:00
|
|
|
require_once(dirname(__FILE__) . '/LanguageSupportInstall.php');
|
2023-03-10 19:41:40 +01:00
|
|
|
/** @var LanguageSupportInstall $installer */
|
2022-03-08 15:55:41 +01:00
|
|
|
$installer = $this->wire(new LanguageSupportInstall());
|
2023-03-10 19:41:40 +01:00
|
|
|
$installer->addFilesFields($this->wire()->fieldgroups->get(LanguageSupport::languageTemplateName));
|
2022-03-08 15:55:41 +01:00
|
|
|
}
|
|
|
|
return parent::___execute();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create and send a ZIP of translation files or CSV of translations
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public function ___executeDownload() {
|
|
|
|
|
|
|
|
$config = $this->wire()->config;
|
|
|
|
$input = $this->wire()->input;
|
|
|
|
|
|
|
|
$id = (int) $input->get('language_id');
|
|
|
|
if(!$id) throw new WireException("No language specified");
|
|
|
|
|
|
|
|
$language = $this->wire()->languages->get($id);
|
|
|
|
if(!$language->id) throw new WireException("Unknown language");
|
|
|
|
|
|
|
|
$fieldName = $input->get('field') == 'language_files_site' ? 'language_files_site' : 'language_files';
|
|
|
|
$textdomain = $this->wire()->sanitizer->textdomain($input->get('textdomain'));
|
|
|
|
$textdomains = array();
|
|
|
|
$csv = (int) $input->get('csv');
|
|
|
|
$path = $language->$fieldName->path();
|
|
|
|
$files = array();
|
|
|
|
|
|
|
|
if($textdomain) {
|
|
|
|
$file = $language->translator->textdomainToFilename($textdomain);
|
|
|
|
if($file) {
|
|
|
|
$files[] = $file;
|
|
|
|
$textdomains[$file] = $textdomain;
|
|
|
|
} else {
|
|
|
|
$textdomain = '';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-10 19:41:40 +01:00
|
|
|
if(!count($files)) {
|
2022-03-08 15:55:41 +01:00
|
|
|
foreach($language->$fieldName as $file) {
|
|
|
|
$files[] = $file->filename;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!count($files)) {
|
|
|
|
throw new WireException('No translation files specified to download');
|
|
|
|
}
|
|
|
|
|
|
|
|
if($csv) {
|
|
|
|
// CSV
|
|
|
|
if($textdomain) {
|
|
|
|
// i.e. es-modulename.csv
|
|
|
|
$parts = explode('--', $textdomain);
|
|
|
|
$basename = array_pop($parts);
|
|
|
|
$parts = explode('-', $basename);
|
|
|
|
$basename = array_shift($parts);
|
|
|
|
$filename = "$language->name-$basename.csv";
|
|
|
|
} else {
|
|
|
|
// i.e. es-site.csv or es-wire.csv
|
|
|
|
$filename = $language->name . "-" . (strpos($fieldName, 'site') ? 'site' : 'wire') . ".csv";
|
|
|
|
}
|
|
|
|
if($input->get('view')) {
|
|
|
|
header("Content-type: text/plain");
|
|
|
|
} else {
|
|
|
|
header("Content-type: application/force-download");
|
|
|
|
header("Content-Transfer-Encoding: Binary");
|
|
|
|
header("Content-disposition: attachment; filename=$filename");
|
|
|
|
}
|
|
|
|
|
|
|
|
$fp = fopen('php://output', 'w');
|
|
|
|
$defaultCol = $language->name == 'en' ? 'default' : 'en';
|
|
|
|
$fields = array($defaultCol, $language->name, 'description', 'file', 'hash');
|
|
|
|
fputcsv($fp, $fields);
|
|
|
|
|
|
|
|
foreach($files as $f) {
|
|
|
|
|
|
|
|
if(isset($textdomains[$f])) {
|
|
|
|
$textdomain = $textdomains[$f];
|
|
|
|
} else {
|
|
|
|
$textdomain = basename($f, '.json');
|
|
|
|
}
|
|
|
|
|
|
|
|
$data = $language->translator->getTextdomain($textdomain);
|
|
|
|
if(empty($data)) continue;
|
|
|
|
|
|
|
|
$file = $data['file'];
|
|
|
|
$pathname = $config->paths->root . $file;
|
|
|
|
$translated =& $data['translations'];
|
|
|
|
$parser = $this->wire(new LanguageParser($language->translator, $pathname)); /** @var LanguageParser $parser */
|
|
|
|
$untranslated = $parser->getUntranslated();
|
|
|
|
$comments = $parser->getComments();
|
|
|
|
|
|
|
|
foreach($untranslated as $hash => $text1) {
|
|
|
|
$text2 = isset($translated[$hash]) ? $translated[$hash]['text'] : '';
|
|
|
|
$comment = isset($comments[$hash]) ? $comments[$hash] : '';
|
|
|
|
if(strpos($comment, '//') !== false) list(, $comment) = explode('//', $comment);
|
|
|
|
$fields = array($text1, $text2, trim($comment), $file, $hash);
|
|
|
|
fputcsv($fp, $fields);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fclose($fp);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
// ZIP
|
|
|
|
$zipname = $language->name . "-";
|
|
|
|
$zipname .= $fieldName == 'language_files' ? 'wire' : 'site';
|
|
|
|
$zipfile = "$path$zipname.zip";
|
|
|
|
$info = wireZipFile($zipfile, $files, array("overwrite" => true));
|
|
|
|
if(!count($info['files'])) {
|
|
|
|
$this->error("Error adding files to ZIP");
|
|
|
|
$this->session->redirect('../');
|
|
|
|
} else {
|
|
|
|
wireSendFile($zipfile);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
exit(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Process a CSV file to import changes from it
|
|
|
|
*
|
|
|
|
* Must be in the same format as the one provied by the executeDownload() method
|
|
|
|
*
|
|
|
|
* @param string $csvFile
|
|
|
|
* @param Language $language
|
|
|
|
* @param array $options Additional options (3.0.181+)
|
|
|
|
* - `file` (string): Use this path/file (relative to install root)
|
|
|
|
* - `quiet` (bool): Suppress generating notifications? (default=false)
|
|
|
|
* @return bool|int Returns false on error or integer on success, where value is number of translations imported
|
|
|
|
* @throws WireException
|
2022-11-05 18:32:48 +01:00
|
|
|
* @since 3.0.195 Previously was not hookable
|
2022-03-08 15:55:41 +01:00
|
|
|
*
|
|
|
|
*/
|
2022-11-05 18:32:48 +01:00
|
|
|
public function ___processCSV($csvFile, Language $language, array $options = array()) {
|
2022-03-08 15:55:41 +01:00
|
|
|
|
|
|
|
$defaults = array(
|
|
|
|
'file' => '',
|
|
|
|
'quiet' => false,
|
|
|
|
);
|
|
|
|
|
|
|
|
$options = array_merge($defaults, $options);
|
|
|
|
|
|
|
|
$fp = fopen($csvFile, "r");
|
|
|
|
|
|
|
|
if($fp === false) {
|
|
|
|
if(!$options['quiet']) $this->error($this->csvImportLabel . "Unable to open: $csvFile");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
$keys = array(
|
|
|
|
'original',
|
|
|
|
'translated',
|
|
|
|
'file',
|
|
|
|
'hash',
|
|
|
|
);
|
|
|
|
|
|
|
|
$n = 0;
|
|
|
|
$header = array();
|
|
|
|
$translator = new LanguageTranslator($language);
|
|
|
|
$textdomain = '';
|
|
|
|
$lastTextdomain = '';
|
|
|
|
$lastFile = '';
|
|
|
|
$numChanges = 0;
|
|
|
|
$numTotal = 0;
|
|
|
|
$numGross = 0;
|
|
|
|
$translations = null;
|
|
|
|
$optionsFileBasename = '';
|
|
|
|
$halt = false;
|
|
|
|
|
|
|
|
$this->wire($translator);
|
|
|
|
|
|
|
|
if(!empty($options['file'])) {
|
|
|
|
$options['file'] = ltrim($this->wire()->files->unixFileName($options['file']), '/');
|
|
|
|
$optionsFileBasename = basename($options['file']);
|
|
|
|
}
|
|
|
|
|
|
|
|
while(($csvData = fgetcsv($fp, 8192, ",")) !== FALSE) {
|
|
|
|
|
|
|
|
if(++$n === 1) {
|
|
|
|
// header row
|
|
|
|
$header = $csvData;
|
|
|
|
foreach($header as $key => $value) {
|
|
|
|
$header[$key] = strtolower($value);
|
|
|
|
}
|
|
|
|
// make sure everything we need is present
|
|
|
|
foreach($keys as $k => $key) {
|
|
|
|
if($k > 1 && !in_array($key, $header)) {
|
|
|
|
if($key === 'file' && !empty($options['file'])) {
|
|
|
|
// default file provided so not required in CSV data
|
|
|
|
} else {
|
|
|
|
if(!$options['quiet']) $this->error($this->csvImportLabel . "CSV data missing required column '$key'");
|
|
|
|
$halt = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if($halt) break;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$row = array();
|
|
|
|
foreach($header as $key => $name) {
|
|
|
|
if($key === 0) $name = 'original';
|
|
|
|
if($key === 1) $name = 'translated';
|
|
|
|
$row[$name] = $csvData[$key];
|
|
|
|
}
|
|
|
|
|
|
|
|
if($options['file']) {
|
|
|
|
if(empty($row['file'])) {
|
|
|
|
$row['file'] = $options['file'];
|
|
|
|
} else {
|
|
|
|
$rowFileBasename = basename($row['file']);
|
|
|
|
if($rowFileBasename === $optionsFileBasename) {
|
|
|
|
// i.e. site/modules/Hello/Hello.module
|
|
|
|
$row['file'] = $options['file'];
|
|
|
|
} else {
|
|
|
|
// i.e. site/modules/Hello/World.module
|
|
|
|
$row['file'] = dirname($options['file']) . '/' . $rowFileBasename;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(empty($row['original']) || empty($row['file'])) continue;
|
|
|
|
|
|
|
|
$file = $row['file'];
|
|
|
|
$hash = $row['hash'];
|
|
|
|
// $textOriginal = $row['original'];
|
|
|
|
$textTranslated = $row['translated'];
|
|
|
|
$textdomain = $translator->filenameToTextdomain($file);
|
|
|
|
|
|
|
|
if(!$translator->textdomainFileExists($textdomain)) {
|
|
|
|
$textdomain = $translator->addFileToTranslate($file, false, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
if(is_null($translations)) {
|
|
|
|
$translations = $translator->getTranslations($textdomain);
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!$textdomain) {
|
|
|
|
if(!$options['quiet']) $this->warning($this->csvImportLabel . sprintf(
|
|
|
|
$this->_('Unrecognized textdomain for file: %s'),
|
|
|
|
$this->wire()->sanitizer->entities($file)
|
|
|
|
));
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if($textdomain != $lastTextdomain) {
|
|
|
|
if(!$lastFile) $lastFile = $file;
|
|
|
|
if(!$lastTextdomain) $lastTextdomain = $textdomain;
|
|
|
|
$this->processCSV_saveTextdomain($translator, $lastTextdomain, $lastFile, $numChanges);
|
|
|
|
$translations = $translator->getTranslations($textdomain);
|
|
|
|
$numChanges = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
$translation = isset($translations[$hash]) ? $translations[$hash] : array('text' => '');
|
|
|
|
if($translation['text'] != $textTranslated) {
|
|
|
|
$translator->setTranslationFromHash($textdomain, $hash, $textTranslated);
|
|
|
|
$numChanges++;
|
|
|
|
$numTotal++;
|
|
|
|
}
|
|
|
|
|
|
|
|
$lastTextdomain = $textdomain;
|
|
|
|
$lastFile = $file;
|
|
|
|
$numGross++;
|
|
|
|
}
|
|
|
|
|
|
|
|
if($numChanges) {
|
|
|
|
$this->processCSV_saveTextdomain($translator, $textdomain, $lastFile, $numChanges);
|
|
|
|
}
|
|
|
|
$language->save();
|
|
|
|
|
|
|
|
fclose($fp);
|
|
|
|
if(!$options['quiet']) {
|
|
|
|
$this->message($this->csvImportLabel . sprintf($this->_('%d total translations, %d total changes'), $numGross, $numTotal), Notice::noGroup);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $halt ? false : $numGross;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Save a textdomain, helper for processCSV method
|
|
|
|
*
|
|
|
|
* @param LanguageTranslator $translator
|
|
|
|
* @param string $textdomain
|
|
|
|
* @param string $filename
|
|
|
|
* @param int $numChanges
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
protected function processCSV_saveTextdomain(LanguageTranslator $translator, $textdomain, $filename, $numChanges) {
|
|
|
|
if($filename) { /* ignore, not currently used */ }
|
|
|
|
$file = $translator->textdomainToFilename($textdomain);
|
|
|
|
if($numChanges) {
|
|
|
|
try {
|
|
|
|
$translator->saveTextdomain($textdomain);
|
|
|
|
$this->message($this->csvImportLabel . sprintf($this->_('Saved %d change(s) for file: %s'), $numChanges, $file), Notice::noGroup);
|
|
|
|
} catch(\Exception $e) {
|
|
|
|
$this->error($e->getMessage());
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// no changes
|
|
|
|
}
|
|
|
|
$translator->unloadTextdomain($textdomain);
|
|
|
|
}
|
|
|
|
}
|