2023-01-30 00:11:26 +01:00
|
|
|
<?php
|
|
|
|
|
|
|
|
namespace EesyPHP;
|
|
|
|
|
|
|
|
use Exception;
|
|
|
|
|
|
|
|
class Cli {
|
|
|
|
|
2023-02-28 01:13:43 +01:00
|
|
|
/**
|
|
|
|
* EesyPHP core mode
|
|
|
|
*/
|
|
|
|
private static $core_mode = false;
|
|
|
|
|
2023-02-26 23:48:53 +01:00
|
|
|
/**
|
|
|
|
* Initialize
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public static function init() {
|
2023-02-28 01:13:43 +01:00
|
|
|
Hook :: register('cli_set_core_mode', array('\\EesyPHP\\Cli', 'on_cli_set_core_mode'));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get/set core mode
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public static function core_mode($enable=null) {
|
|
|
|
if (!is_null($enable)) {
|
|
|
|
self :: $core_mode = boolval($enable);
|
|
|
|
Hook :: trigger('cli_set_core_mode', array('enabled' => self :: $core_mode));
|
|
|
|
}
|
|
|
|
return self :: $core_mode;
|
|
|
|
}
|
2023-02-28 01:15:11 +01:00
|
|
|
|
2023-02-28 01:13:43 +01:00
|
|
|
/**
|
|
|
|
* On CLI set core mode hook
|
|
|
|
* @param \EesyPHP\HookEvent $event
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public static function on_cli_set_core_mode($event) {
|
|
|
|
if ($event->enabled) {
|
|
|
|
self :: add_command(
|
|
|
|
'new_project',
|
|
|
|
array('\\EesyPHP\\Cli', 'cli_new_project'),
|
2023-03-22 18:12:43 +01:00
|
|
|
___("Create a new project using EesyPHP framework"),
|
2023-02-28 01:13:43 +01:00
|
|
|
null,
|
2023-03-22 18:12:43 +01:00
|
|
|
___(
|
2023-02-28 01:13:43 +01:00
|
|
|
"This command could be used to easily build the structure of a new project using the ".
|
|
|
|
"EesyPHP framework.")
|
|
|
|
);
|
|
|
|
}
|
2023-02-28 01:15:11 +01:00
|
|
|
else {
|
|
|
|
self :: add_command(
|
|
|
|
'serve',
|
|
|
|
array('\\EesyPHP\\Cli', 'cli_serve'),
|
2023-03-22 18:12:43 +01:00
|
|
|
___("Start the PHP built-in HTTP server to serve the application"),
|
2023-03-22 19:18:23 +01:00
|
|
|
'[-h] [-P -O path]',
|
2023-03-22 18:12:43 +01:00
|
|
|
___(
|
2023-03-22 19:18:23 +01:00
|
|
|
"This command could be used to start the PHP built-in HTTP server to serve
|
|
|
|
the application.
|
|
|
|
|
|
|
|
Additionnal parameters:
|
|
|
|
-P/--enable-profiler Enable Xdebug profiler
|
|
|
|
-O/--profiler-output [path] Xdebug profiler output directory path"
|
|
|
|
)
|
2023-02-28 01:15:11 +01:00
|
|
|
);
|
|
|
|
}
|
2023-02-26 23:48:53 +01:00
|
|
|
}
|
|
|
|
|
2023-01-30 00:11:26 +01:00
|
|
|
/**
|
|
|
|
* 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) {
|
2023-02-14 01:21:52 +01:00
|
|
|
Log :: error(I18n::_("The CLI command '%s' already exists."), $command);
|
2023-01-30 00:11:26 +01:00
|
|
|
return False;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!is_callable($handler)) {
|
2023-02-14 01:21:52 +01:00
|
|
|
Log :: error(I18n::_("The CLI command '%s' handler is not callable !"), $command);
|
2023-01-30 00:11:26 +01:00
|
|
|
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";
|
2023-02-14 01:21:52 +01:00
|
|
|
echo(I18n::_("Usage: %s [-h] [-qd] command\n", basename($argv[0])));
|
2023-03-03 10:44:13 +01:00
|
|
|
echo I18n::_(" -h Show this message\n");
|
|
|
|
echo I18n::_(" -q / -d Quiet/Debug mode\n");
|
|
|
|
echo I18n::_(" --trace Trace mode (the most verbose)\n");
|
|
|
|
echo I18n::_(" -l / --log-file Overwrite log file specified in configuration\n");
|
|
|
|
echo I18n::_(" -C / --console Enable log on console\n");
|
|
|
|
echo I18n::_(" command Command to run\n");
|
2023-01-30 00:11:26 +01:00
|
|
|
echo "\n";
|
2023-02-14 01:21:52 +01:00
|
|
|
echo I18n::_("Available commands:\n");
|
2023-01-30 00:11:26 +01:00
|
|
|
|
|
|
|
foreach (self :: $commands as $command => $info) {
|
|
|
|
if (self :: $command && $command != self :: $command)
|
|
|
|
continue;
|
|
|
|
echo (
|
|
|
|
" ".str_replace(
|
|
|
|
"\n", "\n ",
|
2023-02-14 01:21:52 +01:00
|
|
|
wordwrap("$command : ".I18n::_($info['short_desc'])))
|
2023-01-30 00:11:26 +01:00
|
|
|
."\n\n");
|
|
|
|
echo (
|
|
|
|
" ".basename($argv[0])." $command ".
|
2023-02-14 01:21:52 +01:00
|
|
|
($info['usage_args']?I18n::_($info['usage_args']):'').
|
2023-01-30 00:11:26 +01:00
|
|
|
"\n");
|
|
|
|
if ($info['long_desc']) {
|
|
|
|
if (is_array($info['long_desc'])) {
|
|
|
|
$lines = array();
|
|
|
|
foreach ($info['long_desc'] as $line)
|
2023-02-14 01:21:52 +01:00
|
|
|
$lines[] = I18n::_($line);
|
2023-01-30 00:11:26 +01:00
|
|
|
$info['long_desc'] = implode("\n", $lines);
|
|
|
|
}
|
|
|
|
else
|
2023-02-14 01:21:52 +01:00
|
|
|
$info['long_desc'] = I18n::_($info['long_desc']);
|
2023-01-30 00:11:26 +01:00
|
|
|
|
|
|
|
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)
|
2023-02-28 14:57:02 +01:00
|
|
|
* @param bool|null $core_mode Force enable/disable EesyPHP core mode (optional, default: false)
|
2023-01-30 00:11:26 +01:00
|
|
|
* @return void
|
|
|
|
*/
|
2023-02-28 14:57:02 +01:00
|
|
|
public static function handle_args($args=null, $core_mode=false) {
|
2023-01-30 00:11:26 +01:00
|
|
|
global $argv;
|
2023-02-28 01:13:43 +01:00
|
|
|
self :: core_mode($core_mode);
|
2023-01-30 00:11:26 +01:00
|
|
|
$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
|
2023-02-14 01:21:52 +01:00
|
|
|
self :: usage(I18n::_("Only one command could be executed !"));
|
2023-01-30 00:11:26 +01:00
|
|
|
}
|
|
|
|
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;
|
2023-03-03 10:44:13 +01:00
|
|
|
case '-l':
|
|
|
|
case '--log-file':
|
|
|
|
$i++;
|
|
|
|
Log :: change_filepath($args[$i]);
|
|
|
|
break;
|
|
|
|
case '-C':
|
|
|
|
case '--console':
|
|
|
|
App :: set('log.cli_console', true);
|
|
|
|
break;
|
2023-01-30 00:11:26 +01:00
|
|
|
default:
|
|
|
|
if (self :: $command)
|
|
|
|
$command_args[] = $args[$i];
|
|
|
|
else
|
|
|
|
self :: usage(
|
2023-02-14 01:21:52 +01:00
|
|
|
I18n::_(
|
2023-01-30 00:11:26 +01:00
|
|
|
"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) {
|
2023-02-14 01:21:52 +01:00
|
|
|
Log :: exception($e, I18n::_("An exception occured running command %s"), self :: $command);
|
2023-01-30 00:11:26 +01:00
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-26 23:48:53 +01:00
|
|
|
/**
|
|
|
|
* Command to create new project based on EesyPHP framework
|
|
|
|
*
|
|
|
|
* @param array $command_args The command arguments
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public static function cli_new_project($command_args) {
|
|
|
|
echo "This CLI tool permit to initialize a new project using the EesyPHP framework.\n";
|
|
|
|
readline('[Press enter to continue]');
|
|
|
|
echo "\n";
|
|
|
|
|
|
|
|
$root_path = null;
|
|
|
|
while (!$root_path) {
|
|
|
|
$root_path = getcwd();
|
|
|
|
$input = readline("Please enter the root directory of your new project [$root_path]: ");
|
|
|
|
if (empty($input))
|
|
|
|
break;
|
|
|
|
if (!is_dir($input))
|
|
|
|
echo "Invalid root directory specified: not found or is not a directory\n";
|
|
|
|
else if (!is_writeable($input))
|
|
|
|
echo "Invalid root directory specified: not writeable\n";
|
|
|
|
else
|
|
|
|
$root_path = $input;
|
|
|
|
}
|
|
|
|
$root_path = rtrim($root_path, "/");
|
|
|
|
$skel = __DIR__ . "/../skel";
|
|
|
|
|
|
|
|
foreach (
|
|
|
|
$iterator = new \RecursiveIteratorIterator(
|
|
|
|
new \RecursiveDirectoryIterator($skel, \RecursiveDirectoryIterator::SKIP_DOTS),
|
|
|
|
\RecursiveIteratorIterator::SELF_FIRST
|
|
|
|
) as $item
|
|
|
|
) {
|
|
|
|
$path = "$root_path/".$iterator->getSubPathname();
|
|
|
|
if ($item->isDir()) {
|
|
|
|
if (!mkdir($path))
|
|
|
|
die("Fail to create $path directory\n");
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
if (!copy($item, $path))
|
|
|
|
die("Fail to copy file to $path\n");
|
|
|
|
}
|
2023-03-05 00:45:18 +01:00
|
|
|
if (!chmod($path, $item->getPerms()))
|
|
|
|
die("Fail to chmod file $path\n");
|
2023-02-26 23:48:53 +01:00
|
|
|
}
|
|
|
|
echo "done. Start coding!\n";
|
|
|
|
}
|
|
|
|
|
2023-02-28 01:15:11 +01:00
|
|
|
/**
|
|
|
|
* Command to start PHP built-in HTTP server to serve the EesyPHP project
|
|
|
|
*
|
|
|
|
* @param array $command_args The command arguments
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public static function cli_serve($command_args) {
|
2023-03-22 19:18:23 +01:00
|
|
|
$listen_address = null;
|
|
|
|
$enable_profiler = false;
|
|
|
|
$profiler_output_dir = realpath(getcwd());
|
|
|
|
for($i=0; $i < count($command_args); $i++) {
|
|
|
|
switch($command_args[$i]) {
|
|
|
|
case '-P':
|
|
|
|
case '--enable-profiler':
|
|
|
|
if (phpversion('xdebug') === false) {
|
|
|
|
self :: usage(I18n::_("The PHP XDEBUG extension is missing."));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
$enable_profiler = true;
|
|
|
|
break;
|
|
|
|
case '-O':
|
|
|
|
case '--profiler-output':
|
|
|
|
$i++;
|
|
|
|
$profiler_output_dir = $command_args[$i];
|
|
|
|
if (!is_dir($profiler_output_dir)) {
|
|
|
|
self :: usage(
|
|
|
|
I18n::_('Invalid profiler output directory "%s": not found'),
|
|
|
|
$profiler_output_dir
|
|
|
|
);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (!is_writable($profiler_output_dir)) {
|
|
|
|
self :: usage(
|
|
|
|
I18n::_('Invalid profiler output directory "%s": not writeable'),
|
|
|
|
$profiler_output_dir
|
|
|
|
);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
if (is_null($listen_address)) {
|
|
|
|
$i++;
|
|
|
|
$listen_address = $command_args[$i];
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
self :: usage(I18n::_('Invalid parameter "%s"', $command_args[$i]));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2023-02-28 01:15:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Check listen address
|
2023-03-22 19:18:23 +01:00
|
|
|
if (is_null($listen_address))
|
|
|
|
$listen_address = '127.0.0.1:8000';
|
2023-02-28 01:15:11 +01:00
|
|
|
$parts = explode(':', $listen_address);
|
|
|
|
if (count($parts) != 2) {
|
|
|
|
self :: usage(
|
|
|
|
I18n::_('Invalid listen address specify. Must be in formart host:port (or :port).')
|
|
|
|
);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (empty($parts[0])) {
|
|
|
|
$parts[0] = '0.0.0.0';
|
|
|
|
}
|
|
|
|
else if (!Check::ip_address($parts[0])) {
|
|
|
|
self :: usage(
|
|
|
|
I18n::_('Invalid listen host specified. Must be an IPv4 or IPv6 address.')
|
|
|
|
);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!Check::tcp_or_udp_port($parts[1])) {
|
|
|
|
self :: usage(
|
|
|
|
I18n::_('Invalid listen port specified. Must be a positive integer between 1 and 65535.')
|
|
|
|
);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
$listen_address = implode(':', $parts);
|
|
|
|
|
|
|
|
$public_html = App::get('root_directory_path')."/public_html";
|
|
|
|
chdir($public_html) or die(
|
|
|
|
sprintf(
|
|
|
|
'Fail to enter in the public_html directory of the application (%s).',
|
|
|
|
$public_html
|
|
|
|
)
|
|
|
|
);
|
2023-03-22 19:18:23 +01:00
|
|
|
$args = array(
|
|
|
|
"-S", $listen_address,
|
|
|
|
);
|
|
|
|
if ($enable_profiler) {
|
|
|
|
$args = array_merge(
|
|
|
|
$args,
|
|
|
|
array(
|
|
|
|
"-d", "xdebug.mode=profile",
|
|
|
|
"-d", "xdebug.profiler_enable=On",
|
|
|
|
"-d", "xdebug.profiler_output_dir=$profiler_output_dir",
|
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
2023-02-28 01:15:11 +01:00
|
|
|
passthru(
|
2023-03-22 19:18:23 +01:00
|
|
|
"EESYPHP_SERVE_URL=http://$listen_address ".PHP_BINARY." ".
|
|
|
|
implode(' ', array_map('escapeshellarg', $args))." index.php",
|
2023-02-28 01:15:11 +01:00
|
|
|
$exit_code
|
|
|
|
);
|
|
|
|
exit($exit_code);
|
|
|
|
}
|
|
|
|
|
2023-02-28 01:44:43 +01:00
|
|
|
/**
|
|
|
|
* Helper method to ask user to enter a password
|
|
|
|
* Note: this method use the bash binary and a fatal error will be trigger if it's not available.
|
|
|
|
* @param string|null $prompt The prompt message (optional)
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
public static function prompt_for_password($prompt=null) {
|
|
|
|
// Check bash is available
|
|
|
|
$command = "/usr/bin/env bash -c 'echo OK'";
|
|
|
|
if (rtrim(shell_exec($command)) !== 'OK')
|
|
|
|
Log::fatal(I18n::_("Can't invoke bash. Can't ask password prompt."));
|
|
|
|
|
|
|
|
$command = "/usr/bin/env bash -c 'read -s -p \"";
|
|
|
|
$command .= addslashes($prompt?_($prompt):I18n::_("Please enter password:"));
|
|
|
|
$command .= "\" mypassword && echo \$mypassword'";
|
|
|
|
$password = rtrim(shell_exec($command));
|
|
|
|
echo "\n";
|
|
|
|
return $password;
|
|
|
|
}
|
|
|
|
|
2023-01-30 00:11:26 +01:00
|
|
|
}
|