2021-09-24 23:58:59 +00:00
|
|
|
<?php
|
|
|
|
/**
|
|
|
|
* @brief translater, a plugin for Dotclear 2
|
2021-11-01 21:32:32 +00:00
|
|
|
*
|
2021-09-24 23:58:59 +00:00
|
|
|
* @package Dotclear
|
|
|
|
* @subpackage Plugin
|
2021-11-01 21:32:32 +00:00
|
|
|
*
|
2021-09-24 23:58:59 +00:00
|
|
|
* @author Jean-Christian Denis & contributors
|
2021-11-01 21:32:32 +00:00
|
|
|
*
|
2021-09-24 23:58:59 +00:00
|
|
|
* @copyright Jean-Christian Denis
|
|
|
|
* @copyright GPL-2.0 https://www.gnu.org/licenses/gpl-2.0.html
|
|
|
|
*/
|
|
|
|
if (!defined('DC_CONTEXT_ADMIN')) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Translater tools.
|
|
|
|
*/
|
|
|
|
class dcTranslaterModule
|
|
|
|
{
|
|
|
|
/** @var dcTranslater dcTranslater instance */
|
|
|
|
public $translater = null;
|
|
|
|
|
|
|
|
/** @var array Module properies */
|
|
|
|
private $prop = [];
|
|
|
|
|
|
|
|
/** @var string Backup file regexp */
|
|
|
|
private $backup_file_regexp = '/^l10n-%s-(.*?)-[0-9]*?\.bck\.zip$/';
|
|
|
|
|
|
|
|
/** @var string Locales file regexp */
|
|
|
|
private $locales_file_regexp = '/^(.*?)\/locales\/(.*?)\/(.*?)(.po|.lang.php)$/';
|
|
|
|
|
|
|
|
public function __construct(dcTranslater $translater, array $module)
|
|
|
|
{
|
|
|
|
$this->translater = $translater;
|
|
|
|
$this->prop = $module;
|
|
|
|
|
2021-11-01 21:32:32 +00:00
|
|
|
$this->prop['root'] = path::real($this->prop['root']);
|
|
|
|
$i = path::info($this->prop['root']);
|
2021-09-24 23:58:59 +00:00
|
|
|
$this->prop['basename'] = $i['basename'];
|
2021-11-01 21:32:32 +00:00
|
|
|
$this->prop['locales'] = $this->prop['root'] . '/locales';
|
2021-09-24 23:58:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get a module property
|
2021-11-01 21:32:32 +00:00
|
|
|
*
|
2021-09-24 23:58:59 +00:00
|
|
|
* @param string $key The module property key
|
|
|
|
* @return mixed The module property value or null
|
|
|
|
*/
|
|
|
|
public function get(string $key)
|
2021-11-01 21:32:32 +00:00
|
|
|
{
|
2021-09-24 23:58:59 +00:00
|
|
|
return array_key_exists($key, $this->prop) ? $this->prop[$key] : null;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Magic get
|
|
|
|
*/
|
|
|
|
public function __get($key)
|
|
|
|
{
|
|
|
|
return $this->get($key);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// @name backup methods
|
|
|
|
//@{
|
|
|
|
/**
|
|
|
|
* Find backup folder of a module
|
2021-11-01 21:32:32 +00:00
|
|
|
*
|
2021-09-24 23:58:59 +00:00
|
|
|
* @param boolean $throw Silently failed
|
|
|
|
* @return mixed The backup folder directory or false
|
|
|
|
*/
|
|
|
|
public function getBackupRoot(bool $throw = false)
|
|
|
|
{
|
|
|
|
$dir = false;
|
2021-11-01 21:32:32 +00:00
|
|
|
switch ($this->translater->backup_folder) {
|
2021-09-24 23:58:59 +00:00
|
|
|
case 'module':
|
|
|
|
if ($this->prop['root_writable']) {
|
|
|
|
$dir = $this->prop['locales'];
|
|
|
|
}
|
2021-11-01 21:32:32 +00:00
|
|
|
|
2022-11-13 17:40:31 +00:00
|
|
|
break;
|
2021-09-24 23:58:59 +00:00
|
|
|
|
|
|
|
case 'plugin':
|
|
|
|
$tmp = path::real(array_pop(explode(PATH_SEPARATOR, DC_PLUGINS_ROOT)));
|
|
|
|
if ($tmp && is_writable($tmp)) {
|
|
|
|
$dir = $tmp;
|
|
|
|
}
|
2021-11-01 21:32:32 +00:00
|
|
|
|
2022-11-13 17:40:31 +00:00
|
|
|
break;
|
2021-09-24 23:58:59 +00:00
|
|
|
|
|
|
|
case 'public':
|
2022-11-13 17:40:31 +00:00
|
|
|
$tmp = path::real(dcCore::app()->blog->public_path);
|
2021-09-24 23:58:59 +00:00
|
|
|
if ($tmp && is_writable($tmp)) {
|
|
|
|
$dir = $tmp;
|
|
|
|
}
|
2021-11-01 21:32:32 +00:00
|
|
|
|
2022-11-13 17:40:31 +00:00
|
|
|
break;
|
2021-09-24 23:58:59 +00:00
|
|
|
|
|
|
|
case 'cache':
|
|
|
|
$tmp = path::real(DC_TPL_CACHE);
|
|
|
|
if ($tmp && is_writable($tmp)) {
|
|
|
|
@mkDir($tmp . '/l10n');
|
|
|
|
$dir = $tmp . '/l10n';
|
|
|
|
}
|
2021-11-01 21:32:32 +00:00
|
|
|
|
2022-11-13 17:40:31 +00:00
|
|
|
break;
|
2021-09-24 23:58:59 +00:00
|
|
|
|
|
|
|
case 'translater':
|
2022-12-26 20:48:51 +00:00
|
|
|
$tmp = path::real(dcCore::app()->plugins->moduleRoot(basename(dirname(__DIR__))));
|
2022-11-13 17:40:31 +00:00
|
|
|
if ($tmp && is_writable($tmp)) {
|
|
|
|
@mkDir($tmp . '/locales');
|
|
|
|
$dir = $tmp . '/locales';
|
|
|
|
}
|
2021-11-01 21:32:32 +00:00
|
|
|
|
2022-11-13 17:40:31 +00:00
|
|
|
break;
|
2021-09-24 23:58:59 +00:00
|
|
|
}
|
|
|
|
if (!$dir && $throw) {
|
|
|
|
throw new Exception(sprintf(
|
2021-11-01 21:32:32 +00:00
|
|
|
__('Failed to find backups folder for module %s'),
|
2022-11-13 17:40:31 +00:00
|
|
|
$this->prop['basename']
|
2021-09-24 23:58:59 +00:00
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
return $dir;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get a list of available backups
|
2021-11-01 21:32:32 +00:00
|
|
|
*
|
2021-09-24 23:58:59 +00:00
|
|
|
* @param boolean $return_filename Return only filenames
|
|
|
|
* @return array The module backups info
|
|
|
|
*/
|
|
|
|
public function getBackups(bool $return_filename = false): array
|
|
|
|
{
|
|
|
|
$backup = $this->getBackupRoot();
|
|
|
|
if (!$backup) {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
2021-11-01 21:32:32 +00:00
|
|
|
$res = [];
|
2021-09-24 23:58:59 +00:00
|
|
|
$files = dcTranslater::scandir($backup);
|
2021-11-01 21:32:32 +00:00
|
|
|
foreach ($files as $file) {
|
2021-09-24 23:58:59 +00:00
|
|
|
$is_backup = preg_match(sprintf($this->backup_file_regexp, preg_quote($this->prop['id'])), $file, $m);
|
|
|
|
|
2021-11-01 21:32:32 +00:00
|
|
|
if (is_dir($backup . '/' . $file)
|
|
|
|
|| !$is_backup
|
2021-09-24 23:58:59 +00:00
|
|
|
|| !l10n::isCode($m[1])
|
|
|
|
) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($return_filename) {
|
|
|
|
$res[] = $file;
|
|
|
|
} else {
|
|
|
|
$res[$m[1]][$file]['code'] = $m[1];
|
|
|
|
$res[$m[1]][$file]['name'] = l10n::getLanguageName($m[1]);
|
|
|
|
$res[$m[1]][$file]['path'] = path::info($backup . '/' . $file);
|
|
|
|
$res[$m[1]][$file]['time'] = filemtime($backup . '/' . $file);
|
|
|
|
$res[$m[1]][$file]['size'] = filesize($backup . '/' . $file);
|
|
|
|
$res[$m[1]][$file]['module'] = $this->prop['id'];
|
|
|
|
}
|
|
|
|
}
|
2021-11-01 21:32:32 +00:00
|
|
|
|
2021-09-24 23:58:59 +00:00
|
|
|
return $res;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create a backup
|
2021-11-01 21:32:32 +00:00
|
|
|
*
|
2021-09-24 23:58:59 +00:00
|
|
|
* @param string $lang The backup lang
|
|
|
|
* @return boolean True on success
|
|
|
|
*/
|
|
|
|
public function createBackup(string $lang): bool
|
|
|
|
{
|
|
|
|
$backup = $this->getBackupRoot(true);
|
|
|
|
|
|
|
|
if (!is_dir($this->prop['locales'] . '/' . $lang)) {
|
|
|
|
throw new Exception(sprintf(
|
2021-11-01 21:32:32 +00:00
|
|
|
__('Failed to find language %s'),
|
|
|
|
$lang
|
2021-09-24 23:58:59 +00:00
|
|
|
));
|
|
|
|
}
|
|
|
|
|
2021-11-01 21:32:32 +00:00
|
|
|
$res = [];
|
2021-09-24 23:58:59 +00:00
|
|
|
$files = dcTranslater::scandir($this->prop['locales'] . '/' . $lang);
|
2021-11-01 21:32:32 +00:00
|
|
|
foreach ($files as $file) {
|
|
|
|
if (!is_dir($this->prop['locales'] . '/' . $lang . '/' . $file)
|
2021-09-24 23:58:59 +00:00
|
|
|
&& (dcTranslater::isLangphpFile($file) || dcTranslater::isPoFile($file))
|
|
|
|
) {
|
2021-11-01 21:32:32 +00:00
|
|
|
$res[$this->prop['locales'] . '/' . $lang . '/' . $file] = $this->prop['id'] . '/locales/' . $lang . '/' . $file;
|
2021-09-24 23:58:59 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!empty($res)) {
|
2022-11-13 17:40:31 +00:00
|
|
|
dcTranslater::isBackupLimit($this->prop['id'], $backup, $this->translater->backup_limit, true);
|
2021-09-24 23:58:59 +00:00
|
|
|
|
|
|
|
@set_time_limit(300);
|
2021-11-01 21:32:32 +00:00
|
|
|
$fp = fopen($backup . '/l10n-' . $this->prop['id'] . '-' . $lang . '-' . time() . '.bck.zip', 'wb');
|
2021-09-24 23:58:59 +00:00
|
|
|
$zip = new fileZip($fp);
|
2021-11-01 21:32:32 +00:00
|
|
|
foreach ($res as $from => $to) {
|
2021-09-24 23:58:59 +00:00
|
|
|
$zip->addFile($from, $to);
|
|
|
|
}
|
|
|
|
$zip->write();
|
|
|
|
$zip->close();
|
|
|
|
unset($zip);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Retore a backup
|
2021-11-01 21:32:32 +00:00
|
|
|
*
|
2021-09-24 23:58:59 +00:00
|
|
|
* @param string $file The backup filename
|
|
|
|
* @return boolean True on success
|
|
|
|
*/
|
|
|
|
public function restoreBackup(string $file): bool
|
|
|
|
{
|
|
|
|
$backup = self::getBackupRoot(true);
|
|
|
|
|
|
|
|
if (!file_exists($backup . '/' . $file)) {
|
|
|
|
throw new Exception(sprintf(
|
2021-11-01 21:32:32 +00:00
|
|
|
__('Failed to find file %s'),
|
|
|
|
$file
|
2021-09-24 23:58:59 +00:00
|
|
|
));
|
|
|
|
}
|
|
|
|
|
2021-11-01 21:32:32 +00:00
|
|
|
$zip = new fileUnzip($backup . '/' . $file);
|
2021-09-24 23:58:59 +00:00
|
|
|
$zip_files = $zip->getFilesList();
|
|
|
|
|
2021-11-01 21:32:32 +00:00
|
|
|
foreach ($zip_files as $zip_file) {
|
2021-09-24 23:58:59 +00:00
|
|
|
$f = $this->parseZipFilename($zip_file, true);
|
|
|
|
$zip->unzip($zip_file, $this->prop['locales'] . '/' . $f['lang'] . '/' . $f['group'] . $f['ext']);
|
|
|
|
$done = true;
|
|
|
|
}
|
|
|
|
$zip->close();
|
|
|
|
unset($zip);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Delete a module backup
|
2021-11-01 21:32:32 +00:00
|
|
|
*
|
2021-09-24 23:58:59 +00:00
|
|
|
* @param string $file The backup filename
|
|
|
|
* @return boolean True on success
|
|
|
|
*/
|
|
|
|
public function deleteBackup(string $file): bool
|
|
|
|
{
|
|
|
|
$backup = $this->getBackupRoot(true);
|
|
|
|
|
|
|
|
$is_backup = preg_match(sprintf($this->backup_file_regexp, preg_quote($this->prop['id'])), $file, $m);
|
|
|
|
|
2021-11-01 21:32:32 +00:00
|
|
|
if (!file_exists($backup . '/' . $file)
|
|
|
|
|| !$is_backup
|
2021-09-24 23:58:59 +00:00
|
|
|
|| !l10n::isCode($m[1])
|
|
|
|
) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!files::isDeletable($backup . '/' . $file)) {
|
|
|
|
throw new Exception(sprintf(
|
2021-11-01 21:32:32 +00:00
|
|
|
__('Failed to delete file %s'),
|
|
|
|
$file
|
2021-09-24 23:58:59 +00:00
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
unlink($backup . '/' . $file);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Import a language pack
|
2021-11-01 21:32:32 +00:00
|
|
|
*
|
2021-09-24 23:58:59 +00:00
|
|
|
* @param array $zip_file The uploaded file info
|
|
|
|
* @return boolean True on success
|
|
|
|
*/
|
|
|
|
public function importPack(array $zip_file): bool
|
|
|
|
{
|
|
|
|
files::uploadStatus($zip_file);
|
|
|
|
|
2021-11-01 21:32:32 +00:00
|
|
|
$imported = false;
|
2021-09-24 23:58:59 +00:00
|
|
|
$not_overwrited = [];
|
2021-11-01 21:32:32 +00:00
|
|
|
$res = [];
|
2021-09-24 23:58:59 +00:00
|
|
|
|
|
|
|
# Load Unzip object
|
2021-11-01 21:32:32 +00:00
|
|
|
$zip = new fileUnzip($zip_file['tmp_name']);
|
2021-09-24 23:58:59 +00:00
|
|
|
$files = $zip->getFilesList();
|
|
|
|
|
2021-11-01 21:32:32 +00:00
|
|
|
foreach ($files as $file) {
|
2021-09-24 23:58:59 +00:00
|
|
|
$f = $this->parseZipFilename($file, true);
|
|
|
|
|
2021-11-01 21:32:32 +00:00
|
|
|
if (!$this->translater->import_overwrite
|
2021-09-24 23:58:59 +00:00
|
|
|
&& file_exists($this->prop['locales'] . '/' . $f['lang'] . '/' . $f['group'] . $f['ext'])
|
|
|
|
) {
|
|
|
|
$not_overwrited[] = implode('-', [$f['lang'], $f['group'], $f['ext']]);
|
2021-11-01 21:32:32 +00:00
|
|
|
|
2021-09-24 23:58:59 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$res[] = [
|
2021-11-01 21:32:32 +00:00
|
|
|
'from' => $file,
|
|
|
|
'root' => $this->prop['locales'] . '/' . $f['lang'],
|
2022-11-13 17:40:31 +00:00
|
|
|
'to' => $this->prop['locales'] . '/' . $f['lang'] . '/' . $f['group'] . $f['ext'],
|
2021-09-24 23:58:59 +00:00
|
|
|
];
|
|
|
|
}
|
|
|
|
|
2021-09-25 15:38:46 +00:00
|
|
|
foreach ($res as $rs) {
|
2021-09-24 23:58:59 +00:00
|
|
|
if (!is_dir($rs['root'])) {
|
|
|
|
files::makeDir($rs['root'], true);
|
|
|
|
}
|
|
|
|
|
|
|
|
$zip->unzip($rs['from'], $rs['to']);
|
|
|
|
$imported = true;
|
|
|
|
}
|
|
|
|
$zip->close();
|
|
|
|
unlink($zip_file['tmp_name']);
|
|
|
|
|
|
|
|
if (!empty($not_overwrited)) {
|
|
|
|
throw new Exception(sprintf(
|
2021-11-01 21:32:32 +00:00
|
|
|
__('Some languages has not been overwrited %s'),
|
|
|
|
implode(', ', $not_overwrited)
|
2021-09-24 23:58:59 +00:00
|
|
|
));
|
2022-11-13 17:40:31 +00:00
|
|
|
} elseif (!$imported) {
|
2021-09-24 23:58:59 +00:00
|
|
|
throw new Exception(sprintf(
|
2021-11-01 21:32:32 +00:00
|
|
|
__('Nothing to import from %s'),
|
|
|
|
$zip_file['name']
|
2021-09-24 23:58:59 +00:00
|
|
|
));
|
|
|
|
}
|
2021-11-01 21:32:32 +00:00
|
|
|
|
2021-09-24 23:58:59 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Export (to output) language pack
|
2021-11-01 21:32:32 +00:00
|
|
|
*
|
2021-09-24 23:58:59 +00:00
|
|
|
* @param array $langs Langs to export
|
|
|
|
*/
|
|
|
|
public function exportPack(array $langs)
|
|
|
|
{
|
|
|
|
if (empty($langs)) {
|
|
|
|
throw new Exception(
|
2021-09-25 14:32:04 +00:00
|
|
|
__('Nothing to export')
|
2021-09-24 23:58:59 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
$filename = files::tidyFileName($this->translater->export_filename);
|
|
|
|
if (empty($filename)) {
|
|
|
|
throw new Exception(
|
|
|
|
__('Export mask is not set in plugin configuration')
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
$res = [];
|
2021-11-01 21:32:32 +00:00
|
|
|
foreach ($langs as $lang) {
|
2021-09-24 23:58:59 +00:00
|
|
|
if (!is_dir($this->prop['locales'] . '/' . $lang)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$files = dcTranslater::scandir($this->prop['locales'] . '/' . $lang);
|
2021-11-01 21:32:32 +00:00
|
|
|
foreach ($files as $file) {
|
|
|
|
if (is_dir($this->prop['locales'] . '/' . $lang . '/' . $file)
|
|
|
|
|| !dcTranslater::isLangphpFile($file)
|
2021-09-24 23:58:59 +00:00
|
|
|
&& !dcTranslater::isPoFile($file)
|
|
|
|
) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2021-11-01 21:32:32 +00:00
|
|
|
$res[$this->prop['locales'] . '/' . $lang . '/' . $file] = $this->prop['id'] . '/locales/' . $lang . '/' . $file;
|
2021-09-24 23:58:59 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (empty($res)) {
|
|
|
|
throw new Exception(
|
|
|
|
__('Nothing to export')
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
@set_time_limit(300);
|
2021-11-01 21:32:32 +00:00
|
|
|
$fp = fopen('php://output', 'wb');
|
2021-09-24 23:58:59 +00:00
|
|
|
$zip = new fileZip($fp);
|
2021-11-01 21:32:32 +00:00
|
|
|
foreach ($res as $from => $to) {
|
2021-09-24 23:58:59 +00:00
|
|
|
$zip->addFile($from, $to);
|
|
|
|
}
|
|
|
|
|
|
|
|
$filename = files::tidyFileName(dt::str(str_replace(
|
|
|
|
['timestamp', 'module', 'type', 'version'],
|
|
|
|
[time(), $this->prop['id'], $this->prop['type'], $this->prop['version']],
|
|
|
|
$this->translater->export_filename
|
|
|
|
)));
|
|
|
|
|
|
|
|
header('Content-Disposition: attachment;filename=' . $filename . '.zip');
|
|
|
|
header('Content-Type: application/x-zip');
|
|
|
|
$zip->write();
|
|
|
|
unset($zip);
|
|
|
|
exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Parse zip filename to module, lang info
|
2021-11-01 21:32:32 +00:00
|
|
|
*
|
2021-09-24 23:58:59 +00:00
|
|
|
* @param string $file The zip filename
|
|
|
|
* @param boolean $throw Silently failed
|
2021-09-25 15:38:46 +00:00
|
|
|
* @return mixed Array of file info
|
2021-09-24 23:58:59 +00:00
|
|
|
*/
|
2021-09-25 15:38:46 +00:00
|
|
|
public function parseZipFilename(string $file = '', bool $throw = false): array
|
2021-09-24 23:58:59 +00:00
|
|
|
{
|
|
|
|
$is_file = preg_match('/^(.*?)\/locales\/(.*?)\/(.*?)(.po|.lang.php)$/', $file, $f);
|
|
|
|
|
|
|
|
if ($is_file) {
|
2021-11-05 00:21:33 +00:00
|
|
|
$module = $f[1] == $this->prop['id'] ? $f[1] : false;
|
2021-11-01 21:32:32 +00:00
|
|
|
$lang = l10n::isCode($f[2]) ? $f[2] : false;
|
2022-11-13 17:40:31 +00:00
|
|
|
$group = in_array($f[3], dcTranslater::$allowed_l10n_groups) ? $f[3] : false;
|
|
|
|
$ext = dcTranslater::isLangphpFile($f[4]) || dcTranslater::isPoFile($f[4]) ? $f[4] : false;
|
2021-09-24 23:58:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!$is_file || !$module || !$lang || !$group || !$ext) {
|
|
|
|
if ($throw) {
|
|
|
|
throw new Exception(sprintf(
|
2021-11-01 21:32:32 +00:00
|
|
|
__('Zip file %s is not in translater format'),
|
|
|
|
$file
|
2021-09-24 23:58:59 +00:00
|
|
|
));
|
|
|
|
}
|
2021-11-01 21:32:32 +00:00
|
|
|
|
2021-09-25 15:38:46 +00:00
|
|
|
return [];
|
2021-09-24 23:58:59 +00:00
|
|
|
}
|
2021-11-01 21:32:32 +00:00
|
|
|
|
2021-09-24 23:58:59 +00:00
|
|
|
return [
|
|
|
|
'module' => $module,
|
|
|
|
'lang' => $lang,
|
|
|
|
'group' => $group,
|
2022-11-13 17:40:31 +00:00
|
|
|
'ext' => $ext,
|
2021-09-24 23:58:59 +00:00
|
|
|
];
|
|
|
|
}
|
|
|
|
//@}
|
|
|
|
|
|
|
|
/// @name lang methods
|
|
|
|
//@{
|
|
|
|
/**
|
|
|
|
* List available langs of a module
|
2021-11-01 21:32:32 +00:00
|
|
|
*
|
2021-09-24 23:58:59 +00:00
|
|
|
* @param boolean $return_path Return path or name
|
|
|
|
* @return array The lang list
|
|
|
|
*/
|
2021-09-25 15:38:46 +00:00
|
|
|
public function getLangs(bool $return_path = false): array
|
2021-09-24 23:58:59 +00:00
|
|
|
{
|
|
|
|
$res = [];
|
|
|
|
|
|
|
|
$prefix = preg_match('/(locales(.*))$/', $this->prop['locales']) ? 'locales' : '';
|
|
|
|
|
|
|
|
$files = dcTranslater::scandir($this->prop['locales']);
|
2021-11-01 21:32:32 +00:00
|
|
|
foreach ($files as $file) {
|
2021-09-24 23:58:59 +00:00
|
|
|
if (!preg_match('/.*?locales\/([^\/]*?)\/([^\/]*?)(.lang.php|.po)$/', $prefix . $file, $m)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!l10n::isCode($m[1])) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($return_path) {
|
|
|
|
$res[$m[1]][] = $file; // Path
|
|
|
|
} else {
|
|
|
|
$res[$m[1]] = l10n::getLanguageName($m[1]); // Lang name
|
|
|
|
}
|
|
|
|
}
|
2021-11-01 21:32:32 +00:00
|
|
|
|
2021-09-24 23:58:59 +00:00
|
|
|
return $res;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* List of used langs of a module
|
2021-11-01 21:32:32 +00:00
|
|
|
*
|
2021-09-24 23:58:59 +00:00
|
|
|
* @return array The list of iso names and codes
|
|
|
|
*/
|
|
|
|
public function getUsedLangs()
|
|
|
|
{
|
|
|
|
return array_flip($this->getLangs());
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* List of unsused langs of a module
|
2021-11-01 21:32:32 +00:00
|
|
|
*
|
2021-09-24 23:58:59 +00:00
|
|
|
* @return array The list of iso names and codes
|
|
|
|
*/
|
|
|
|
public function getUnusedLangs()
|
|
|
|
{
|
|
|
|
return array_diff(l10n::getISOcodes(true, false), $this->getUsedLangs());
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Add a lang to a module
|
2021-11-01 21:32:32 +00:00
|
|
|
*
|
2021-09-24 23:58:59 +00:00
|
|
|
* @param string $lang The lang id
|
|
|
|
* @param string $from_lang The lang to copy from
|
|
|
|
* @return boolean True on success
|
|
|
|
*/
|
|
|
|
public function addLang(string $lang, string $from_lang = '')
|
|
|
|
{
|
|
|
|
if (!l10n::isCode($lang)) {
|
|
|
|
throw new Exception(sprintf(
|
2021-11-01 21:32:32 +00:00
|
|
|
__('Unknow language %s'),
|
|
|
|
$lang
|
2021-09-24 23:58:59 +00:00
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
$langs = $this->getLangs();
|
|
|
|
if (isset($langs[$lang])) {
|
|
|
|
throw new Exception(sprintf(
|
2021-11-01 21:32:32 +00:00
|
|
|
__('Language %s already exists'),
|
|
|
|
$lang
|
2021-09-24 23:58:59 +00:00
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
files::makeDir($this->prop['locales'] . '/' . $lang, true);
|
|
|
|
|
|
|
|
if (!empty($from_lang) && !isset($langs[$from_lang])) {
|
|
|
|
throw new Exception(sprintf(
|
2021-11-01 21:32:32 +00:00
|
|
|
__('Failed to copy file from language %s'),
|
|
|
|
$from_lang
|
2021-09-24 23:58:59 +00:00
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!empty($from_lang) && isset($langs[$from_lang])) {
|
|
|
|
$files = dcTranslater::scandir($this->prop['locales'] . '/' . $from_lang);
|
2021-11-01 21:32:32 +00:00
|
|
|
foreach ($files as $file) {
|
|
|
|
if (is_dir($this->prop['locales'] . '/' . $from_lang . '/' . $file)
|
|
|
|
|| !dcTranslater::isLangphpFile($file)
|
2021-09-24 23:58:59 +00:00
|
|
|
&& !dcTranslater::isPoFile($file)
|
|
|
|
) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2021-11-01 21:32:32 +00:00
|
|
|
files::putContent(
|
|
|
|
$this->prop['locales'] . '/' . $lang . '/' . $file,
|
2021-09-24 23:58:59 +00:00
|
|
|
file_get_contents($this->prop['locales'] . '/' . $from_lang . '/' . $file)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
$this->setPoContent($lang, 'main', []);
|
2021-09-25 11:59:51 +00:00
|
|
|
$this->setLangphpContent($lang, 'main', []);
|
2021-09-24 23:58:59 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Update an existing lang
|
2021-11-01 21:32:32 +00:00
|
|
|
*
|
2021-09-24 23:58:59 +00:00
|
|
|
* @param string $lang The lang
|
|
|
|
* @param array $msgs The messages
|
|
|
|
*/
|
|
|
|
public function updLang(string $lang, array $msgs)
|
|
|
|
{
|
|
|
|
if (!l10n::isCode($lang)) {
|
|
|
|
throw new Exception(sprintf(
|
2021-11-01 21:32:32 +00:00
|
|
|
__('Unknow language %s'),
|
|
|
|
$lang
|
2021-09-24 23:58:59 +00:00
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
$langs = $this->getLangs();
|
|
|
|
if (!isset($langs[$lang])) {
|
|
|
|
throw new Exception(sprintf(
|
2021-11-01 21:32:32 +00:00
|
|
|
__('Failed to find language %s'),
|
|
|
|
$lang
|
2021-09-24 23:58:59 +00:00
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($this->translater->backup_auto) {
|
|
|
|
$this->createBackup($lang);
|
|
|
|
}
|
|
|
|
|
|
|
|
$rs = [];
|
2021-11-01 21:32:32 +00:00
|
|
|
foreach ($msgs as $msg) {
|
2021-09-24 23:58:59 +00:00
|
|
|
if (empty($msg['msgstr'][0])) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
$rs[$msg['group']][] = $msg;
|
|
|
|
}
|
|
|
|
|
2021-11-01 21:32:32 +00:00
|
|
|
foreach (dcTranslater::$allowed_l10n_groups as $group) {
|
2021-09-24 23:58:59 +00:00
|
|
|
if (isset($rs[$group])) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2021-11-01 21:32:32 +00:00
|
|
|
$po_file = $this->prop['locales'] . '/' . $lang . '/' . $group . '.po';
|
2021-09-24 23:58:59 +00:00
|
|
|
$langphp_file = $this->prop['locales'] . '/' . $lang . '/' . $group . '.lang.php';
|
|
|
|
|
|
|
|
if (file_exists($po_file)) {
|
|
|
|
unlink($po_file);
|
|
|
|
}
|
|
|
|
if (file_exists($langphp_file)) {
|
|
|
|
unlink($langphp_file);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (empty($rs)) {
|
|
|
|
throw new Exception(sprintf(
|
2021-11-01 21:32:32 +00:00
|
|
|
__('No string to write, language %s deleted'),
|
|
|
|
$lang
|
2021-09-24 23:58:59 +00:00
|
|
|
));
|
|
|
|
}
|
|
|
|
|
2021-11-01 21:32:32 +00:00
|
|
|
foreach ($rs as $group => $msgs) {
|
2021-09-24 23:58:59 +00:00
|
|
|
$this->setPoContent($lang, $group, $msgs);
|
2021-09-25 11:59:51 +00:00
|
|
|
$this->setLangphpContent($lang, $group, $msgs);
|
2021-09-24 23:58:59 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Delete a lang
|
2021-11-01 21:32:32 +00:00
|
|
|
*
|
2021-09-24 23:58:59 +00:00
|
|
|
* @param string $lang The lang code
|
|
|
|
* @param boolean $del_empty_dir Also remove empty locales dir
|
|
|
|
* @return boolean True on success
|
|
|
|
*/
|
|
|
|
public function delLang(string $lang, bool $del_empty_dir = true): bool
|
|
|
|
{
|
|
|
|
# Path is right formed
|
|
|
|
if (!l10n::isCode($lang)) {
|
|
|
|
throw new Exception(sprintf(
|
2021-11-01 21:32:32 +00:00
|
|
|
__('Unknow language %s'),
|
|
|
|
$lang
|
2021-09-24 23:58:59 +00:00
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
$files = $this->getLangs(true);
|
|
|
|
if (!isset($files[$lang])) {
|
|
|
|
throw new Exception(sprintf(
|
2021-11-01 21:32:32 +00:00
|
|
|
__('Failed to find language %s'),
|
|
|
|
$lang
|
2021-09-24 23:58:59 +00:00
|
|
|
));
|
|
|
|
}
|
|
|
|
|
2021-11-01 21:32:32 +00:00
|
|
|
foreach ($files[$lang] as $file) {
|
2021-09-24 23:58:59 +00:00
|
|
|
unlink($this->prop['locales'] . '/' . $file);
|
|
|
|
}
|
|
|
|
|
|
|
|
$dir = dcTranslater::scandir($this->prop['locales'] . '/' . $lang);
|
|
|
|
if (empty($dir)) {
|
|
|
|
rmdir($this->prop['locales'] . '/' . $lang);
|
|
|
|
}
|
|
|
|
|
|
|
|
$loc = dcTranslater::scandir($this->prop['locales']);
|
|
|
|
if (empty($loc)) {
|
|
|
|
rmdir($this->prop['locales']);
|
|
|
|
}
|
2021-11-01 21:32:32 +00:00
|
|
|
|
2021-09-24 23:58:59 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Construct and parse a .po file
|
2021-11-01 21:32:32 +00:00
|
|
|
*
|
2021-09-24 23:58:59 +00:00
|
|
|
* @param string $lang The lang code
|
|
|
|
* @param string $group The lang group
|
|
|
|
* @param array $msgs The strings
|
|
|
|
*/
|
|
|
|
private function setPoContent(string $lang, string $group, array $msgs)
|
|
|
|
{
|
|
|
|
$lang = new dcTranslaterLang($this, $lang);
|
|
|
|
|
2021-09-25 11:59:51 +00:00
|
|
|
$content = '';
|
2021-09-24 23:58:59 +00:00
|
|
|
if ($this->translater->parse_comment) {
|
2021-11-01 21:32:32 +00:00
|
|
|
$content .= '# Language: ' . $lang->name . "\n" .
|
|
|
|
'# Module: ' . $this->id . ' - ' . $this->version . "\n" .
|
2021-09-24 23:58:59 +00:00
|
|
|
'# Date: ' . dt::str('%Y-%m-%d %H:%M:%S') . "\n";
|
|
|
|
|
2021-09-25 16:04:38 +00:00
|
|
|
if ($this->translater->parse_user && $this->translater->parse_userinfo != '') {
|
2022-11-13 17:40:31 +00:00
|
|
|
$search = dcTranslater::$allowed_user_informations;
|
2021-11-01 21:32:32 +00:00
|
|
|
foreach ($search as $n) {
|
2022-11-13 17:40:31 +00:00
|
|
|
$replace[] = dcCore::app()->auth->getInfo('user_' . $n);
|
2021-09-24 23:58:59 +00:00
|
|
|
}
|
|
|
|
$info = trim(str_replace($search, $replace, $this->translater->parse_userinfo));
|
|
|
|
if (!empty($info)) {
|
2021-09-25 11:59:51 +00:00
|
|
|
$content .= '# Author: ' . html::escapeHTML($info) . "\n";
|
2021-09-24 23:58:59 +00:00
|
|
|
}
|
|
|
|
}
|
2022-12-26 20:48:51 +00:00
|
|
|
$content .= '# Translated with translater ' . dcCore::app()->plugins->moduleInfo(basename(dirname(__DIR__)), 'version') . "\n\n";
|
2021-09-24 23:58:59 +00:00
|
|
|
}
|
2021-11-01 21:32:32 +00:00
|
|
|
$content .= "msgid \"\"\n" .
|
2021-09-24 23:58:59 +00:00
|
|
|
"msgstr \"\"\n" .
|
|
|
|
'"Content-Type: text/plain; charset=UTF-8\n"' . "\n" .
|
2021-09-25 11:59:51 +00:00
|
|
|
'"Project-Id-Version: ' . $this->id . ' ' . $this->version . '\n"' . "\n" .
|
2021-09-24 23:58:59 +00:00
|
|
|
'"POT-Creation-Date: \n"' . "\n" .
|
|
|
|
'"PO-Revision-Date: ' . date('c') . '\n"' . "\n" .
|
2022-11-13 17:40:31 +00:00
|
|
|
'"Last-Translator: ' . dcCore::app()->auth->getInfo('user_cn') . '\n"' . "\n" .
|
2021-09-24 23:58:59 +00:00
|
|
|
'"Language-Team: \n"' . "\n" .
|
|
|
|
'"MIME-Version: 1.0\n"' . "\n" .
|
|
|
|
'"Content-Transfer-Encoding: 8bit\n"' . "\n" .
|
|
|
|
'"Plural-Forms: nplurals=2; plural=(n > 1);\n"' . "\n\n";
|
|
|
|
|
|
|
|
$comments = [];
|
|
|
|
if ($this->translater->parse_comment) {
|
|
|
|
$msgids = $lang->getMsgids();
|
2021-11-01 21:32:32 +00:00
|
|
|
foreach ($msgids as $msg) {
|
|
|
|
$comments[$msg['msgid']] = ($comments[$msg['msgid']] ?? '') .
|
|
|
|
'#: ' . trim($msg['file'], '/') . ':' . $msg['line'] . "\n";
|
2021-09-24 23:58:59 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-01 21:32:32 +00:00
|
|
|
foreach ($msgs as $msg) {
|
2021-09-24 23:58:59 +00:00
|
|
|
if (empty($msg['msgstr'][0])) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if ($this->translater->parse_comment && isset($comments[$msg['msgid']])) {
|
|
|
|
$content .= $comments[$msg['msgid']];
|
|
|
|
}
|
|
|
|
$content .= 'msgid "' . dcTranslater::poString($msg['msgid'], true) . '"' . "\n";
|
|
|
|
if (empty($msg['msgid_plural'])) {
|
|
|
|
$content .= 'msgstr "' . dcTranslater::poString($msg['msgstr'][0], true) . '"' . "\n";
|
|
|
|
} else {
|
|
|
|
$content .= 'msgid_plural "' . dcTranslater::poString($msg['msgid_plural'], true) . '"' . "\n";
|
2021-11-01 21:32:32 +00:00
|
|
|
foreach ($msg['msgstr'] as $i => $plural) {
|
2021-09-24 23:58:59 +00:00
|
|
|
$content .= 'msgstr[' . $i . '] "' . dcTranslater::poString(($msg['msgstr'][$i] ?: ''), true) . '"' . "\n";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$content .= "\n";
|
|
|
|
}
|
|
|
|
|
2021-09-25 12:37:49 +00:00
|
|
|
$file = $this->locales . '/' . $lang->code . '/' . $group . '.po';
|
|
|
|
$path = path::info($file);
|
2021-11-01 21:32:32 +00:00
|
|
|
if (is_dir($path['dirname']) && !is_writable($path['dirname'])
|
2022-11-13 17:40:31 +00:00
|
|
|
|| file_exists($file) && !is_writable($file)) {
|
2021-09-25 12:37:49 +00:00
|
|
|
throw new Exception(sprintf(
|
2021-11-01 21:32:32 +00:00
|
|
|
__('Failed to grant write acces on file %s'),
|
|
|
|
$file
|
2021-09-25 12:37:49 +00:00
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!($f = @files::putContent($file, $content))) {
|
|
|
|
throw new Exception(sprintf(
|
2021-11-01 21:32:32 +00:00
|
|
|
__('Failed to write file %s'),
|
|
|
|
$file
|
2021-09-25 12:37:49 +00:00
|
|
|
));
|
|
|
|
}
|
2021-09-24 23:58:59 +00:00
|
|
|
}
|
2021-09-25 11:59:51 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Construct and write a .lang.php file
|
2021-11-01 21:32:32 +00:00
|
|
|
*
|
2021-09-25 11:59:51 +00:00
|
|
|
* @param string $lang The lang code
|
|
|
|
* @param string $group The lang group
|
|
|
|
* @param array $msgs The strings
|
|
|
|
*/
|
|
|
|
private function setLangphpContent(string $lang, string $group, array $msgs)
|
|
|
|
{
|
|
|
|
if (!$this->translater->write_langphp) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
$lang = new dcTranslaterLang($this, $lang);
|
|
|
|
|
2021-09-25 12:37:49 +00:00
|
|
|
$content = '';
|
2021-09-25 11:59:51 +00:00
|
|
|
if ($this->translater->parse_comment) {
|
2021-11-01 21:32:32 +00:00
|
|
|
$content .= '// Language: ' . $lang->name . "\n" .
|
|
|
|
'// Module: ' . $this->id . ' - ' . $this->verison . "\n" .
|
2021-11-01 21:24:53 +00:00
|
|
|
'// Date: ' . dt::str('%Y-%m-%d %H:%M:%S') . "\n";
|
2021-09-25 11:59:51 +00:00
|
|
|
|
|
|
|
if ($this->translater->parse_user && !empty($this->translater->parse_userinfo)) {
|
|
|
|
$search = dcTranslater::$allowed_user_informations;
|
2021-11-01 21:32:32 +00:00
|
|
|
foreach ($search as $n) {
|
2022-11-13 17:40:31 +00:00
|
|
|
$replace[] = dcCore::app()->auth->getInfo('user_' . $n);
|
2021-11-01 21:32:32 +00:00
|
|
|
}
|
|
|
|
$info = trim(str_replace($search, $replace, $this->translater->parse_userinfo));
|
2021-09-25 11:59:51 +00:00
|
|
|
if (!empty($info)) {
|
|
|
|
$content .= '// Author: ' . html::escapeHTML($info) . "\n";
|
|
|
|
}
|
|
|
|
}
|
2022-12-26 20:48:51 +00:00
|
|
|
$content .= '// Translated with dcTranslater - ' . dcCore::app()->plugins->moduleInfo(basename(dirname(__DIR__)), 'version') . "\n\n";
|
2021-09-25 11:59:51 +00:00
|
|
|
}
|
|
|
|
|
2021-09-25 12:37:49 +00:00
|
|
|
l10n::generatePhpFileFromPo($this->locales . '/' . $lang->code . '/' . $group, $content);
|
2021-09-25 11:59:51 +00:00
|
|
|
}
|
2021-09-24 23:58:59 +00:00
|
|
|
//@}
|
2021-11-01 21:32:32 +00:00
|
|
|
}
|