From 18d73b23afd9cd4f2492698f661540c1e0d39b6b Mon Sep 17 00:00:00 2001 From: Holger Schmidt Date: Wed, 12 Oct 2016 17:08:58 +1100 Subject: [PATCH 1/2] added [-wp|--runInWebProcess] option to yii running the command in the web application's process --- controllers/DefaultController.php | 56 +++++++++++++++++++++++++++++-- views/default/index.php | 16 +++++---- 2 files changed, 64 insertions(+), 8 deletions(-) diff --git a/controllers/DefaultController.php b/controllers/DefaultController.php index 204e512..a2038f3 100644 --- a/controllers/DefaultController.php +++ b/controllers/DefaultController.php @@ -50,11 +50,63 @@ public function actionRpc() switch ($options['method']) { case 'yii': - list ($status, $output) = $this->runConsole(implode(' ', $options['params'])); - return ['result' => $output]; + $exitCode = 1; + $output = null; + $params = explode(' ', $options['params']); + + if (!empty($params) and in_array($params[0], ['-wp', '--runInWebProcess'])) { + array_shift($params); //remove runInWebProcess option parameter + + if (empty($params)) + $params[] = 'help'; //just typing 'yii' in console defaults to 'yii help' + + list($exitCode, $output) = $this->runYiiConsoleCommandsInWebProcess($params); + } + else + list ($exitCode, $output) = $this->runConsole(implode(' ', $params)); + + return [ + 'exitCode' => $exitCode, + 'output' => $output + ]; } } + // popen might be disabled for security reasons in the php.ini. + // this is common on shared hosting. + // read http://www.cyberciti.biz/faq/linux-unix-apache-lighttpd-phpini-disable-functions/ + // refer to http://php.net/manual/en/ini.core.php#ini.disable-functions + private function runYiiConsoleCommandsInWebProcess(array $requestParams) + { + //remember current web app + // inspired by https://github.com/tebazil/yii2-console-runner/blob/master/src/ConsoleCommandRunner.php + $webApp = Yii::$app; + + try { + $request = new \yii\console\Request; + $request->setParams($requestParams); + list ($route, $params) = $request->resolve(); + + define('STDOUT', fopen('php://memory', 'w+')); + define('STDERR', STDOUT); + $stdout = STDOUT; + ob_start(); + Yii::$app = new \yii\console\Application(require(Yii::getAlias('@app/config/console.php'))); + $exitCode = Yii::$app->runAction($route, $params); + rewind(STDOUT); + $output = stream_get_contents(STDOUT); + $output .= ob_get_clean(); + fclose(STDOUT); + } + catch (\Exception $ex) { + $output = $webApp->errorHandler->convertExceptionToString($ex); + $exitCode = 1; + } + + Yii::$app = $webApp; + return [$exitCode, $output]; + } + /** * Runs console command * diff --git a/views/default/index.php b/views/default/index.php index 7a76de6..f7b7c8a 100644 --- a/views/default/index.php +++ b/views/default/index.php @@ -18,17 +18,21 @@ webshell.terminal( function(command, term) { if (command.indexOf('yii') === 0 || command.indexOf('yii') === 3) { - $.jrpc('{$endpoint}', 'yii', [command.replace(/^yii ?/, '')], function(json) { - term.echo(json.result); + $.jrpc('{$endpoint}', 'yii', command.replace(/^yii ?/, ''), function(json) { + term.echo(json.output); scrollDown(); }); } else if (command === 'help') { term.echo('Available commands are:'); term.echo(''); - term.echo("clear\tClear console"); - term.echo('help\tThis help text'); - term.echo('yii\tyii command'); - term.echo('quit\tQuit web shell'); + term.echo('help\t This help text'); + term.echo('clear\tClear console'); + term.echo('quit\t Quit web shell'); + term.echo('yii [-wp|--runInWebProcess] '); + term.echo('\tcommand \t\t\t\t A yii console command. Type "yii help" to list available commands.'); + term.echo('\t-wp|--runInWebProcess\tRun command in the web application process.'); + term.echo('\t\t\t\t\t\t\t Does not require creating a background process via "popen",'); + term.echo('\t\t\t\t\t\t\t a function which might be disabled in your PHP config.'); scrollDown(); } else if (command === 'quit') { var exitUrl = '{$quitUrl}'; From 78ad8e023230d42d29425e2d9bcbfbac764b46bf Mon Sep 17 00:00:00 2001 From: Holger Schmidt Date: Fri, 14 Oct 2016 21:37:56 +1100 Subject: [PATCH 2/2] commented code and refactored config file path into module option --- Module.php | 7 +++++++ controllers/DefaultController.php | 26 +++++++++++++++++--------- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/Module.php b/Module.php index ec526e9..99d447b 100644 --- a/Module.php +++ b/Module.php @@ -45,6 +45,13 @@ class Module extends \yii\base\Module */ public $yiiScript = '@app/yii'; + /** + * @var string path to the yii console app configuration file + * used to run the yii command in the same process as the web application's + * if so chosen. + */ + public $consoleConfig = '@app/config/console.php'; + /** * @var array the list of IPs that are allowed to access this module. * Each array element represents a single IP filter which can be either an IP address diff --git a/controllers/DefaultController.php b/controllers/DefaultController.php index a2038f3..216cea3 100644 --- a/controllers/DefaultController.php +++ b/controllers/DefaultController.php @@ -55,10 +55,12 @@ public function actionRpc() $params = explode(' ', $options['params']); if (!empty($params) and in_array($params[0], ['-wp', '--runInWebProcess'])) { - array_shift($params); //remove runInWebProcess option parameter + array_shift($params); // remove runInWebProcess option parameter + // mimicking yii command behaviour + // just typing 'yii' in console defaults to 'yii help' if (empty($params)) - $params[] = 'help'; //just typing 'yii' in console defaults to 'yii help' + $params[] = 'help'; list($exitCode, $output) = $this->runYiiConsoleCommandsInWebProcess($params); } @@ -78,24 +80,30 @@ public function actionRpc() // refer to http://php.net/manual/en/ini.core.php#ini.disable-functions private function runYiiConsoleCommandsInWebProcess(array $requestParams) { - //remember current web app + // remember current web app // inspired by https://github.com/tebazil/yii2-console-runner/blob/master/src/ConsoleCommandRunner.php $webApp = Yii::$app; try { + //use console request to resolve command and arguments $request = new \yii\console\Request; $request->setParams($requestParams); list ($route, $params) = $request->resolve(); + /* redefine STDOUT and STDERR streams + to write to the memory stream which is readable. + this might cause a PHP Notice because of global constant + redefinition if they're previously defined or this method + is called mor than once per HTTP request. */ define('STDOUT', fopen('php://memory', 'w+')); define('STDERR', STDOUT); - $stdout = STDOUT; - ob_start(); - Yii::$app = new \yii\console\Application(require(Yii::getAlias('@app/config/console.php'))); + ob_start(); // aditionally buffer output, echo() and printf() write to this + // set app context + Yii::$app = new \yii\console\Application(require(Yii::getAlias($this->module->consoleConfig))); $exitCode = Yii::$app->runAction($route, $params); rewind(STDOUT); - $output = stream_get_contents(STDOUT); - $output .= ob_get_clean(); + $output = stream_get_contents(STDOUT); // whatever has been written to STDOUT and STDERR + $output .= ob_get_clean(); // and what has been echoed and printed fclose(STDOUT); } catch (\Exception $ex) { @@ -103,7 +111,7 @@ private function runYiiConsoleCommandsInWebProcess(array $requestParams) $exitCode = 1; } - Yii::$app = $webApp; + Yii::$app = $webApp; // revert to web app context return [$exitCode, $output]; }