translater/inc/class.dc.translater.php

545 lines
17 KiB
PHP
Raw Normal View History

2021-08-18 19:42:30 +00:00
<?php
2021-09-02 21:44:01 +00:00
/**
* @brief translater, a plugin for Dotclear 2
2021-11-01 21:32:32 +00:00
*
2021-09-02 21:44:01 +00:00
* @package Dotclear
* @subpackage Plugin
2021-11-01 21:32:32 +00:00
*
2021-09-02 21:44:01 +00:00
* @author Jean-Christian Denis & contributors
2021-11-01 21:32:32 +00:00
*
2021-09-02 21:44:01 +00:00
* @copyright Jean-Christian Denis
* @copyright GPL-2.0 https://www.gnu.org/licenses/gpl-2.0.html
*/
2021-08-18 22:48:47 +00:00
if (!defined('DC_CONTEXT_ADMIN')) {
return;
}
2021-08-18 19:42:30 +00:00
/**
* Translater tools.
*/
class dcTranslater
{
/** @var dCore dcCore instance */
2021-08-18 22:48:47 +00:00
public $core;
2021-09-19 13:31:57 +00:00
/** @var array $default_settings Plugins default settings */
private static $default_settings = [
2021-09-19 13:31:57 +00:00
'plugin_menu' => [
2021-11-01 21:32:32 +00:00
'id' => 'translater_plugin_menu',
2021-08-18 22:48:47 +00:00
'value' => 0,
2021-11-01 21:32:32 +00:00
'type' => 'boolean',
2021-08-18 22:48:47 +00:00
'label' => 'Put an link in plugins page'
2021-09-19 13:31:57 +00:00
],
'theme_menu' => [
2021-11-01 21:32:32 +00:00
'id' => 'translater_theme_menu',
2021-08-18 22:48:47 +00:00
'value' => 0,
2021-11-01 21:32:32 +00:00
'type' => 'boolean',
2021-08-18 22:48:47 +00:00
'label' => 'Put a link in themes page'
2021-09-19 13:31:57 +00:00
],
'backup_auto' => [
2021-11-01 21:32:32 +00:00
'id' => 'translater_backup_auto',
2021-08-18 22:48:47 +00:00
'value' => 1,
2021-11-01 21:32:32 +00:00
'type' => 'boolean',
2021-08-18 22:48:47 +00:00
'label' => 'Make a backup of languages old files when there are modified'
2021-09-19 13:31:57 +00:00
],
'backup_limit' => [
2021-11-01 21:32:32 +00:00
'id' => 'translater_backup_limit',
2021-08-18 22:48:47 +00:00
'value' => 20,
2021-11-01 21:32:32 +00:00
'type' => 'string',
2021-08-18 22:48:47 +00:00
'label' => 'Maximum backups per module'
2021-09-19 13:31:57 +00:00
],
'backup_folder' => [
2021-11-01 21:32:32 +00:00
'id' => 'translater_backup_folder',
2021-08-18 22:48:47 +00:00
'value' => 'module',
2021-11-01 21:32:32 +00:00
'type' => 'string',
2021-08-18 22:48:47 +00:00
'label' => 'In which folder to store backups'
2021-09-19 13:31:57 +00:00
],
'start_page' => [
2021-11-01 21:32:32 +00:00
'id' => 'translater_start_page',
2021-09-25 15:24:38 +00:00
'value' => '-',
2021-11-01 21:32:32 +00:00
'type' => 'string',
2021-08-18 22:48:47 +00:00
'label' => 'Page to start on'
2021-09-19 13:31:57 +00:00
],
'write_langphp' => [
2021-11-01 21:32:32 +00:00
'id' => 'translater_write_langphp',
'value' => 0,
2021-11-01 21:32:32 +00:00
'type' => 'boolean',
2021-08-18 22:48:47 +00:00
'label' => 'Write .lang.php languages files'
2021-09-19 13:31:57 +00:00
],
'scan_tpl' => [
2021-11-01 21:32:32 +00:00
'id' => 'translater_scan_tpl',
2021-09-25 16:04:00 +00:00
'value' => 1,
2021-11-01 21:32:32 +00:00
'type' => 'boolean',
2021-08-18 22:48:47 +00:00
'label' => 'Translate strings of templates files'
2021-09-19 13:31:57 +00:00
],
'parse_nodc' => [
2021-11-01 21:32:32 +00:00
'id' => 'translater_parse_nodc',
2021-08-18 22:48:47 +00:00
'value' => 1,
2021-11-01 21:32:32 +00:00
'type' => 'boolean',
2021-08-18 22:48:47 +00:00
'label' => 'Translate only untranslated strings of Dotclear',
2021-09-19 13:31:57 +00:00
],
'hide_default' => [
2021-11-01 21:32:32 +00:00
'id' => 'translater_hide_default',
2021-08-18 22:48:47 +00:00
'value' => 1,
2021-11-01 21:32:32 +00:00
'type' => 'boolean',
2021-08-18 22:48:47 +00:00
'label' => 'Hide default modules of Dotclear',
2021-09-19 13:31:57 +00:00
],
'parse_comment' => [
2021-11-01 21:32:32 +00:00
'id' => 'translater_parse_comment',
2021-09-25 16:04:00 +00:00
'value' => 0,
2021-11-01 21:32:32 +00:00
'type' => 'boolean',
2021-08-18 22:48:47 +00:00
'label' => 'Write comments and strings informations in lang files'
2021-09-19 13:31:57 +00:00
],
'parse_user' => [
2021-11-01 21:32:32 +00:00
'id' => 'translater_parse_user',
2021-09-25 16:04:00 +00:00
'value' => 0,
2021-11-01 21:32:32 +00:00
'type' => 'boolean',
2021-08-18 22:48:47 +00:00
'label' => 'Write inforamtions about author in lang files'
2021-09-19 13:31:57 +00:00
],
'parse_userinfo' => [
2021-11-01 21:32:32 +00:00
'id' => 'translater_parse_userinfo',
2021-08-18 22:48:47 +00:00
'value' => 'displayname, email',
2021-11-01 21:32:32 +00:00
'type' => 'string',
2021-08-18 22:48:47 +00:00
'label' => 'Type of informations about user to write'
2021-09-19 13:31:57 +00:00
],
'import_overwrite' => [
2021-11-01 21:32:32 +00:00
'id' => 'translater_import_overwrite',
2021-08-18 22:48:47 +00:00
'value' => 0,
2021-11-01 21:32:32 +00:00
'type' => 'boolean',
2021-08-18 22:48:47 +00:00
'label' => 'Overwrite existing languages when import packages'
2021-09-19 13:31:57 +00:00
],
'export_filename' => [
2021-11-01 21:32:32 +00:00
'id' => 'translater_export_filename',
2021-08-18 22:48:47 +00:00
'value' => 'type-module-l10n-timestamp',
2021-11-01 21:32:32 +00:00
'type' => 'string',
2021-08-18 22:48:47 +00:00
'label' => 'Name of files of exported package'
2021-09-19 13:31:57 +00:00
],
'proposal_tool' => [
2021-11-01 21:32:32 +00:00
'id' => 'translater_proposal_tool',
2021-08-18 22:48:47 +00:00
'value' => 'google',
2021-11-01 21:32:32 +00:00
'type' => 'string',
2021-08-18 22:48:47 +00:00
'label' => 'Id of default tool for proposed translation'
2021-09-19 13:31:57 +00:00
],
'proposal_lang' => [
2021-11-01 21:32:32 +00:00
'id' => 'translater_proposal_lang',
2021-08-18 22:48:47 +00:00
'value' => 'en',
2021-11-01 21:32:32 +00:00
'type' => 'string',
2021-08-18 22:48:47 +00:00
'label' => 'Default source language for proposed translation'
2021-09-19 13:31:57 +00:00
]
];
/** @var array $allowed_backup_folders List of allowed backup folder */
public static $allowed_backup_folders = [];
/** @var array $allowed_l10n_groups List of place of tranlsations */
public static $allowed_l10n_groups = [
'main', 'public', 'theme', 'admin', 'date', 'error'
];
/** @var array $allowed_user_informations List of user info can be parsed */
public static $allowed_user_informations = [
'firstname', 'displayname', 'name', 'email', 'url'
];
/** @var array $default_distrib_modules List of distributed plugins and themes */
public static $default_distrib_modules = ['plugin' => [], 'theme' => []];
2021-09-02 21:44:01 +00:00
2021-09-19 13:31:57 +00:00
/** @var array $modules List of modules we could work on */
private $modules = [];
2021-09-02 21:44:01 +00:00
2021-09-19 13:31:57 +00:00
/**
* translater instance
2021-11-01 21:32:32 +00:00
*
2021-09-25 15:24:38 +00:00
* @param dcCore $core dcCore instance
* @param boolean $core Also load modules
2021-09-19 13:31:57 +00:00
*/
2021-09-25 15:24:38 +00:00
public function __construct(dcCore $core, bool $full = true)
2021-08-18 22:48:47 +00:00
{
2021-09-19 13:31:57 +00:00
$this->core = $core;
2021-08-18 22:48:47 +00:00
$core->blog->settings->addNamespace('translater');
2021-09-25 15:24:38 +00:00
if ($full) {
$this->loadModules();
}
self::$allowed_backup_folders = [
__('locales folders of each module') => 'module',
__('plugins folder root') => 'plugin',
__('public folder root') => 'public',
__('cache folder of Dotclear') => 'cache',
__('locales folder of translater') => 'translater'
];
self::$default_distrib_modules = [
2021-11-01 21:32:32 +00:00
'plugin' => explode(',', DC_DISTRIB_PLUGINS),
'theme' => explode(',', DC_DISTRIB_THEMES)
];
2021-08-18 22:48:47 +00:00
}
2021-09-02 21:44:01 +00:00
2021-09-19 13:31:57 +00:00
/// @name settings methods
//@{
/**
* Get array of default settings
2021-11-01 21:32:32 +00:00
*
2021-09-19 13:31:57 +00:00
* @return array All default settings
*/
public function getDefaultSettings(): array
2021-08-18 22:48:47 +00:00
{
return self::$default_settings;
2021-08-18 22:48:47 +00:00
}
2021-09-02 21:44:01 +00:00
2021-09-19 13:31:57 +00:00
/**
* Get a setting according to default settings list
2021-11-01 21:32:32 +00:00
*
2021-09-19 13:31:57 +00:00
* @param string $id The settings short id
* @return mixed The setting value if exists or null
*/
public function getSetting(string $id)
2021-11-01 21:32:32 +00:00
{
return array_key_exists($id, self::$default_settings) ?
$this->core->blog->settings->translater->get(self::$default_settings[$id]['id']) : '';
2021-08-18 22:48:47 +00:00
}
2021-09-02 21:44:01 +00:00
2021-09-19 13:31:57 +00:00
/**
* Magic getSetting
*/
2021-08-18 22:48:47 +00:00
public function __get($id)
{
2021-09-19 13:31:57 +00:00
return $this->getSetting($id);
2021-08-18 22:48:47 +00:00
}
2021-09-02 21:44:01 +00:00
2021-09-19 13:31:57 +00:00
/**
* Set a setting according to default settings list
2021-11-01 21:32:32 +00:00
*
* @param string $id The setting short id
* @param mixed $value The setting value
* @param mixed $overwrite Overwrite settings if exists
* @return boolean Success
2021-09-19 13:31:57 +00:00
*/
2021-09-25 15:24:38 +00:00
public function setSetting(string $id, $value, $overwrite = true): bool
2021-08-18 22:48:47 +00:00
{
if (!array_key_exists($id, self::$default_settings)) {
2021-08-18 22:48:47 +00:00
return false;
}
2021-09-25 15:24:38 +00:00
$s = self::$default_settings[$id];
$this->core->blog->settings->translater->drop($s['id']);
$this->core->blog->settings->translater->put($s['id'], $value, $s['type'], $s['label'], $overwrite, true);
2021-11-01 21:32:32 +00:00
2021-08-18 22:48:47 +00:00
return true;
}
2021-09-02 21:44:01 +00:00
2021-09-19 13:31:57 +00:00
/**
* Magic setSetting
2021-09-19 13:31:57 +00:00
*/
public function __set($id, $value)
2021-08-18 22:48:47 +00:00
{
return $this->setSetting($id, $value);
2021-08-18 22:48:47 +00:00
}
2021-09-19 13:31:57 +00:00
//@}
/// @name modules methods
//@{
2021-11-01 21:32:32 +00:00
/**
2021-09-19 13:31:57 +00:00
* Load array of modules infos by type of modules
*/
2021-08-18 22:48:47 +00:00
private function loadModules()
{
2021-09-19 13:31:57 +00:00
$this->modules['theme'] = $this->modules['plugin'] = [];
2021-09-02 21:44:01 +00:00
$themes = new dcThemes($this->core);
$themes->loadModules($this->core->blog->themes_path, null);
2021-09-02 21:44:01 +00:00
$list = [
'theme' => $themes->getModules(),
'plugin' => $this->core->plugins->getModules()
];
2021-11-01 21:32:32 +00:00
foreach ($list as $type => $modules) {
foreach ($modules as $id => $info) {
if (!$info['root_writable']) {
// continue;
}
2021-11-01 21:32:32 +00:00
$info['id'] = $id;
$info['type'] = $type;
$this->modules[$type][$id] = new dcTranslaterModule($this, $info);
2021-08-18 22:48:47 +00:00
}
}
}
2021-08-18 19:42:30 +00:00
2021-11-01 21:32:32 +00:00
/**
2021-09-19 13:31:57 +00:00
* Return array of modules infos by type of modules
2021-11-01 21:32:32 +00:00
*
* @param string $type The modules type
* @return array The list of modules infos
2021-09-19 13:31:57 +00:00
*/
public function getModules(string $type = ''): array
2021-08-18 22:48:47 +00:00
{
2021-11-01 21:32:32 +00:00
return in_array($type, ['plugin', 'theme']) ?
$this->modules[$type] :
2021-08-18 22:48:47 +00:00
array_merge($this->modules['theme'], $this->modules['plugin']);
}
2021-09-02 21:44:01 +00:00
2021-11-01 21:32:32 +00:00
/**
* Return module class of a particular module for a given type of module
2021-11-01 21:32:32 +00:00
*
* @param string $type The module type
* @param string $id The module id
* @return dcTranslaterModule The dcTranslaterModule instance
2021-09-19 13:31:57 +00:00
*/
public function getModule(string $type, string $id)
2021-08-18 22:48:47 +00:00
{
if (!isset($this->modules[$type][$id])) {
2021-09-19 13:31:57 +00:00
throw new Exception(
sprintf(__('Failed to find module %s'), $id)
2021-09-19 13:31:57 +00:00
);
2021-11-01 21:32:32 +00:00
2021-08-18 22:48:47 +00:00
return false;
}
2021-11-01 21:32:32 +00:00
return $this->modules[$type][$id];
}
2021-09-02 21:44:01 +00:00
2021-11-01 21:32:32 +00:00
/**
* Return module class of a particular module for a given type of module
2021-11-01 21:32:32 +00:00
*
* @param string $module dcTranslaterModule instance
* @param string $lang The lang iso code
* @return dcTranslaterLang dcTranslaterLang instance or false
*/
public function getLang(dcTranslaterModule $module, string $lang)
{
if (!l10n::isCode($lang)) {
throw new Exception(
sprintf(__('Failed find language %s'), $lang)
);
2021-11-01 21:32:32 +00:00
return false;
2021-08-18 22:48:47 +00:00
}
2021-11-01 21:32:32 +00:00
return new dcTranslaterLang($module, $lang);
2021-08-18 22:48:47 +00:00
}
2021-09-19 13:31:57 +00:00
//@}
/// @name helper methods
//@{
2021-11-01 21:32:32 +00:00
/**
* Scan recursively a folder and return files and folders names
2021-11-01 21:32:32 +00:00
*
* @param string $path The path to scan
* @param string $dir Internal recursion
* @param array $res Internal recursion
* @return array List of path
2021-09-19 13:31:57 +00:00
*/
2021-09-25 15:38:46 +00:00
public static function scandir(string $path, string $dir = '', array $res = []): array
2021-08-18 22:48:47 +00:00
{
$path = path::real($path, false);
if (!is_dir($path) || !is_readable($path)) {
return [];
}
2021-08-18 19:42:30 +00:00
$files = files::scandir($path);
2021-11-01 21:32:32 +00:00
foreach ($files as $file) {
if (in_array($file, ['.', '..'])) {
continue;
}
2021-08-18 19:42:30 +00:00
if (is_dir($path . '/' . $file)) {
$res[] = $file;
2021-11-01 21:32:32 +00:00
$res = self::scanDir($path . '/' . $file, $dir . '/' . $file, $res);
} else {
$res[] = empty($dir) ? $file : $dir . '/' . $file;
2021-08-18 22:48:47 +00:00
}
}
2021-11-01 21:32:32 +00:00
return $res;
2021-08-18 22:48:47 +00:00
}
2021-09-02 21:44:01 +00:00
2021-09-25 15:38:46 +00:00
/**
* Encode a string
2021-11-01 21:32:32 +00:00
*
2021-09-25 15:38:46 +00:00
* @param string $str The string to encode
* @return string The encoded string
*/
public static function encodeMsg(string $str): string
2021-08-18 22:48:47 +00:00
{
2021-09-25 15:38:46 +00:00
return text::toUTF8(stripslashes(trim($str)));
}
/**
* Clean a po string
2021-11-01 21:32:32 +00:00
*
2021-09-25 15:38:46 +00:00
* @param string $string The string to clean
* @param boolean $reverse Un/escape string
* @return string The cleaned string
*/
public static function poString(string $string, bool $reverse = false): string
{
if ($reverse) {
2021-11-01 21:32:32 +00:00
$smap = ['"', "\n", "\t", "\r"];
$rmap = ['\\"', '\\n"' . "\n" . '"', '\\t', '\\r'];
2021-09-25 15:38:46 +00:00
return trim((string) str_replace($smap, $rmap, $string));
}
2021-11-01 21:32:32 +00:00
$smap = ['/"\s+"/', '/\\\\n/', '/\\\\r/', '/\\\\t/', '/\\\"/'];
$rmap = ['', "\n", "\r", "\t", '"'];
return trim((string) preg_replace($smap, $rmap, $string));
2021-08-18 22:48:47 +00:00
}
2021-08-18 19:42:30 +00:00
2021-09-25 15:38:46 +00:00
/**
* Try if a file is a .po file
2021-11-01 21:32:32 +00:00
*
2021-09-25 15:38:46 +00:00
* @param string $file The path to test
* @return boolean Success
*/
public static function isPoFile(string $file): bool
2021-08-18 22:48:47 +00:00
{
return files::getExtension($file) == 'po';
2021-08-18 22:48:47 +00:00
}
2021-08-18 19:42:30 +00:00
2021-09-25 15:38:46 +00:00
/**
* Try if a file is a .lang.php file
2021-11-01 21:32:32 +00:00
*
2021-09-25 15:38:46 +00:00
* @param string $file The path to test
* @return boolean Success
*/
public static function isLangphpFile(string $file): bool
{
return files::getExtension($file) == 'php' && stristr($file, '.lang.php');
}
2021-09-19 13:31:57 +00:00
/**
* Check limit number of backup for a module
2021-11-01 21:32:32 +00:00
*
* @param string $root The backups root
* @param string $limit The backups limit
2021-09-19 13:31:57 +00:00
* @param boolean $throw Silently failed
* @return boolean True if limit is riched
*/
public static function isBackupLimit(string $root, int $limit = 10, bool $throw = false): bool
2021-08-18 22:48:47 +00:00
{
$count = 0;
2021-11-01 21:32:32 +00:00
foreach (self::scandir($root) as $file) {
if (!is_dir($root . '/' . $file)
2021-11-01 21:32:32 +00:00
&& preg_match('/^(l10n-' . $id . '(.*?).bck.zip)$/', $root)
2021-09-19 13:31:57 +00:00
) {
2021-08-18 22:48:47 +00:00
$count++;
}
}
2021-09-02 21:44:01 +00:00
2021-08-18 22:48:47 +00:00
# Limite exceed
if ($count >= $limit) {
2021-08-18 22:48:47 +00:00
if ($throw) {
2021-09-19 13:31:57 +00:00
throw new Exception(
sprintf(__('Limit of %s backups for module %s exceed'), $this->backup_limit, $id)
);
2021-08-18 22:48:47 +00:00
}
2021-11-01 21:32:32 +00:00
2021-08-18 22:48:47 +00:00
return true;
}
2021-11-01 21:32:32 +00:00
2021-09-19 13:31:57 +00:00
return false;
2021-08-18 22:48:47 +00:00
}
2021-08-18 19:42:30 +00:00
2021-09-19 13:31:57 +00:00
/**
* Extract messages from a php contents
*
* support plurals
2021-11-01 21:32:32 +00:00
*
* @param string $content The contents
* @param string $func The function name
2021-09-25 13:15:16 +00:00
* @return array The messages
2021-09-19 13:31:57 +00:00
*/
public static function extractPhpMsgs(string $content, string $func = '__'): array
{
$duplicate = $final_strings = $lines = [];
// split content by line to combine match/line on the end
$content = str_replace("\r\n", "\n", $content);
2021-11-01 21:32:32 +00:00
$o = 0;
$parts = explode("\n", $content);
foreach ($parts as $li => $part) {
$m = explode($func . '(', $part);
2021-11-01 21:32:32 +00:00
for ($i = 1; $i < count($m); $i++) {
$lines[$o] = $li + 1;
$o++;
}
}
// split content by translation function
$parts = explode($func . '(', $content);
// remove fisrt element from array
array_shift($parts);
// walk through parts
$p = 0;
2021-11-01 21:32:32 +00:00
foreach ($parts as $part) {
// should start with quote
2021-11-01 21:32:32 +00:00
if (!in_array(substr($part, 0, 1), ['"', "'"])) {
$p++;
2021-11-01 21:32:32 +00:00
continue;
}
// put back first parenthesis
2021-11-01 21:32:32 +00:00
$part = '(' . $part;
// find pairs of parenthesis
preg_match_all("/\((?:[^\)\(]+|(?R))*+\)/s", $part, $subparts);
// find quoted strings (single or double)
preg_match_all("/\'(?:[^\']+)\'|\"(?:[^\"]+)\"/s", $subparts[0][0], $strings);
// strings exist
if (!empty($strings[0])) {
// remove quotes
2021-11-01 21:32:32 +00:00
$strings[0] = array_map(function ($v) { return substr($v, 1, -1);}, $strings[0]);
// filter duplicate strings (only check first string for plurals form)
if (!in_array($strings[0][0], $duplicate)) {
// fill final array
$final_strings[] = [$strings[0], $lines[$p]];
2021-11-01 21:32:32 +00:00
$duplicate[] = $strings[0][0];
2021-08-18 22:48:47 +00:00
}
}
$p++;
2021-08-18 22:48:47 +00:00
}
2021-11-01 21:32:32 +00:00
return $final_strings;
2021-08-18 22:48:47 +00:00
}
2021-09-25 13:15:16 +00:00
/**
* Extract messages from a tpl contents
*
* @param string $content The contents
* @param string $func The function name
* @return array The messages
*/
public static function extractTplMsgs(string $content, string $func = 'tpl:lang'): array
{
$duplicate = $final_strings = $lines = [];
// split content by line to combine match/line on the end
$content = str_replace("\r\n", "\n", $content);
2021-11-01 21:32:32 +00:00
$o = 0;
$parts = explode("\n", $content);
foreach ($parts as $li => $part) {
2021-09-25 13:15:16 +00:00
$m = explode('{{' . $func . ' ', $part);
2021-11-01 21:32:32 +00:00
for ($i = 1; $i < count($m); $i++) {
$lines[$o] = $li + 1;
2021-09-25 13:15:16 +00:00
$o++;
}
}
// split content by translation function
if (!preg_match_all('/\{\{' . preg_quote($func) . '\s([^}]+)\}\}/', $content, $parts)) {
return $final_strings;
}
// walk through parts
$p = 0;
2021-11-01 21:32:32 +00:00
foreach ($parts[1] as $part) {
2021-09-25 13:15:16 +00:00
// strings exist
if (!empty($part)) {
// filter duplicate strings
if (!in_array($part, $duplicate)) {
// fill final array
$final_strings[] = [[$part], $lines[$p]];
2021-11-01 21:32:32 +00:00
$duplicate[] = $part;
2021-09-25 13:15:16 +00:00
}
}
$p++;
}
2021-11-01 21:32:32 +00:00
2021-09-25 13:15:16 +00:00
return $final_strings;
}
//@}
2021-11-01 21:32:32 +00:00
}