Compare commits
3 commits
cf6ea5480b
...
c8659ea46f
Author | SHA1 | Date | |
---|---|---|---|
c8659ea46f | |||
5e8a2b6d1c | |||
15c2acee08 |
25 changed files with 898 additions and 765 deletions
|
@ -1,8 +1,7 @@
|
||||||
#!/usr/bin/php
|
#!/usr/bin/php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
use EesyPHP\Cli;
|
||||||
|
|
||||||
require realpath(dirname(__FILE__).'/..')."/includes/core.php";
|
require realpath(dirname(__FILE__).'/..')."/includes/core.php";
|
||||||
if (is_callable('handle_cli_args'))
|
Cli :: handle_args();
|
||||||
handle_cli_args();
|
|
||||||
else
|
|
||||||
Log :: fatal("An error occured initializing CLI : handle_cli_args() function not found.");
|
|
||||||
|
|
198
includes/cli.php
198
includes/cli.php
|
@ -1,31 +1,10 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
use EesyPHP\Check;
|
use EesyPHP\Check;
|
||||||
|
use EesyPHP\Cli;
|
||||||
|
use EesyPHP\I18n;
|
||||||
use EesyPHP\Log;
|
use EesyPHP\Log;
|
||||||
|
|
||||||
$cli_commands=array();
|
|
||||||
function add_cli_command($command, $handler, $short_desc, $usage_args=false, $long_desc=false,
|
|
||||||
$override=false) {
|
|
||||||
global $cli_commands;
|
|
||||||
if (array_key_exists($command, $cli_commands) && !$override) {
|
|
||||||
Log :: error(_("The CLI command '%s' already exists."), $command);
|
|
||||||
return False;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!is_callable($handler)) {
|
|
||||||
Log :: error(_("The CLI command '%s' handler is not callable !"), $command);
|
|
||||||
return False;
|
|
||||||
}
|
|
||||||
|
|
||||||
$cli_commands[$command] = array (
|
|
||||||
'handler' => $handler,
|
|
||||||
'short_desc' => $short_desc,
|
|
||||||
'usage_args' => $usage_args,
|
|
||||||
'long_desc' => $long_desc,
|
|
||||||
);
|
|
||||||
return True;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
*************************************************************************************************
|
*************************************************************************************************
|
||||||
* /!\ Code after this message will only be execute on CLI context /!\
|
* /!\ Code after this message will only be execute on CLI context /!\
|
||||||
|
@ -34,126 +13,11 @@ function add_cli_command($command, $handler, $short_desc, $usage_args=false, $lo
|
||||||
if (php_sapi_name() != "cli")
|
if (php_sapi_name() != "cli")
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
// Store current CLI command
|
|
||||||
$cli_command = null;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CLI Helpers
|
* CLI Helpers
|
||||||
**/
|
**/
|
||||||
|
|
||||||
function usage($error=false) {
|
|
||||||
global $cli_commands, $cli_command, $argv;
|
|
||||||
|
|
||||||
// If more than 1 arguments passed, format error message using sprintf
|
|
||||||
if (func_num_args() > 1) {
|
|
||||||
$error = call_user_func_array(
|
|
||||||
'sprintf',
|
|
||||||
array_merge(array($error), array_slice(func_get_args(), 1))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($error)
|
|
||||||
echo "$error\n\n";
|
|
||||||
printf(_("Usage: %s [-h] [-qd] command\n"), basename($argv[0]));
|
|
||||||
echo _(" -h Show this message\n");
|
|
||||||
echo _(" -q / -d Quiet/Debug mode\n");
|
|
||||||
echo _(" --trace Trace mode (the most verbose)\n");
|
|
||||||
echo _(" command Command to run\n");
|
|
||||||
echo "\n";
|
|
||||||
echo _("Available commands:\n");
|
|
||||||
|
|
||||||
foreach ($cli_commands as $command => $info) {
|
|
||||||
if ($cli_command && $command != $cli_command)
|
|
||||||
continue;
|
|
||||||
echo (
|
|
||||||
" ".str_replace(
|
|
||||||
"\n", "\n ",
|
|
||||||
wordwrap("$command : "._($info['short_desc'])))
|
|
||||||
."\n\n");
|
|
||||||
echo (
|
|
||||||
" ".basename($argv[0])." $command ".
|
|
||||||
($info['usage_args']?_($info['usage_args']):'').
|
|
||||||
"\n");
|
|
||||||
if ($info['long_desc']) {
|
|
||||||
if (is_array($info['long_desc'])) {
|
|
||||||
$lines = array();
|
|
||||||
foreach ($info['long_desc'] as $line)
|
|
||||||
$lines[] = _($line);
|
|
||||||
$info['long_desc'] = implode("\n", $lines);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
$info['long_desc'] = _($info['long_desc']);
|
|
||||||
|
|
||||||
echo "\n ".str_replace("\n", "\n ", wordwrap($info['long_desc']))."\n";
|
|
||||||
}
|
|
||||||
echo "\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
exit(($error?1:0));
|
|
||||||
}
|
|
||||||
|
|
||||||
function handle_cli_args() {
|
|
||||||
global $log_level, $cli_commands, $cli_command, $argv;
|
|
||||||
$log_level = 'INFO';
|
|
||||||
$cli_command = false;
|
|
||||||
$command_args = array();
|
|
||||||
for ($i=1; $i < count($argv); $i++) {
|
|
||||||
if (array_key_exists($argv[$i], $cli_commands)) {
|
|
||||||
if (!$cli_command)
|
|
||||||
$cli_command = $argv[$i];
|
|
||||||
else
|
|
||||||
usage(_("Only one command could be executed !"));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
switch($argv[$i]) {
|
|
||||||
case '-h':
|
|
||||||
case '--help':
|
|
||||||
usage();
|
|
||||||
break;
|
|
||||||
case '-d':
|
|
||||||
case '--debug':
|
|
||||||
$log_level = 'DEBUG';
|
|
||||||
break;
|
|
||||||
case '-q':
|
|
||||||
case '--quiet':
|
|
||||||
$log_level = 'WARNING';
|
|
||||||
break;
|
|
||||||
case '--trace':
|
|
||||||
$log_level = 'TRACE';
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
if ($cli_command)
|
|
||||||
$command_args[] = $argv[$i];
|
|
||||||
else
|
|
||||||
usage(
|
|
||||||
_(
|
|
||||||
"Invalid parameter \"%s\".\nNote: Command's parameter/argument must be place ".
|
|
||||||
"after the command."
|
|
||||||
), $argv[$i]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$cli_command)
|
|
||||||
usage();
|
|
||||||
|
|
||||||
Log :: debug(
|
|
||||||
"Run %s command %s with argument(s) '%s'.",
|
|
||||||
basename($argv[0]), $cli_command, implode("', '", $command_args)
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
|
||||||
$result = call_user_func($cli_commands[$cli_command]['handler'], $command_args);
|
|
||||||
|
|
||||||
exit($result?0:1);
|
|
||||||
}
|
|
||||||
catch(Exception $e) {
|
|
||||||
Log :: exception($e, _("An exception occured running command %s"), $cli_command);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function print_item_info($item) {
|
function print_item_info($item) {
|
||||||
printf(_("Item #%s:\n"), $item['id']);
|
printf(_("Item #%s:\n"), $item['id']);
|
||||||
printf("\t"._("ID: %s")."\n", $item['id']);
|
printf("\t"._("ID: %s")."\n", $item['id']);
|
||||||
|
@ -187,7 +51,7 @@ function cli_list($command_args) {
|
||||||
case '--orderby':
|
case '--orderby':
|
||||||
$i++;
|
$i++;
|
||||||
if(!in_array($command_args[$i], $orderbys))
|
if(!in_array($command_args[$i], $orderbys))
|
||||||
usage('Invalid --orderby clause');
|
Cli :: usage('Invalid --orderby clause');
|
||||||
$params['order'] = $command_args[$i];
|
$params['order'] = $command_args[$i];
|
||||||
break;
|
break;
|
||||||
case '-r':
|
case '-r':
|
||||||
|
@ -198,7 +62,7 @@ function cli_list($command_args) {
|
||||||
case '--status':
|
case '--status':
|
||||||
$i++;
|
$i++;
|
||||||
if(!check_status($command_args[$i]))
|
if(!check_status($command_args[$i]))
|
||||||
usage('Invalid -s/--status clause');
|
Cli :: usage('Invalid -s/--status clause');
|
||||||
$params['status'] = $command_args[$i];
|
$params['status'] = $command_args[$i];
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -245,23 +109,23 @@ function cli_list($command_args) {
|
||||||
echo "\n".sprintf(_("%d item(s)"), $items['count'])."\n";
|
echo "\n".sprintf(_("%d item(s)"), $items['count'])."\n";
|
||||||
return True;
|
return True;
|
||||||
}
|
}
|
||||||
add_cli_command(
|
Cli :: add_command(
|
||||||
'list',
|
'list',
|
||||||
'cli_list',
|
'cli_list',
|
||||||
___("List/search items"),
|
I18n :: ___("List/search items"),
|
||||||
___("[patterns]"),
|
I18n :: ___("[patterns]"),
|
||||||
array(
|
array(
|
||||||
___("-o|--orderby Ordering list criterion. Possible values:"),
|
I18n :: ___("-o|--orderby Ordering list criterion. Possible values:"),
|
||||||
" - ".implode("\n - ", $orderbys),
|
" - ".implode("\n - ", $orderbys),
|
||||||
___("-r|--reverse Reverse order"),
|
I18n :: ___("-r|--reverse Reverse order"),
|
||||||
___("-s|--status Filter on status. Possible values:"),
|
I18n :: ___("-s|--status Filter on status. Possible values:"),
|
||||||
" - ".implode("\n - ", array_keys($status_list)),
|
" - ".implode("\n - ", array_keys($status_list)),
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
function cli_show($command_args) {
|
function cli_show($command_args) {
|
||||||
if (count($command_args) != 1 || !Check :: id($command_args[0]))
|
if (count($command_args) != 1 || !Check :: id($command_args[0]))
|
||||||
usage(_('You must provide a valid ID.'));
|
Cli :: usage(_('You must provide a valid ID.'));
|
||||||
|
|
||||||
$item_id = $command_args[0];
|
$item_id = $command_args[0];
|
||||||
$item = get_item($item_id);
|
$item = get_item($item_id);
|
||||||
|
@ -272,16 +136,16 @@ function cli_show($command_args) {
|
||||||
print_item_info($item);
|
print_item_info($item);
|
||||||
return True;
|
return True;
|
||||||
}
|
}
|
||||||
add_cli_command(
|
Cli :: add_command(
|
||||||
'show',
|
'show',
|
||||||
'cli_show',
|
'cli_show',
|
||||||
___("Show item"),
|
I18n :: ___("Show item"),
|
||||||
___("[ID]")
|
I18n :: ___("[ID]")
|
||||||
);
|
);
|
||||||
|
|
||||||
function cli_delete($command_args) {
|
function cli_delete($command_args) {
|
||||||
if (count($command_args) != 1)
|
if (count($command_args) != 1)
|
||||||
usage(_('You must provide item ID.'));
|
Cli :: usage(_('You must provide item ID.'));
|
||||||
|
|
||||||
// Check URI
|
// Check URI
|
||||||
if (!Check :: id($command_args[0]))
|
if (!Check :: id($command_args[0]))
|
||||||
|
@ -310,11 +174,11 @@ function cli_delete($command_args) {
|
||||||
|
|
||||||
return True;
|
return True;
|
||||||
}
|
}
|
||||||
add_cli_command(
|
Cli :: add_command(
|
||||||
'delete',
|
'delete',
|
||||||
'cli_delete',
|
'cli_delete',
|
||||||
___("Delete item"),
|
I18n :: ___("Delete item"),
|
||||||
___("[item ID]")
|
I18n :: ___("[item ID]")
|
||||||
);
|
);
|
||||||
|
|
||||||
function cli_export($command_args) {
|
function cli_export($command_args) {
|
||||||
|
@ -323,11 +187,11 @@ function cli_export($command_args) {
|
||||||
fclose($fd);
|
fclose($fd);
|
||||||
Log :: info("Items export to '".(count($command_args) >= 1?$command_args[0]:'STDOUT')."'.");
|
Log :: info("Items export to '".(count($command_args) >= 1?$command_args[0]:'STDOUT')."'.");
|
||||||
}
|
}
|
||||||
add_cli_command(
|
Cli :: add_command(
|
||||||
'export',
|
'export',
|
||||||
'cli_export',
|
'cli_export',
|
||||||
___("Export items (as CSV)"),
|
I18n :: ___("Export items (as CSV)"),
|
||||||
___("[output file path]")
|
I18n :: ___("[output file path]")
|
||||||
);
|
);
|
||||||
|
|
||||||
function cli_restore($command_args) {
|
function cli_restore($command_args) {
|
||||||
|
@ -339,11 +203,11 @@ function cli_restore($command_args) {
|
||||||
(count($command_args) >= 1?$command_args[0]:'STDIN')
|
(count($command_args) >= 1?$command_args[0]:'STDIN')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
add_cli_command(
|
Cli :: add_command(
|
||||||
'restore',
|
'restore',
|
||||||
'cli_restore',
|
'cli_restore',
|
||||||
___("Restore items (from CSV)"),
|
I18n :: ___("Restore items (from CSV)"),
|
||||||
___("[input file path]")
|
I18n :: ___("[input file path]")
|
||||||
);
|
);
|
||||||
|
|
||||||
function cli_cron($command_args) {
|
function cli_cron($command_args) {
|
||||||
|
@ -358,7 +222,7 @@ function cli_cron($command_args) {
|
||||||
case '--max-age':
|
case '--max-age':
|
||||||
$i++;
|
$i++;
|
||||||
if(!Check :: id($command_args[$i]))
|
if(!Check :: id($command_args[$i]))
|
||||||
usage('Invalid -m|--max-age clause');
|
Cli :: usage('Invalid -m|--max-age clause');
|
||||||
$item_max_age = $command_args[$i];
|
$item_max_age = $command_args[$i];
|
||||||
break;
|
break;
|
||||||
case '-j':
|
case '-j':
|
||||||
|
@ -366,7 +230,7 @@ function cli_cron($command_args) {
|
||||||
$just_try = true;
|
$just_try = true;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
usage('Invalid parameter '.$command_args[$i]);
|
Cli :: usage('Invalid parameter '.$command_args[$i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -407,14 +271,14 @@ function cli_cron($command_args) {
|
||||||
}
|
}
|
||||||
exit($error?1:0);
|
exit($error?1:0);
|
||||||
}
|
}
|
||||||
add_cli_command(
|
Cli :: add_command(
|
||||||
'cron',
|
'cron',
|
||||||
'cli_cron',
|
'cli_cron',
|
||||||
___("Cron to handle item expiration"),
|
I18n :: ___("Cron to handle item expiration"),
|
||||||
false,
|
null,
|
||||||
array (
|
array (
|
||||||
___("-j/--just-try Just-try mode : do not really removed expired item(s)"),
|
I18n :: ___("-j/--just-try Just-try mode : do not really removed expired item(s)"),
|
||||||
___("-m/--max-age Item expiration limit (in days, optional)"),
|
I18n :: ___("-m/--max-age Item expiration limit (in days, optional)"),
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -71,7 +71,7 @@ $sentry_traces_sample_rate = 0.2;
|
||||||
$smarty_templates_dir = "$root_dir_path/templates";
|
$smarty_templates_dir = "$root_dir_path/templates";
|
||||||
$smarty_templates_c_dir = "$tmp_root_dir/templates_c";
|
$smarty_templates_c_dir = "$tmp_root_dir/templates_c";
|
||||||
|
|
||||||
// Default locale (see lang directory for available languages list)
|
// Default locale (see locales directory for available languages list)
|
||||||
$default_locale = 'en_US.UTF8';
|
$default_locale = 'en_US.UTF8';
|
||||||
|
|
||||||
// Session
|
// Session
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
use EesyPHP\Email;
|
use EesyPHP\Email;
|
||||||
|
use EesyPHP\I18n;
|
||||||
use EesyPHP\Log;
|
use EesyPHP\Log;
|
||||||
use EesyPHP\SentryIntegration;
|
use EesyPHP\SentryIntegration;
|
||||||
use EesyPHP\SentrySpan;
|
use EesyPHP\SentrySpan;
|
||||||
use EesyPHP\SentryTransaction;
|
use EesyPHP\SentryTransaction;
|
||||||
|
use EesyPHP\Session;
|
||||||
use EesyPHP\Url;
|
use EesyPHP\Url;
|
||||||
|
|
||||||
error_reporting(E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED);
|
error_reporting(E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED);
|
||||||
|
@ -30,7 +32,6 @@ set_include_path($root_dir_path.'/includes' . PATH_SEPARATOR . get_include_path(
|
||||||
require("$root_dir_path/vendor/autoload.php");
|
require("$root_dir_path/vendor/autoload.php");
|
||||||
|
|
||||||
// Load configuration
|
// Load configuration
|
||||||
require_once('translation.php');
|
|
||||||
require_once('config.inc.php');
|
require_once('config.inc.php');
|
||||||
|
|
||||||
// Load local configuration file is present
|
// Load local configuration file is present
|
||||||
|
@ -59,18 +60,20 @@ Log::init(
|
||||||
isset($log_php_errors_levels)?$log_php_errors_levels:null
|
isset($log_php_errors_levels)?$log_php_errors_levels:null
|
||||||
);
|
);
|
||||||
require_once('functions.php');
|
require_once('functions.php');
|
||||||
require_once('session.php');
|
Session :: init(
|
||||||
|
isset($session_max_duration)?$session_max_duration:null,
|
||||||
|
isset($session_timeout)?$session_timeout:null
|
||||||
|
);
|
||||||
|
|
||||||
// Nomenclatures
|
// Nomenclatures
|
||||||
$status_list = array (
|
$status_list = array (
|
||||||
'pending' => ___('Pending'),
|
'pending' => I18n :: ___('Pending'),
|
||||||
'validated' => ___('Validated'),
|
'validated' => I18n :: ___('Validated'),
|
||||||
'refused' => ___('Refused'),
|
'refused' => I18n :: ___('Refused'),
|
||||||
'archived' => ___('Archived'),
|
'archived' => I18n :: ___('Archived'),
|
||||||
);
|
);
|
||||||
|
|
||||||
require_once('cli.php');
|
require_once('cli.php');
|
||||||
require_once('translation-cli.php');
|
|
||||||
require_once('smarty.php');
|
require_once('smarty.php');
|
||||||
Url::init(isset($public_root_url)?$public_root_url:null);
|
Url::init(isset($public_root_url)?$public_root_url:null);
|
||||||
require_once('url-helpers.php');
|
require_once('url-helpers.php');
|
||||||
|
@ -86,7 +89,10 @@ Email :: init(
|
||||||
);
|
);
|
||||||
|
|
||||||
// Initialize translation
|
// Initialize translation
|
||||||
init_translation();
|
I18n::init(
|
||||||
|
"$root_dir_path/locales",
|
||||||
|
isset($default_locale)?$default_locale:null);
|
||||||
|
|
||||||
foreach($status_list as $key => $value)
|
foreach($status_list as $key => $value)
|
||||||
$status_list[$key] = _($value);
|
$status_list[$key] = _($value);
|
||||||
$sentry_span->finish();
|
$sentry_span->finish();
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
use EesyPHP\Check;
|
use EesyPHP\Check;
|
||||||
use EesyPHP\Log;
|
use EesyPHP\Log;
|
||||||
|
use EesyPHP\Session;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Check values helpers
|
* Check values helpers
|
||||||
|
@ -313,7 +314,7 @@ function check_ajax_request($session_key=null) {
|
||||||
global $ajax, $debug_ajax;
|
global $ajax, $debug_ajax;
|
||||||
$ajax = true;
|
$ajax = true;
|
||||||
|
|
||||||
if (check_session_key($session_key))
|
if (Session :: check_key($session_key))
|
||||||
fatal_error('Invalid request');
|
fatal_error('Invalid request');
|
||||||
|
|
||||||
if ($debug_ajax)
|
if ($debug_ajax)
|
||||||
|
|
|
@ -1,46 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
use EesyPHP\Log;
|
|
||||||
|
|
||||||
if (php_sapi_name() == "cli")
|
|
||||||
return true;
|
|
||||||
|
|
||||||
// Define session max duration
|
|
||||||
if (!isset($session_max_duration))
|
|
||||||
$session_max_duration = (12*60*60); // Default to 12h
|
|
||||||
ini_set('session.gc_maxlifetime', $session_max_duration);
|
|
||||||
ini_set('session.cookie_lifetime', $session_max_duration);
|
|
||||||
|
|
||||||
// Start session
|
|
||||||
session_start();
|
|
||||||
|
|
||||||
// Init session key
|
|
||||||
if (!isset($_SESSION['session_key'])) {
|
|
||||||
$_SESSION['session_key']=uniqid();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle session timeout
|
|
||||||
if (isset($session_timeout) && $session_timeout) {
|
|
||||||
if (!isset($_SESSION['session_last_access'])) {
|
|
||||||
Log :: debug('Set initial session last access');
|
|
||||||
$_SESSION['session_last_access'] = time();
|
|
||||||
}
|
|
||||||
elseif ($_SESSION['session_last_access'] > (time() - $session_timeout)) {
|
|
||||||
Log :: debug(
|
|
||||||
'Session timeout not expired, update session last access '.
|
|
||||||
'(Previous value : '.$_SESSION['session_last_access'].')');
|
|
||||||
$_SESSION['session_last_access'] = time();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Log :: info('Session destroyed due to inactivity');
|
|
||||||
session_destroy();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function check_session_key($value=null) {
|
|
||||||
if (is_null($value) && isset($_REQUEST['session_key']))
|
|
||||||
$value = $_REQUEST['session_key'];
|
|
||||||
return ($value && $_SESSION['session_key'] == $value);
|
|
||||||
}
|
|
||||||
|
|
||||||
# vim: tabstop=2 shiftwidth=2 softtabstop=2 expandtab
|
|
|
@ -1,373 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
use EesyPHP\Log;
|
|
||||||
|
|
||||||
/*
|
|
||||||
********************************************************************
|
|
||||||
* Translations CLI commands *
|
|
||||||
********************************************************************
|
|
||||||
*/
|
|
||||||
|
|
||||||
if (php_sapi_name() != "cli")
|
|
||||||
return true;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert PO file to JSON file
|
|
||||||
*
|
|
||||||
* @param string $locale The locale of the input PO file
|
|
||||||
* @param string $path The path of the input PO file
|
|
||||||
*
|
|
||||||
* @return string JSON encoded file content
|
|
||||||
*/
|
|
||||||
function po2json($locale, $path) {
|
|
||||||
$fileHandler = new \Sepia\PoParser\SourceHandler\FileSystem($path);
|
|
||||||
$poparser = new \Sepia\PoParser\Parser($fileHandler);
|
|
||||||
$catalog = $poparser->parse();
|
|
||||||
$headers = $catalog->getHeader();
|
|
||||||
|
|
||||||
$messages = array();
|
|
||||||
foreach ($catalog->getEntries() as $entry) {
|
|
||||||
// msg id json format
|
|
||||||
$msg = $entry->getMsgStr();
|
|
||||||
if ($entry->isPlural())
|
|
||||||
$msg = array($msg, $entry->getMsgIdPlural());
|
|
||||||
$messages[$entry->getMsgId()] = $msg;
|
|
||||||
}
|
|
||||||
return json_encode(array(
|
|
||||||
'messages' => $messages,
|
|
||||||
'locale' => $locale,
|
|
||||||
'domain' => TEXT_DOMAIN,
|
|
||||||
'plural_expr' => '(n > 1)',
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Command to extract messages from PHP/JS & template files and
|
|
||||||
* generate the lang/messages.pot file.
|
|
||||||
*
|
|
||||||
* @param array $command_args The command arguments
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
function cli_extract_messages($command_args) {
|
|
||||||
global $root_dir_path, $root_lang_dir, $smarty_templates_dir;
|
|
||||||
|
|
||||||
// List PHP files to parse
|
|
||||||
$php_files = run_external_command(
|
|
||||||
array('find', escapeshellarg($root_dir_path), '-name', "'*.php'"),
|
|
||||||
null, // no STDIN data
|
|
||||||
false // do not escape command args (already done)
|
|
||||||
);
|
|
||||||
if (!is_array($php_files) || $php_files[0] != 0) {
|
|
||||||
Log :: fatal(_("Fail to list PHP files."));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract messages from PHP files using xgettext
|
|
||||||
$result = run_external_command(
|
|
||||||
array(
|
|
||||||
"xgettext",
|
|
||||||
"--from-code utf-8",
|
|
||||||
"--language=PHP",
|
|
||||||
"-o", "$root_lang_dir/php-messages.pot", // Output
|
|
||||||
"--omit-header", // No POT header
|
|
||||||
"--keyword=___", // Handle custom ___() translation function
|
|
||||||
"--files=-" // Read files to parse from STDIN
|
|
||||||
),
|
|
||||||
$php_files[1] // Pass PHP files list via STDIN
|
|
||||||
);
|
|
||||||
if (!is_array($result) || $result[0] != 0)
|
|
||||||
Log :: fatal(_("Fail to extract messages from PHP files using xgettext."));
|
|
||||||
|
|
||||||
|
|
||||||
// List JS files to parse
|
|
||||||
$js_files = run_external_command(
|
|
||||||
array('find', escapeshellarg("$root_dir_path/public_html/js"), '-name', "'*.js'"),
|
|
||||||
null, // no STDIN data
|
|
||||||
false // do not escape command args (already done)
|
|
||||||
);
|
|
||||||
if (!is_array($js_files) || $js_files[0] != 0) {
|
|
||||||
Log :: fatal(_("Fail to list JS files."));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract messages from JS files using xgettext
|
|
||||||
$result = run_external_command(
|
|
||||||
array(
|
|
||||||
"xgettext",
|
|
||||||
"--from-code utf-8",
|
|
||||||
"--language=JavaScript",
|
|
||||||
"-o", "$root_lang_dir/js-messages.pot", // Output
|
|
||||||
"--omit-header", // No POT header
|
|
||||||
"--keyword=___", // Handle custom ___() translation function
|
|
||||||
"--files=-" // Read files to parse from STDIN
|
|
||||||
),
|
|
||||||
$js_files[1] // Pass JS files list via STDIN
|
|
||||||
);
|
|
||||||
if (!is_array($result) || $result[0] != 0)
|
|
||||||
Log :: fatal(_("Fail to extract messages from JS files using xgettext."));
|
|
||||||
|
|
||||||
// Extract messages from templates files using tsmarty2c.php
|
|
||||||
$result = run_external_command(
|
|
||||||
array (
|
|
||||||
"$root_dir_path/vendor/bin/tsmarty2c.php",
|
|
||||||
"-o", "$root_lang_dir/templates-messages.pot",
|
|
||||||
$smarty_templates_dir,
|
|
||||||
)
|
|
||||||
);
|
|
||||||
if (!is_array($result) || $result[0] != 0)
|
|
||||||
Log :: fatal(
|
|
||||||
_("Fail to extract messages from template files using tsmarty2c.php script."));
|
|
||||||
|
|
||||||
$fd = fopen("$root_lang_dir/headers.pot", 'w');
|
|
||||||
$headers = array(
|
|
||||||
'msgid ""',
|
|
||||||
'msgstr ""',
|
|
||||||
'"POT-Creation-Date: '.date('Y-m-d H:iO').'\n"',
|
|
||||||
'"PO-Revision-Date: '.date('Y-m-d H:iO').'\n"',
|
|
||||||
'"MIME-Version: 1.0\n"',
|
|
||||||
'"Content-Type: text/plain; charset=utf-8\n"',
|
|
||||||
'"Content-Transfer-Encoding: 8bit\n"',
|
|
||||||
);
|
|
||||||
fwrite($fd, implode("\n", $headers));
|
|
||||||
fclose($fd);
|
|
||||||
|
|
||||||
// Merge previous results in messages.pot file using msgcat
|
|
||||||
$result = run_external_command(array(
|
|
||||||
'msgcat',
|
|
||||||
"$root_lang_dir/headers.pot",
|
|
||||||
"$root_lang_dir/php-messages.pot",
|
|
||||||
"$root_lang_dir/js-messages.pot",
|
|
||||||
"$root_lang_dir/templates-messages.pot",
|
|
||||||
"-t", "utf-8", "--use-first",
|
|
||||||
"-o", "$root_lang_dir/messages.pot",
|
|
||||||
));
|
|
||||||
if (!is_array($result) || $result[0] != 0)
|
|
||||||
Log :: fatal(_("Fail to merge messages using msgcat."));
|
|
||||||
}
|
|
||||||
add_cli_command(
|
|
||||||
'extract_messages',
|
|
||||||
'cli_extract_messages',
|
|
||||||
___("Extract messages that need to be translated"),
|
|
||||||
null,
|
|
||||||
___("This command could be used to generate/update lang/messages.pot file.")
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Command to update messages from lang/messages.pot file to
|
|
||||||
* all PO file in lang/[lang]/LC_MESSAGES.
|
|
||||||
*
|
|
||||||
* @param array $command_args The command arguments
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
function cli_update_messages($command_args) {
|
|
||||||
global $root_dir_path, $root_lang_dir, $smarty_templates_dir;
|
|
||||||
|
|
||||||
$compendium_args = array();
|
|
||||||
foreach ($command_args as $path) {
|
|
||||||
if (!file_exists($path))
|
|
||||||
Log :: fatal(_("Compendium file %s not found."), $path);
|
|
||||||
$compendium_args[] = '-C';
|
|
||||||
$compendium_args[] = $path;
|
|
||||||
}
|
|
||||||
|
|
||||||
$pot_file = "$root_lang_dir/messages.pot";
|
|
||||||
if (!is_file($pot_file))
|
|
||||||
Log :: fatal(_("POT file not found (%s). Please run extract_messages first."), $pot_file);
|
|
||||||
|
|
||||||
if ($dh = opendir($root_lang_dir)) {
|
|
||||||
$error = False;
|
|
||||||
while (($file = readdir($dh)) !== false) {
|
|
||||||
if (
|
|
||||||
!is_dir($root_lang_dir . '/' . $file) ||
|
|
||||||
in_array($file, array('.', '..')) ||
|
|
||||||
is_link($root_lang_dir . '/' . $file)
|
|
||||||
)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
Log :: debug(_("Lang directory '%s' found"), $file);
|
|
||||||
|
|
||||||
// Check LC_MESSAGES directory exists
|
|
||||||
$lang = $file;
|
|
||||||
$lang_dir = $root_lang_dir . '/' . $file . '/LC_MESSAGES' ;
|
|
||||||
if (!is_dir($lang_dir)) {
|
|
||||||
Log :: debug(_("LC_MESSAGES directory not found in lang '%s' directory, ignore it."),
|
|
||||||
$lang);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$po_file = $lang_dir . '/' . TEXT_DOMAIN . '.po';
|
|
||||||
$created = false;
|
|
||||||
if (!is_file($po_file)) {
|
|
||||||
// Init PO file from POT file using msginit
|
|
||||||
$result = run_external_command(
|
|
||||||
array("msginit", "-i", "$pot_file", "-l", "$lang", "-o", $po_file)
|
|
||||||
);
|
|
||||||
if (is_array($result) && $result[0] == 0) {
|
|
||||||
$created = true;
|
|
||||||
} else {
|
|
||||||
Log :: error(_("Fail to init messages in %s PO file using msginit (%s)."),
|
|
||||||
$lang, $po_file);
|
|
||||||
$error = True;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update messages in PO file from POT file using msgmerge
|
|
||||||
// Note: msginit does not accept compendium files, so we also run
|
|
||||||
// msgmerge on creation with compendium file(s).
|
|
||||||
if (is_file($po_file) && (!$created || $compendium_args)) {
|
|
||||||
$result = run_external_command(
|
|
||||||
array_merge(
|
|
||||||
array("msgmerge", "-q", "-U"),
|
|
||||||
$compendium_args,
|
|
||||||
array($po_file, $pot_file)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
if (!is_array($result) || $result[0] != 0) {
|
|
||||||
Log :: error(_("Fail to update messages in %s PO file using msgmerge (%s)."),
|
|
||||||
$lang, $po_file);
|
|
||||||
$error = True;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
elseif (!$created) {
|
|
||||||
Log :: debug(_("PO file not found in lang '%s' directory, ignore it."), $lang);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
closedir($dh);
|
|
||||||
return !$error;
|
|
||||||
}
|
|
||||||
|
|
||||||
Log :: fatal(_("Fail to open root lang directory (%s)."), $root_dir_path);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
add_cli_command(
|
|
||||||
'update_messages',
|
|
||||||
'cli_update_messages',
|
|
||||||
___("Update messages in translation PO lang files"),
|
|
||||||
null,
|
|
||||||
___("This command could be used to init/update PO files in lang/*/LC_MESSAGES directories.")
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Command to compile messages from existing translation PO lang files
|
|
||||||
* to corresponding MO files and as JSON catalog (for translation in JS).
|
|
||||||
*
|
|
||||||
* @param array $command_args The command arguments
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
function cli_compile_messages($command_args) {
|
|
||||||
global $root_dir_path, $root_lang_dir, $smarty_templates_dir;
|
|
||||||
|
|
||||||
if ($dh = opendir($root_lang_dir)) {
|
|
||||||
$error = False;
|
|
||||||
while (($file = readdir($dh)) !== false) {
|
|
||||||
if (
|
|
||||||
!is_dir($root_lang_dir . '/' . $file) ||
|
|
||||||
in_array($file, array('.', '..'))
|
|
||||||
)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (is_link($root_lang_dir . '/' . $file)) {
|
|
||||||
$real_lang_dir = readlink($root_lang_dir . '/' . $file);
|
|
||||||
if (dirname($real_lang_dir) != '.' || !is_dir($root_lang_dir . '/' . $real_lang_dir))
|
|
||||||
continue;
|
|
||||||
$lang = $file;
|
|
||||||
Log :: debug(_("Lang alias symlink found: %s -> %s"), $lang, $real_lang_dir);
|
|
||||||
|
|
||||||
// Create JSON catalog symlink (if not exists)
|
|
||||||
$js_link = "$root_dir_path/public_html/translations/$lang.js";
|
|
||||||
$link_target = "$real_lang_dir.js";
|
|
||||||
if (!file_exists($js_link)) {
|
|
||||||
if (symlink($link_target, $js_link)) {
|
|
||||||
Log :: info(_("JSON catalog symlink for %s -> %s created (%s)"),
|
|
||||||
$lang, $real_lang_dir, $js_link);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Log :: error(_("Fail to create JSON catalog symlink for %s -> %s (%s)"),
|
|
||||||
$lang, $real_lang_dir, $js_link);
|
|
||||||
$error = True;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
elseif (readlink($js_link) == $link_target) {
|
|
||||||
Log :: debug(_("JSON catalog symlink for %s -> %s already exist (%s)"),
|
|
||||||
$lang, $real_lang_dir, $js_link);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Log :: warning(
|
|
||||||
_("JSON catalog file for %s already exist, but it's not a symlink to %s (%s)"),
|
|
||||||
$lang, $real_lang_dir, $js_link
|
|
||||||
);
|
|
||||||
$error = True;
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Log :: debug(_("Lang directory '%s' found"), $file);
|
|
||||||
|
|
||||||
// Check LC_MESSAGES directory exists
|
|
||||||
$lang = $file;
|
|
||||||
$lang_dir = $root_lang_dir . '/' . $file . '/LC_MESSAGES' ;
|
|
||||||
if (!is_dir($lang_dir)) {
|
|
||||||
Log :: debug(_("LC_MESSAGES directory not found in lang '%s' directory, ignore it."),
|
|
||||||
$lang);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test .PO file is present
|
|
||||||
$po_file = $lang_dir . '/' . TEXT_DOMAIN . '.po';
|
|
||||||
if (!is_file($po_file)) {
|
|
||||||
Log :: debug(_("PO file not found in lang '%s' directory, ignore it."),
|
|
||||||
$lang);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$mo_file = preg_replace('/\.po$/', '.mo', $po_file);
|
|
||||||
|
|
||||||
// Compile messages from PO file to MO file using msgfmt
|
|
||||||
$result = run_external_command(
|
|
||||||
array("msgfmt", "-o", $mo_file, $po_file)
|
|
||||||
);
|
|
||||||
if (!is_array($result) || $result[0] != 0) {
|
|
||||||
Log :: error(
|
|
||||||
_("Fail to compile messages from %s PO file as MO file using msgfmt (%s)."),
|
|
||||||
$lang, $po_file
|
|
||||||
);
|
|
||||||
$error = True;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compile messages from PO file to JSON catalog file
|
|
||||||
$json_catalog = po2json($lang, $po_file);
|
|
||||||
$js_file = "$root_dir_path/public_html/translations/$lang.js";
|
|
||||||
if(!$fd = fopen($js_file, 'w')) {
|
|
||||||
Log :: error(_("Fail to open %s JSON catalog file in write mode (%s)."),
|
|
||||||
$lang, $js_file);
|
|
||||||
$error = True;
|
|
||||||
}
|
|
||||||
elseif (fwrite($fd, sprintf("translations_data = %s;", $json_catalog)) === false) {
|
|
||||||
Log :: error(_("Fail to write %s JSON catalog in file (%s)."),
|
|
||||||
$lang, $js_file);
|
|
||||||
$error = True;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Log :: info(_("%s JSON catalog writed (%s)."), $lang, $js_file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
closedir($dh);
|
|
||||||
|
|
||||||
return !$error;
|
|
||||||
}
|
|
||||||
Log :: fatal(_("Fail to open root lang directory (%s)."), $root_dir_path);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
add_cli_command(
|
|
||||||
'compile_messages',
|
|
||||||
'cli_compile_messages',
|
|
||||||
___(
|
|
||||||
"Compile messages from existing translation PO lang files to ".
|
|
||||||
"corresponding MO files and JSON catalogs"
|
|
||||||
),
|
|
||||||
null,
|
|
||||||
___(
|
|
||||||
"This command could be used to compile PO files in lang/*/LC_MESSAGES ".
|
|
||||||
"directories to MO files and as JSON catalogs in public_html/translations."
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
# vim: tabstop=2 shiftwidth=2 softtabstop=2 expandtab
|
|
|
@ -1,151 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
use EesyPHP\Log;
|
|
||||||
|
|
||||||
// Gettext text domain
|
|
||||||
define('TEXT_DOMAIN', 'DEFAULT');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* List available translation languages
|
|
||||||
*
|
|
||||||
* @param $as_locales boolean If true, locale names will be return instead
|
|
||||||
* of primary languages (optional, default: false)
|
|
||||||
*
|
|
||||||
* @return array Array of available translation languages (or locales)
|
|
||||||
*/
|
|
||||||
function get_available_langs($as_locales=false) {
|
|
||||||
global $root_lang_dir;
|
|
||||||
if (!is_dir($root_lang_dir))
|
|
||||||
Log :: fatal("Root land directory not found ($root_lang_dir)");
|
|
||||||
$langs = array(($as_locales?'en_US.UTF8':'en'));
|
|
||||||
if ($dh = opendir($root_lang_dir)) {
|
|
||||||
while (($file = readdir($dh)) !== false) {
|
|
||||||
if (!is_dir($root_lang_dir . '/' . $file) || in_array($file, array('.', '..')))
|
|
||||||
continue;
|
|
||||||
if ($as_locales) {
|
|
||||||
$langs[] = $file;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$lang = Locale::getPrimaryLanguage($file);
|
|
||||||
if (!in_array($lang, $langs))
|
|
||||||
$langs[] = $lang;
|
|
||||||
}
|
|
||||||
closedir($dh);
|
|
||||||
}
|
|
||||||
$langs = array_unique($langs);
|
|
||||||
Log :: trace('Available '.($as_locales?'locales':'languages').': '.implode(', ', $langs));
|
|
||||||
return $langs;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get locale name corresponding to specified translation language
|
|
||||||
*
|
|
||||||
* @param $lang string The translation language
|
|
||||||
* @param $default string|null Default locale name to return if any available translation
|
|
||||||
* locales matched with the specified language
|
|
||||||
* (optional, default: $default_locale)
|
|
||||||
* @return string Corresponding locale
|
|
||||||
*/
|
|
||||||
function lang2locale($lang, $default=null) {
|
|
||||||
global $default_locale;
|
|
||||||
if (is_null($default))
|
|
||||||
$default = $default_locale;
|
|
||||||
foreach (get_available_langs(true) as $locale) {
|
|
||||||
if (strpos($locale, $lang) === false)
|
|
||||||
continue;
|
|
||||||
return $locale;
|
|
||||||
}
|
|
||||||
return $default;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper function: just mark message for translation
|
|
||||||
*
|
|
||||||
* @param string $msg The message to translate
|
|
||||||
*
|
|
||||||
* @return string The message without transformation
|
|
||||||
*/
|
|
||||||
function ___($msg) {
|
|
||||||
return $msg;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize translation system
|
|
||||||
*
|
|
||||||
* Detect best translation language and configure the translation
|
|
||||||
* system.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
function init_translation() {
|
|
||||||
global $root_dir_path, $root_lang_dir, $default_locale, $smarty;
|
|
||||||
$root_lang_dir = "$root_dir_path/lang";
|
|
||||||
|
|
||||||
if (!class_exists('Locale')) {
|
|
||||||
Log :: error('Locale PHP class does not exist. May be php-intl is not installed?');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$available_langs = get_available_langs();
|
|
||||||
if (php_sapi_name() != "cli") {
|
|
||||||
if (isset($_REQUEST['lang']) && in_array($_REQUEST['lang'], $available_langs)) {
|
|
||||||
$lang = $_REQUEST['lang'];
|
|
||||||
Log :: trace("Select lang from request parameter: '$lang'");
|
|
||||||
}
|
|
||||||
elseif (isset($_SESSION['lang']) && in_array($_SESSION['lang'], $available_langs) && !isset($_REQUEST['reset_lang'])) {
|
|
||||||
$lang = $_SESSION['lang'];
|
|
||||||
Log :: trace("Restore lang from session: '$lang'");
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$lang = Locale::lookup(
|
|
||||||
get_available_langs(),
|
|
||||||
Locale::acceptFromHttp($_SERVER['HTTP_ACCEPT_LANGUAGE']),
|
|
||||||
true,
|
|
||||||
Locale::getPrimaryLanguage($default_locale)
|
|
||||||
);
|
|
||||||
Log :: trace("Best lang found is '$lang'");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$lang = null;
|
|
||||||
$sys_current = getenv('LC_ALL');
|
|
||||||
if (!$sys_current)
|
|
||||||
$sys_current = getenv('LANG');
|
|
||||||
if ($sys_current)
|
|
||||||
$lang = Locale::getPrimaryLanguage($sys_current);
|
|
||||||
if (is_null($lang)) {
|
|
||||||
Log :: trace('No configured lang detected from CLI env, use default.');
|
|
||||||
$lang = Locale::getPrimaryLanguage($default_locale);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
Log :: trace("Lang detected from CLI env : '$lang'");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keep selected lang in session
|
|
||||||
$_SESSION['lang'] = $lang;
|
|
||||||
|
|
||||||
$locale = lang2locale($lang);
|
|
||||||
Log :: trace("Matching locale found with language '$lang' is '$locale'");
|
|
||||||
|
|
||||||
// Gettext firstly look the LANGUAGE env variable, so set it
|
|
||||||
if (!putenv("LANGUAGE=$locale"))
|
|
||||||
Log :: error("Fail to set LANGUAGE variable in environnement to '$locale'");
|
|
||||||
|
|
||||||
// Set the locale
|
|
||||||
if (setlocale(LC_ALL, $locale) === false)
|
|
||||||
Log :: error("An error occured setting locale to '$locale'");
|
|
||||||
|
|
||||||
// Configure and set the text domain
|
|
||||||
$fullpath = bindtextdomain(TEXT_DOMAIN, $root_lang_dir);
|
|
||||||
Log :: trace("Text domain fullpath is '$fullpath'.");
|
|
||||||
Log :: trace("Text domain is '".textdomain(TEXT_DOMAIN)."'.");
|
|
||||||
Log :: trace("Test: "._('Hello world !'));
|
|
||||||
|
|
||||||
// JS translation file
|
|
||||||
$js_translation_file = "translations/$lang.js";
|
|
||||||
if (php_sapi_name() != "cli" && is_file("$root_dir_path/public_html/$js_translation_file")) {
|
|
||||||
add_js_file(array("lib/babel.js", "js/translation.js", $js_translation_file));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# vim: tabstop=2 shiftwidth=2 softtabstop=2 expandtab
|
|
|
@ -4,7 +4,7 @@ parameters:
|
||||||
- src
|
- src
|
||||||
- includes
|
- includes
|
||||||
- public_html
|
- public_html
|
||||||
- bin
|
- bin/eesyphp
|
||||||
excludePaths:
|
excludePaths:
|
||||||
- includes/config.local.php
|
- includes/config.local.php
|
||||||
ignoreErrors:
|
ignoreErrors:
|
||||||
|
|
185
src/Cli.php
Normal file
185
src/Cli.php
Normal file
|
@ -0,0 +1,185 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace EesyPHP;
|
||||||
|
|
||||||
|
use EesyPHP\Log;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
class Cli {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registered commands
|
||||||
|
* @var array<string,array>
|
||||||
|
*/
|
||||||
|
protected static $commands = array();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current CLI command
|
||||||
|
* @var string|null
|
||||||
|
*/
|
||||||
|
protected static $command = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add CLI command
|
||||||
|
* @param string $command The command name
|
||||||
|
* @param callable $handler The command handler
|
||||||
|
* @param string $short_desc Short command description
|
||||||
|
* @param string|null $usage_args Argument usage short message
|
||||||
|
* @param string|array<string>|null $long_desc Long command description
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function add_command($command, $handler, $short_desc, $usage_args=null, $long_desc=null,
|
||||||
|
$override=false) {
|
||||||
|
if (array_key_exists($command, self :: $commands) && !$override) {
|
||||||
|
Log :: error(_("The CLI command '%s' already exists."), $command);
|
||||||
|
return False;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_callable($handler)) {
|
||||||
|
Log :: error(_("The CLI command '%s' handler is not callable !"), $command);
|
||||||
|
return False;
|
||||||
|
}
|
||||||
|
|
||||||
|
self :: $commands[$command] = array (
|
||||||
|
'handler' => $handler,
|
||||||
|
'short_desc' => $short_desc,
|
||||||
|
'usage_args' => $usage_args,
|
||||||
|
'long_desc' => $long_desc,
|
||||||
|
);
|
||||||
|
return True;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show usage message
|
||||||
|
* @param string|false $error Error message to show (optional)
|
||||||
|
* @param array $extra_args Extra arguments to use to compute error message using sprintf
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function usage($error=false, ...$extra_args) {
|
||||||
|
global $argv;
|
||||||
|
|
||||||
|
// If extra arguments passed, format error message using sprintf
|
||||||
|
if ($extra_args) {
|
||||||
|
$error = call_user_func_array(
|
||||||
|
'sprintf',
|
||||||
|
array_merge(array($error), $extra_args)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($error)
|
||||||
|
echo "$error\n\n";
|
||||||
|
printf(_("Usage: %s [-h] [-qd] command\n"), basename($argv[0]));
|
||||||
|
echo _(" -h Show this message\n");
|
||||||
|
echo _(" -q / -d Quiet/Debug mode\n");
|
||||||
|
echo _(" --trace Trace mode (the most verbose)\n");
|
||||||
|
echo _(" command Command to run\n");
|
||||||
|
echo "\n";
|
||||||
|
echo _("Available commands:\n");
|
||||||
|
|
||||||
|
foreach (self :: $commands as $command => $info) {
|
||||||
|
if (self :: $command && $command != self :: $command)
|
||||||
|
continue;
|
||||||
|
echo (
|
||||||
|
" ".str_replace(
|
||||||
|
"\n", "\n ",
|
||||||
|
wordwrap("$command : "._($info['short_desc'])))
|
||||||
|
."\n\n");
|
||||||
|
echo (
|
||||||
|
" ".basename($argv[0])." $command ".
|
||||||
|
($info['usage_args']?_($info['usage_args']):'').
|
||||||
|
"\n");
|
||||||
|
if ($info['long_desc']) {
|
||||||
|
if (is_array($info['long_desc'])) {
|
||||||
|
$lines = array();
|
||||||
|
foreach ($info['long_desc'] as $line)
|
||||||
|
$lines[] = _($line);
|
||||||
|
$info['long_desc'] = implode("\n", $lines);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
$info['long_desc'] = _($info['long_desc']);
|
||||||
|
|
||||||
|
echo "\n ".str_replace("\n", "\n ", wordwrap($info['long_desc']))."\n";
|
||||||
|
}
|
||||||
|
echo "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
exit(($error?1:0));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle command line arguments
|
||||||
|
* @param array|null $args Command line argurment to handle (optional, default: $argv)
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function handle_args($args=null) {
|
||||||
|
global $argv;
|
||||||
|
$args = is_array($args)?$args:array_slice($argv, 1);
|
||||||
|
$log_level_set = false;
|
||||||
|
self :: $command = null;
|
||||||
|
$command_args = array();
|
||||||
|
for ($i=0; $i < count($args); $i++) {
|
||||||
|
if (array_key_exists($args[$i], self :: $commands)) {
|
||||||
|
if (!self :: $command)
|
||||||
|
self :: $command = $args[$i];
|
||||||
|
else
|
||||||
|
self :: usage(_("Only one command could be executed !"));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
switch($args[$i]) {
|
||||||
|
case '-h':
|
||||||
|
case '--help':
|
||||||
|
self :: usage();
|
||||||
|
break;
|
||||||
|
case '-d':
|
||||||
|
case '--debug':
|
||||||
|
Log :: set_level('DEBUG');
|
||||||
|
$log_level_set = true;
|
||||||
|
break;
|
||||||
|
case '-q':
|
||||||
|
case '--quiet':
|
||||||
|
Log :: set_level('WARNING');
|
||||||
|
$log_level_set = true;
|
||||||
|
break;
|
||||||
|
case '--trace':
|
||||||
|
Log :: set_level('TRACE');
|
||||||
|
$log_level_set = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (self :: $command)
|
||||||
|
$command_args[] = $args[$i];
|
||||||
|
else
|
||||||
|
self :: usage(
|
||||||
|
_(
|
||||||
|
"Invalid parameter \"%s\".\nNote: Command's parameter/argument must be place ".
|
||||||
|
"after the command."
|
||||||
|
), $args[$i]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$log_level_set)
|
||||||
|
Log :: set_level('INFO');
|
||||||
|
|
||||||
|
if (!self :: $command)
|
||||||
|
self :: usage();
|
||||||
|
|
||||||
|
Log :: debug(
|
||||||
|
"Run %s command %s with argument(s) '%s'.",
|
||||||
|
basename($args[0]), self :: $command, implode("', '", $command_args)
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$result = call_user_func(self :: $commands[self :: $command]['handler'], $command_args);
|
||||||
|
|
||||||
|
exit($result?0:1);
|
||||||
|
}
|
||||||
|
catch(Exception $e) {
|
||||||
|
Log :: exception($e, _("An exception occured running command %s"), self :: $command);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
553
src/I18n.php
Normal file
553
src/I18n.php
Normal file
|
@ -0,0 +1,553 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace EesyPHP;
|
||||||
|
|
||||||
|
use EesyPHP\Cli;
|
||||||
|
use EesyPHP\Log;
|
||||||
|
|
||||||
|
use Locale;
|
||||||
|
use add_js_file;
|
||||||
|
|
||||||
|
class I18n {
|
||||||
|
// Gettext text domain
|
||||||
|
public const TEXT_DOMAIN = 'DEFAULT';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The root directory path of translation files
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected static string $root_path = 'locales';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default locale
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected static string $default_locale = 'en_US.UTF8';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize translation system
|
||||||
|
*
|
||||||
|
* Detect best translation language and configure the translation
|
||||||
|
* system.
|
||||||
|
*
|
||||||
|
* @param string|null $root_path The root directory path of translation files
|
||||||
|
* (optional, default: ./locales)
|
||||||
|
* @param string|null $default_locale The default locale
|
||||||
|
* (optional, default: en_US.UTF8)
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function init($root_path=null, $default_locale=null) {
|
||||||
|
global $root_dir_path;
|
||||||
|
if (!is_null($root_path))
|
||||||
|
self :: $root_path = $root_path;
|
||||||
|
if (!is_null($default_locale))
|
||||||
|
self :: $default_locale = $default_locale;
|
||||||
|
|
||||||
|
if (!class_exists('Locale')) {
|
||||||
|
Log :: error('Locale PHP class does not exist. May be php-intl is not installed?');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$available_langs = self :: get_available_langs();
|
||||||
|
if (php_sapi_name() != "cli") {
|
||||||
|
if (isset($_REQUEST['lang']) && in_array($_REQUEST['lang'], $available_langs)) {
|
||||||
|
$lang = $_REQUEST['lang'];
|
||||||
|
Log :: trace("Select lang from request parameter: '$lang'");
|
||||||
|
}
|
||||||
|
elseif (isset($_SESSION['lang']) && in_array($_SESSION['lang'], $available_langs) && !isset($_REQUEST['reset_lang'])) {
|
||||||
|
$lang = $_SESSION['lang'];
|
||||||
|
Log :: trace("Restore lang from session: '$lang'");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$lang = Locale::lookup(
|
||||||
|
self :: get_available_langs(),
|
||||||
|
Locale::acceptFromHttp($_SERVER['HTTP_ACCEPT_LANGUAGE']),
|
||||||
|
true,
|
||||||
|
Locale::getPrimaryLanguage(self :: $default_locale)
|
||||||
|
);
|
||||||
|
Log :: trace("Best lang found is '$lang'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$lang = null;
|
||||||
|
$sys_current = getenv('LC_ALL');
|
||||||
|
if (!$sys_current)
|
||||||
|
$sys_current = getenv('LANG');
|
||||||
|
if ($sys_current)
|
||||||
|
$lang = Locale::getPrimaryLanguage($sys_current);
|
||||||
|
if (is_null($lang)) {
|
||||||
|
Log :: trace('No configured lang detected from CLI env, use default.');
|
||||||
|
$lang = Locale::getPrimaryLanguage(self :: $default_locale);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
Log :: trace("Lang detected from CLI env : '$lang'");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep selected lang in session
|
||||||
|
$_SESSION['lang'] = $lang;
|
||||||
|
|
||||||
|
$locale = self :: lang2locale($lang);
|
||||||
|
Log :: trace("Matching locale found with language '$lang' is '$locale'");
|
||||||
|
|
||||||
|
// Gettext firstly look the LANGUAGE env variable, so set it
|
||||||
|
if (!putenv("LANGUAGE=$locale"))
|
||||||
|
Log :: error("Fail to set LANGUAGE variable in environnement to '$locale'");
|
||||||
|
|
||||||
|
// Set the locale
|
||||||
|
if (setlocale(LC_ALL, $locale) === false)
|
||||||
|
Log :: error("An error occured setting locale to '$locale'");
|
||||||
|
|
||||||
|
// Configure and set the text domain
|
||||||
|
$fullpath = bindtextdomain(self :: TEXT_DOMAIN, self :: $root_path);
|
||||||
|
Log :: trace("Text domain fullpath is '$fullpath'.");
|
||||||
|
Log :: trace("Text domain is '".textdomain(self :: TEXT_DOMAIN)."'.");
|
||||||
|
Log :: trace("Test: "._('Hello world !'));
|
||||||
|
|
||||||
|
// JS translation file
|
||||||
|
$js_translation_file = "translations/$lang.js";
|
||||||
|
if (
|
||||||
|
php_sapi_name() != "cli"
|
||||||
|
&& isset($root_dir_path) && $root_dir_path
|
||||||
|
&& is_file("$root_dir_path/public_html/$js_translation_file")
|
||||||
|
&& function_exists('add_js_file')
|
||||||
|
) {
|
||||||
|
add_js_file(array("lib/babel.js", "js/translation.js", $js_translation_file));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (php_sapi_name() == 'cli') {
|
||||||
|
Cli :: add_command(
|
||||||
|
'extract_messages',
|
||||||
|
array('\\EesyPHP\\I18n', 'cli_extract_messages'),
|
||||||
|
self :: ___("Extract messages that need to be translated"),
|
||||||
|
null,
|
||||||
|
self :: ___("This command could be used to generate/update lang/messages.pot file.")
|
||||||
|
);
|
||||||
|
|
||||||
|
Cli :: add_command(
|
||||||
|
'update_messages',
|
||||||
|
array('\\EesyPHP\\I18n', 'cli_update_messages'),
|
||||||
|
self :: ___("Update messages in translation PO lang files"),
|
||||||
|
null,
|
||||||
|
self :: ___("This command could be used to init/update PO files in lang/*/LC_MESSAGES directories.")
|
||||||
|
);
|
||||||
|
|
||||||
|
Cli :: add_command(
|
||||||
|
'compile_messages',
|
||||||
|
array('\\EesyPHP\\I18n', 'cli_compile_messages'),
|
||||||
|
self :: ___(
|
||||||
|
"Compile messages from existing translation PO lang files to ".
|
||||||
|
"corresponding MO files and JSON catalogs"
|
||||||
|
),
|
||||||
|
null,
|
||||||
|
self :: ___(
|
||||||
|
"This command could be used to compile PO files in lang/*/LC_MESSAGES ".
|
||||||
|
"directories to MO files and as JSON catalogs in public_html/translations."
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List available translation languages
|
||||||
|
*
|
||||||
|
* @param $as_locales boolean If true, locale names will be return instead
|
||||||
|
* of primary languages (optional, default: false)
|
||||||
|
*
|
||||||
|
* @return array Array of available translation languages (or locales)
|
||||||
|
*/
|
||||||
|
public static function get_available_langs($as_locales=false) {
|
||||||
|
if (!is_dir(self :: $root_path))
|
||||||
|
Log :: fatal("Root land directory not found (%s)", self :: $root_path);
|
||||||
|
$langs = array(($as_locales?'en_US.UTF8':'en'));
|
||||||
|
if ($dh = opendir(self :: $root_path)) {
|
||||||
|
while (($file = readdir($dh)) !== false) {
|
||||||
|
if (!is_dir(self :: $root_path . '/' . $file) || in_array($file, array('.', '..')))
|
||||||
|
continue;
|
||||||
|
if ($as_locales) {
|
||||||
|
$langs[] = $file;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$lang = Locale::getPrimaryLanguage($file);
|
||||||
|
if (!in_array($lang, $langs))
|
||||||
|
$langs[] = $lang;
|
||||||
|
}
|
||||||
|
closedir($dh);
|
||||||
|
}
|
||||||
|
$langs = array_unique($langs);
|
||||||
|
Log :: trace('Available '.($as_locales?'locales':'languages').': '.implode(', ', $langs));
|
||||||
|
return $langs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get locale name corresponding to specified translation language
|
||||||
|
*
|
||||||
|
* @param $lang string The translation language
|
||||||
|
* @param $default string|null Default locale name to return if any available translation
|
||||||
|
* locales matched with the specified language
|
||||||
|
* (optional, default: self :: $default_locale)
|
||||||
|
* @return string Corresponding locale
|
||||||
|
*/
|
||||||
|
public static function lang2locale($lang, $default=null) {
|
||||||
|
if (is_null($default))
|
||||||
|
$default = self :: $default_locale;
|
||||||
|
foreach (self :: get_available_langs(true) as $locale) {
|
||||||
|
if (strpos($locale, $lang) === false)
|
||||||
|
continue;
|
||||||
|
return $locale;
|
||||||
|
}
|
||||||
|
return $default;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function: just mark message for translation
|
||||||
|
*
|
||||||
|
* @param string $msg The message to translate
|
||||||
|
*
|
||||||
|
* @return string The message without transformation
|
||||||
|
*/
|
||||||
|
public static function ___($msg) {
|
||||||
|
return $msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
********************************************************************
|
||||||
|
* Translations CLI commands *
|
||||||
|
********************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert PO file to JSON file
|
||||||
|
*
|
||||||
|
* @param string $locale The locale of the input PO file
|
||||||
|
* @param string $path The path of the input PO file
|
||||||
|
*
|
||||||
|
* @return string JSON encoded file content
|
||||||
|
*/
|
||||||
|
public static function po2json($locale, $path) {
|
||||||
|
$fileHandler = new \Sepia\PoParser\SourceHandler\FileSystem($path);
|
||||||
|
$poparser = new \Sepia\PoParser\Parser($fileHandler);
|
||||||
|
$catalog = $poparser->parse();
|
||||||
|
$headers = $catalog->getHeader();
|
||||||
|
|
||||||
|
$messages = array();
|
||||||
|
foreach ($catalog->getEntries() as $entry) {
|
||||||
|
// msg id json format
|
||||||
|
$msg = $entry->getMsgStr();
|
||||||
|
if ($entry->isPlural())
|
||||||
|
$msg = array($msg, $entry->getMsgIdPlural());
|
||||||
|
$messages[$entry->getMsgId()] = $msg;
|
||||||
|
}
|
||||||
|
return json_encode(array(
|
||||||
|
'messages' => $messages,
|
||||||
|
'locale' => $locale,
|
||||||
|
'domain' => self :: TEXT_DOMAIN,
|
||||||
|
'plural_expr' => '(n > 1)',
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Command to extract messages from PHP/JS & template files and
|
||||||
|
* generate the lang/messages.pot file.
|
||||||
|
*
|
||||||
|
* @param array $command_args The command arguments
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function cli_extract_messages($command_args) {
|
||||||
|
global $root_dir_path, $smarty_templates_dir;
|
||||||
|
|
||||||
|
// List PHP files to parse
|
||||||
|
$php_files = run_external_command(
|
||||||
|
array('find', escapeshellarg($root_dir_path), '-name', "'*.php'"),
|
||||||
|
null, // no STDIN data
|
||||||
|
false // do not escape command args (already done)
|
||||||
|
);
|
||||||
|
if (!is_array($php_files) || $php_files[0] != 0) {
|
||||||
|
Log :: fatal(_("Fail to list PHP files."));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract messages from PHP files using xgettext
|
||||||
|
$result = run_external_command(
|
||||||
|
array(
|
||||||
|
"xgettext",
|
||||||
|
"--from-code utf-8",
|
||||||
|
"--language=PHP",
|
||||||
|
"-o", self :: $root_path."/php-messages.pot", // Output
|
||||||
|
"--omit-header", // No POT header
|
||||||
|
"--keyword=___", // Handle custom ___() translation function
|
||||||
|
"--files=-" // Read files to parse from STDIN
|
||||||
|
),
|
||||||
|
$php_files[1] // Pass PHP files list via STDIN
|
||||||
|
);
|
||||||
|
if (!is_array($result) || $result[0] != 0)
|
||||||
|
Log :: fatal(_("Fail to extract messages from PHP files using xgettext."));
|
||||||
|
|
||||||
|
|
||||||
|
// List JS files to parse
|
||||||
|
$js_files = run_external_command(
|
||||||
|
array('find', escapeshellarg("$root_dir_path/public_html/js"), '-name', "'*.js'"),
|
||||||
|
null, // no STDIN data
|
||||||
|
false // do not escape command args (already done)
|
||||||
|
);
|
||||||
|
if (!is_array($js_files) || $js_files[0] != 0) {
|
||||||
|
Log :: fatal(_("Fail to list JS files."));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract messages from JS files using xgettext
|
||||||
|
$result = run_external_command(
|
||||||
|
array(
|
||||||
|
"xgettext",
|
||||||
|
"--from-code utf-8",
|
||||||
|
"--language=JavaScript",
|
||||||
|
"-o", self :: $root_path."/js-messages.pot", // Output
|
||||||
|
"--omit-header", // No POT header
|
||||||
|
"--keyword=___", // Handle custom ___() translation function
|
||||||
|
"--files=-" // Read files to parse from STDIN
|
||||||
|
),
|
||||||
|
$js_files[1] // Pass JS files list via STDIN
|
||||||
|
);
|
||||||
|
if (!is_array($result) || $result[0] != 0)
|
||||||
|
Log :: fatal(_("Fail to extract messages from JS files using xgettext."));
|
||||||
|
|
||||||
|
// Extract messages from templates files using tsmarty2c.php
|
||||||
|
$result = run_external_command(
|
||||||
|
array (
|
||||||
|
"$root_dir_path/vendor/bin/tsmarty2c.php",
|
||||||
|
"-o", self :: $root_path."/templates-messages.pot",
|
||||||
|
$smarty_templates_dir,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
if (!is_array($result) || $result[0] != 0)
|
||||||
|
Log :: fatal(
|
||||||
|
_("Fail to extract messages from template files using tsmarty2c.php script."));
|
||||||
|
|
||||||
|
$fd = fopen(self :: $root_path."/headers.pot", 'w');
|
||||||
|
$headers = array(
|
||||||
|
'msgid ""',
|
||||||
|
'msgstr ""',
|
||||||
|
'"POT-Creation-Date: '.date('Y-m-d H:iO').'\n"',
|
||||||
|
'"PO-Revision-Date: '.date('Y-m-d H:iO').'\n"',
|
||||||
|
'"MIME-Version: 1.0\n"',
|
||||||
|
'"Content-Type: text/plain; charset=utf-8\n"',
|
||||||
|
'"Content-Transfer-Encoding: 8bit\n"',
|
||||||
|
);
|
||||||
|
fwrite($fd, implode("\n", $headers));
|
||||||
|
fclose($fd);
|
||||||
|
|
||||||
|
// Merge previous results in messages.pot file using msgcat
|
||||||
|
$result = run_external_command(array(
|
||||||
|
'msgcat',
|
||||||
|
self :: $root_path."/headers.pot",
|
||||||
|
self :: $root_path."/php-messages.pot",
|
||||||
|
self :: $root_path."/js-messages.pot",
|
||||||
|
self :: $root_path."/templates-messages.pot",
|
||||||
|
"-t", "utf-8", "--use-first",
|
||||||
|
"-o", self :: $root_path."/messages.pot",
|
||||||
|
));
|
||||||
|
if (!is_array($result) || $result[0] != 0)
|
||||||
|
Log :: fatal(_("Fail to merge messages using msgcat."));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Command to update messages from lang/messages.pot file to
|
||||||
|
* all PO file in lang/[lang]/LC_MESSAGES.
|
||||||
|
*
|
||||||
|
* @param array $command_args The command arguments
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function cli_update_messages($command_args) {
|
||||||
|
global $root_dir_path, $smarty_templates_dir;
|
||||||
|
|
||||||
|
$compendium_args = array();
|
||||||
|
foreach ($command_args as $path) {
|
||||||
|
if (!file_exists($path))
|
||||||
|
Log :: fatal(_("Compendium file %s not found."), $path);
|
||||||
|
$compendium_args[] = '-C';
|
||||||
|
$compendium_args[] = $path;
|
||||||
|
}
|
||||||
|
|
||||||
|
$pot_file = self :: $root_path."/messages.pot";
|
||||||
|
if (!is_file($pot_file))
|
||||||
|
Log :: fatal(_("POT file not found (%s). Please run extract_messages first."), $pot_file);
|
||||||
|
|
||||||
|
if ($dh = opendir(self :: $root_path)) {
|
||||||
|
$error = False;
|
||||||
|
while (($file = readdir($dh)) !== false) {
|
||||||
|
if (
|
||||||
|
!is_dir(self :: $root_path . '/' . $file) ||
|
||||||
|
in_array($file, array('.', '..')) ||
|
||||||
|
is_link(self :: $root_path . '/' . $file)
|
||||||
|
)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Log :: debug(_("Lang directory '%s' found"), $file);
|
||||||
|
|
||||||
|
// Check LC_MESSAGES directory exists
|
||||||
|
$lang = $file;
|
||||||
|
$lang_dir = self :: $root_path . '/' . $file . '/LC_MESSAGES' ;
|
||||||
|
if (!is_dir($lang_dir)) {
|
||||||
|
Log :: debug(_("LC_MESSAGES directory not found in lang '%s' directory, ignore it."),
|
||||||
|
$lang);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$po_file = $lang_dir . '/' . self :: TEXT_DOMAIN . '.po';
|
||||||
|
$created = false;
|
||||||
|
if (!is_file($po_file)) {
|
||||||
|
// Init PO file from POT file using msginit
|
||||||
|
$result = run_external_command(
|
||||||
|
array("msginit", "-i", "$pot_file", "-l", "$lang", "-o", $po_file)
|
||||||
|
);
|
||||||
|
if (is_array($result) && $result[0] == 0) {
|
||||||
|
$created = true;
|
||||||
|
} else {
|
||||||
|
Log :: error(_("Fail to init messages in %s PO file using msginit (%s)."),
|
||||||
|
$lang, $po_file);
|
||||||
|
$error = True;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update messages in PO file from POT file using msgmerge
|
||||||
|
// Note: msginit does not accept compendium files, so we also run
|
||||||
|
// msgmerge on creation with compendium file(s).
|
||||||
|
if (is_file($po_file) && (!$created || $compendium_args)) {
|
||||||
|
$result = run_external_command(
|
||||||
|
array_merge(
|
||||||
|
array("msgmerge", "-q", "-U"),
|
||||||
|
$compendium_args,
|
||||||
|
array($po_file, $pot_file)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
if (!is_array($result) || $result[0] != 0) {
|
||||||
|
Log :: error(_("Fail to update messages in %s PO file using msgmerge (%s)."),
|
||||||
|
$lang, $po_file);
|
||||||
|
$error = True;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elseif (!$created) {
|
||||||
|
Log :: debug(_("PO file not found in lang '%s' directory, ignore it."), $lang);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
closedir($dh);
|
||||||
|
return !$error;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log :: fatal(_("Fail to open root lang directory (%s)."), $root_dir_path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Command to compile messages from existing translation PO lang files
|
||||||
|
* to corresponding MO files and as JSON catalog (for translation in JS).
|
||||||
|
*
|
||||||
|
* @param array $command_args The command arguments
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function cli_compile_messages($command_args) {
|
||||||
|
global $root_dir_path, $smarty_templates_dir;
|
||||||
|
|
||||||
|
if ($dh = opendir(self :: $root_path)) {
|
||||||
|
$error = False;
|
||||||
|
while (($file = readdir($dh)) !== false) {
|
||||||
|
if (
|
||||||
|
!is_dir(self :: $root_path . '/' . $file) ||
|
||||||
|
in_array($file, array('.', '..'))
|
||||||
|
)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (is_link(self :: $root_path . '/' . $file)) {
|
||||||
|
$real_lang_dir = readlink(self :: $root_path . '/' . $file);
|
||||||
|
if (dirname($real_lang_dir) != '.' || !is_dir(self :: $root_path . '/' . $real_lang_dir))
|
||||||
|
continue;
|
||||||
|
$lang = $file;
|
||||||
|
Log :: debug(_("Lang alias symlink found: %s -> %s"), $lang, $real_lang_dir);
|
||||||
|
|
||||||
|
// Create JSON catalog symlink (if not exists)
|
||||||
|
$js_link = "$root_dir_path/public_html/translations/$lang.js";
|
||||||
|
$link_target = "$real_lang_dir.js";
|
||||||
|
if (!file_exists($js_link)) {
|
||||||
|
if (symlink($link_target, $js_link)) {
|
||||||
|
Log :: info(_("JSON catalog symlink for %s -> %s created (%s)"),
|
||||||
|
$lang, $real_lang_dir, $js_link);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Log :: error(_("Fail to create JSON catalog symlink for %s -> %s (%s)"),
|
||||||
|
$lang, $real_lang_dir, $js_link);
|
||||||
|
$error = True;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elseif (readlink($js_link) == $link_target) {
|
||||||
|
Log :: debug(_("JSON catalog symlink for %s -> %s already exist (%s)"),
|
||||||
|
$lang, $real_lang_dir, $js_link);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Log :: warning(
|
||||||
|
_("JSON catalog file for %s already exist, but it's not a symlink to %s (%s)"),
|
||||||
|
$lang, $real_lang_dir, $js_link
|
||||||
|
);
|
||||||
|
$error = True;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log :: debug(_("Lang directory '%s' found"), $file);
|
||||||
|
|
||||||
|
// Check LC_MESSAGES directory exists
|
||||||
|
$lang = $file;
|
||||||
|
$lang_dir = self :: $root_path . '/' . $file . '/LC_MESSAGES' ;
|
||||||
|
if (!is_dir($lang_dir)) {
|
||||||
|
Log :: debug(_("LC_MESSAGES directory not found in lang '%s' directory, ignore it."),
|
||||||
|
$lang);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test .PO file is present
|
||||||
|
$po_file = $lang_dir . '/' . self :: TEXT_DOMAIN . '.po';
|
||||||
|
if (!is_file($po_file)) {
|
||||||
|
Log :: debug(_("PO file not found in lang '%s' directory, ignore it."),
|
||||||
|
$lang);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$mo_file = preg_replace('/\.po$/', '.mo', $po_file);
|
||||||
|
|
||||||
|
// Compile messages from PO file to MO file using msgfmt
|
||||||
|
$result = run_external_command(
|
||||||
|
array("msgfmt", "-o", $mo_file, $po_file)
|
||||||
|
);
|
||||||
|
if (!is_array($result) || $result[0] != 0) {
|
||||||
|
Log :: error(
|
||||||
|
_("Fail to compile messages from %s PO file as MO file using msgfmt (%s)."),
|
||||||
|
$lang, $po_file
|
||||||
|
);
|
||||||
|
$error = True;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile messages from PO file to JSON catalog file
|
||||||
|
$json_catalog = self :: po2json($lang, $po_file);
|
||||||
|
$js_file = "$root_dir_path/public_html/translations/$lang.js";
|
||||||
|
if(!$fd = fopen($js_file, 'w')) {
|
||||||
|
Log :: error(_("Fail to open %s JSON catalog file in write mode (%s)."),
|
||||||
|
$lang, $js_file);
|
||||||
|
$error = True;
|
||||||
|
}
|
||||||
|
elseif (fwrite($fd, sprintf("translations_data = %s;", $json_catalog)) === false) {
|
||||||
|
Log :: error(_("Fail to write %s JSON catalog in file (%s)."),
|
||||||
|
$lang, $js_file);
|
||||||
|
$error = True;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Log :: info(_("%s JSON catalog writed (%s)."), $lang, $js_file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
closedir($dh);
|
||||||
|
|
||||||
|
return !$error;
|
||||||
|
}
|
||||||
|
Log :: fatal(_("Fail to open root lang directory (%s)."), $root_dir_path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
# vim: tabstop=2 shiftwidth=2 softtabstop=2 expandtab
|
36
src/Log.php
36
src/Log.php
|
@ -66,19 +66,8 @@ class Log {
|
||||||
public static function init($filepath, $level=null, $php_errors_levels=null) {
|
public static function init($filepath, $level=null, $php_errors_levels=null) {
|
||||||
self :: $filepath = $filepath;
|
self :: $filepath = $filepath;
|
||||||
|
|
||||||
// Set default log level (if not defined or invalid)
|
// Set log level
|
||||||
if (is_null($level)) {
|
self :: set_level($level);
|
||||||
self :: $level = self :: $default_level;
|
|
||||||
}
|
|
||||||
elseif (!array_key_exists($level, self :: $levels)) {
|
|
||||||
self :: $level = self :: $default_level;
|
|
||||||
self :: warning(
|
|
||||||
"Invalid log level value found in configuration (%s). ".
|
|
||||||
"Set as default (%s).", $level, self :: $default_level);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
self :: $level = $level;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log PHP errors
|
// Log PHP errors
|
||||||
if (!is_null($php_errors_levels))
|
if (!is_null($php_errors_levels))
|
||||||
|
@ -258,6 +247,27 @@ class Log {
|
||||||
return True;
|
return True;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set current log level
|
||||||
|
* @param string $level The new log level to set
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function set_level($level=null) {
|
||||||
|
// Set default log level (if not defined or invalid)
|
||||||
|
if (is_null($level)) {
|
||||||
|
self :: $level = self :: $default_level;
|
||||||
|
}
|
||||||
|
elseif (!array_key_exists($level, self :: $levels)) {
|
||||||
|
self :: $level = self :: $default_level;
|
||||||
|
self :: warning(
|
||||||
|
"Invalid log level value found in configuration (%s). ".
|
||||||
|
"Set as default (%s).", $level, self :: $default_level);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
self :: $level = $level;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
*******************************************************************************
|
*******************************************************************************
|
||||||
* Handle exception logging
|
* Handle exception logging
|
||||||
|
|
85
src/Session.php
Normal file
85
src/Session.php
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace EesyPHP;
|
||||||
|
|
||||||
|
use EesyPHP\Log;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Session management
|
||||||
|
*/
|
||||||
|
class Session {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Session max duration (in seconds, default: 12h)
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
protected static int $max_duration = 12 * 60 * 60;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialization
|
||||||
|
* @param int|null $max_duration Session max duration in second
|
||||||
|
* (optional, default: 12h)
|
||||||
|
* @param int|null $timeout Session inactivity timeout in second
|
||||||
|
* (optional, default: no timeout)
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function init($max_duration=null, $timeout=null) {
|
||||||
|
if (php_sapi_name() == "cli")
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Define session max duration
|
||||||
|
if (is_int($max_duration))
|
||||||
|
self :: $max_duration = $max_duration;
|
||||||
|
|
||||||
|
ini_set('session.gc_maxlifetime', strval(self :: $max_duration));
|
||||||
|
ini_set('session.cookie_lifetime', strval(self :: $max_duration));
|
||||||
|
|
||||||
|
// Start session
|
||||||
|
session_start();
|
||||||
|
|
||||||
|
// Init session key
|
||||||
|
if (!isset($_SESSION['session_key'])) {
|
||||||
|
$_SESSION['session_key'] = uniqid();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle session timeout
|
||||||
|
if ($timeout) {
|
||||||
|
if (!isset($_SESSION['session_last_access'])) {
|
||||||
|
Log :: debug('Set initial session last access');
|
||||||
|
$_SESSION['session_last_access'] = time();
|
||||||
|
}
|
||||||
|
elseif ($_SESSION['session_last_access'] > (time() - $timeout)) {
|
||||||
|
Log :: debug(
|
||||||
|
'Session timeout not expired, update session last access '.
|
||||||
|
'(Previous value : %d', $_SESSION['session_last_access']);
|
||||||
|
$_SESSION['session_last_access'] = time();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Log :: info('Session destroyed due to inactivity');
|
||||||
|
session_destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check session key
|
||||||
|
* @param string|null $value The value of the session key to check
|
||||||
|
* (optional, default: $_REQUEST['session_key'])
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function check_key($value=null) {
|
||||||
|
if (is_null($value) && isset($_REQUEST['session_key']))
|
||||||
|
$value = $_REQUEST['session_key'];
|
||||||
|
return ($value && $_SESSION['session_key'] == $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (php_sapi_name() == "cli")
|
||||||
|
return true;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# vim: tabstop=2 shiftwidth=2 softtabstop=2 expandtab
|
Loading…
Reference in a new issue