diff --git a/appinfo/info.xml b/appinfo/info.xml index 5fa0c22..ad05434 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -5,7 +5,7 @@ OCC Web OCC Commands in a web terminal - 0.0.2 + 0.0.3 agpl Adphi OCCWeb diff --git a/lib/Controller/OCCApplication.php b/lib/Controller/OCCApplication.php new file mode 100644 index 0000000..7acca97 --- /dev/null +++ b/lib/Controller/OCCApplication.php @@ -0,0 +1,225 @@ + + * @author Jörn Friedrich Dreyer + * @author Lukas Reschke + * @author Miha Frangez + * @author Morris Jobke + * @author noveens + * @author Robin Appelman + * @author Thomas Müller + * @author Victor Dubiniuk + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ +namespace OCA\OCCWeb\Controller; + +use OC\MemoryInfo; +use OC\NeedsUpdateException; +use OC_App; +use OCP\AppFramework\QueryException; +use OCP\Console\ConsoleEvent; +use OCP\IConfig; +use OCP\ILogger; +use Symfony\Component\Console\Application as SymfonyApplication; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; + +class OCCApplication { + /** @var IConfig */ + private $config; + /** @var EventDispatcherInterface */ + private $dispatcher; + /** @var ILogger */ + private $logger; + /** @var MemoryInfo */ + private $memoryInfo; + + /** + * @param IConfig $config + * @param EventDispatcherInterface $dispatcher + * @param ILogger $logger + * @param MemoryInfo $memoryInfo + */ + public function __construct(IConfig $config, + EventDispatcherInterface $dispatcher, + ILogger $logger, + MemoryInfo $memoryInfo) { + $defaults = \OC::$server->getThemingDefaults(); + $this->config = $config; + $this->application = new SymfonyApplication($defaults->getName(), \OC_Util::getVersionString()); + $this->dispatcher = $dispatcher; + $this->logger = $logger; + $this->memoryInfo = $memoryInfo; + } + + /** + * @param InputInterface $input + * @param ConsoleOutputInterface $output + * @throws \Exception + */ + public function loadCommands( + InputInterface $input, + ConsoleOutputInterface $output + ) { + // $application is required to be defined in the register_command scripts + $application = $this->application; + $inputDefinition = $application->getDefinition(); + $inputDefinition->addOption( + new InputOption( + 'no-warnings', + null, + InputOption::VALUE_NONE, + 'Skip global warnings, show command output only', + null + ) + ); + try { + $input->bind($inputDefinition); + } catch (\RuntimeException $e) { + //expected if there are extra options + } + if ($input->getOption('no-warnings')) { + $output->setVerbosity(OutputInterface::VERBOSITY_QUIET); + } + + if ($this->memoryInfo->isMemoryLimitSufficient() === false) { + $output->getErrorOutput()->writeln( + 'The current PHP memory limit ' . + 'is below the recommended value of 512MB.' + ); + } + + try { + require_once __DIR__ . '/../../../../core/register_command.php'; + if ($this->config->getSystemValue('installed', false)) { + if (\OCP\Util::needUpgrade()) { + throw new NeedsUpdateException(); + } elseif ($this->config->getSystemValue('maintenance', false)) { + $this->writeMaintenanceModeInfo($input, $output); + } else { + OC_App::loadApps(); + foreach (\OC::$server->getAppManager()->getInstalledApps() as $app) { + $appPath = \OC_App::getAppPath($app); + if ($appPath === false) { + continue; + } + // load commands using info.xml + $info = \OC_App::getAppInfo($app); + if (isset($info['commands'])) { + $this->loadCommandsFromInfoXml($info['commands']); + } + // load from register_command.php + \OC_App::registerAutoloading($app, $appPath); + $file = $appPath . '/appinfo/register_command.php'; + if (file_exists($file)) { + try { + require $file; + } catch (\Exception $e) { + $this->logger->logException($e); + } + } + } + } + } else if ($input->getArgument('command') !== '_completion' && $input->getArgument('command') !== 'maintenance:install') { + $output->writeln("Nextcloud is not installed - only a limited number of commands are available"); + } + } catch(NeedsUpdateException $e) { + if ($input->getArgument('command') !== '_completion') { + $output->writeln("Nextcloud or one of the apps require upgrade - only a limited number of commands are available"); + $output->writeln("You may use your browser or the occ upgrade command to do the upgrade"); + } + } + + if ($input->getFirstArgument() !== 'check') { + $errors = \OC_Util::checkServer(\OC::$server->getSystemConfig()); + if (!empty($errors)) { + foreach ($errors as $error) { + $output->writeln((string)$error['error']); + $output->writeln((string)$error['hint']); + $output->writeln(''); + } + throw new \Exception("Environment not properly prepared."); + } + } + } + + /** + * Write a maintenance mode info. + * The commands "_completion" and "maintenance:mode" are excluded. + * + * @param InputInterface $input The input implementation for reading inputs. + * @param ConsoleOutputInterface $output The output implementation + * for writing outputs. + * @return void + */ + private function writeMaintenanceModeInfo( + InputInterface $input, ConsoleOutputInterface $output + ) { + if ($input->getArgument('command') !== '_completion' + && $input->getArgument('command') !== 'maintenance:mode') { + $errOutput = $output->getErrorOutput(); + $errOutput->writeln( + 'Nextcloud is in maintenance mode - ' . + 'no apps have been loaded' . PHP_EOL + ); + } + } + + /** + * Sets whether to automatically exit after a command execution or not. + * + * @param bool $boolean Whether to automatically exit after a command execution or not + */ + public function setAutoExit($boolean) { + $this->application->setAutoExit($boolean); + } + + /** + * @param InputInterface $input + * @param OutputInterface $output + * @return int + * @throws \Exception + */ + public function run(InputInterface $input = null, OutputInterface $output = null) { + $this->dispatcher->dispatch(ConsoleEvent::EVENT_RUN, new ConsoleEvent( + ConsoleEvent::EVENT_RUN, + ['occ'] + )); + return $this->application->run($input, $output); + } + + private function loadCommandsFromInfoXml($commands) { + foreach ($commands as $command) { + try { + $c = \OC::$server->query($command); + } catch (QueryException $e) { + if (class_exists($command)) { + $c = new $command(); + } else { + throw new \Exception("Console command '$command' is unknown and could not be loaded"); + } + } + + $this->application->add($c); + } + } +} diff --git a/lib/Controller/OCCController.php b/lib/Controller/OCCController.php index 8dbc800..3a51589 100644 --- a/lib/Controller/OCCController.php +++ b/lib/Controller/OCCController.php @@ -2,7 +2,6 @@ namespace OCA\OCCWeb\Controller; -use OC\Console\Application; use OCP\IRequest; use OCP\AppFramework\Http\TemplateResponse; use OCP\AppFramework\Http\DataResponse; @@ -11,7 +10,7 @@ use OCP\ILogger; use Symfony\Component\Console\Input\StringInput; use Symfony\Component\Console\Output\OutputInterface; -class OccController extends Controller implements IRequest +class OccController extends Controller { private $logger; private $userId; @@ -30,10 +29,9 @@ class OccController extends Controller implements IRequest $this->server = array( 'argv' => ["occ"], ); - $this->application = new Application( + $this->application = new OCCApplication( \OC::$server->getConfig(), \OC::$server->getEventDispatcher(), - $this, \OC::$server->getLogger(), \OC::$server->query(\OC\MemoryInfo::class) ); @@ -95,264 +93,6 @@ class OccController extends Controller implements IRequest } return new DataResponse($cmds); } - - /** - * Lets you access post and get parameters by the index - * In case of json requests the encoded json body is accessed - * - * @param string $key the key which you want to access in the URL Parameter - * placeholder, $_POST or $_GET array. - * The priority how they're returned is the following: - * 1. URL parameters - * 2. POST parameters - * 3. GET parameters - * @param mixed $default If the key is not found, this value will be returned - * @return mixed the content of the array - * @since 6.0.0 - */ - public function getParam(string $key, $default = null) - { - // TODO: Implement getParam() method. - } - - /** - * Returns all params that were received, be it from the request - * - * (as GET or POST) or through the URL by the route - * - * @return array the array with all parameters - * @since 6.0.0 - */ - public function getParams(): array - { - // TODO: Implement getParams() method. - } - - /** - * Returns the method of the request - * - * @return string the method of the request (POST, GET, etc) - * @since 6.0.0 - */ - public function getMethod(): string - { - // TODO: Implement getMethod() method. - } - - /** - * Shortcut for accessing an uploaded file through the $_FILES array - * - * @param string $key the key that will be taken from the $_FILES array - * @return array the file in the $_FILES element - * @since 6.0.0 - */ - public function getUploadedFile(string $key) - { - // TODO: Implement getUploadedFile() method. - } - - /** - * Shortcut for getting env variables - * - * @param string $key the key that will be taken from the $_ENV array - * @return array the value in the $_ENV element - * @since 6.0.0 - */ - public function getEnv(string $key) - { - // TODO: Implement getEnv() method. - } - - /** - * Shortcut for getting cookie variables - * - * @param string $key the key that will be taken from the $_COOKIE array - * @return string|null the value in the $_COOKIE element - * @since 6.0.0 - */ - public function getCookie(string $key) - { - // TODO: Implement getCookie() method. - } - - /** - * Checks if the CSRF check was correct - * - * @return bool true if CSRF check passed - * @since 6.0.0 - */ - public function passesCSRFCheck(): bool - { - // TODO: Implement passesCSRFCheck() method. - } - - /** - * Checks if the strict cookie has been sent with the request if the request - * is including any cookies. - * - * @return bool - * @since 9.0.0 - */ - public function passesStrictCookieCheck(): bool - { - // TODO: Implement passesStrictCookieCheck() method. - } - - /** - * Checks if the lax cookie has been sent with the request if the request - * is including any cookies. - * - * @return bool - * @since 9.0.0 - */ - public function passesLaxCookieCheck(): bool - { - // TODO: Implement passesLaxCookieCheck() method. - } - - /** - * Returns an ID for the request, value is not guaranteed to be unique and is mostly meant for logging - * If `mod_unique_id` is installed this value will be taken. - * - * @return string - * @since 8.1.0 - */ - public function getId(): string - { - // TODO: Implement getId() method. - } - - /** - * Returns the remote address, if the connection came from a trusted proxy - * and `forwarded_for_headers` has been configured then the IP address - * specified in this header will be returned instead. - * Do always use this instead of $_SERVER['REMOTE_ADDR'] - * - * @return string IP address - * @since 8.1.0 - */ - public function getRemoteAddress(): string - { - // TODO: Implement getRemoteAddress() method. - } - - /** - * Returns the server protocol. It respects reverse proxy servers and load - * balancers. - * - * @return string Server protocol (http or https) - * @since 8.1.0 - */ - public function getServerProtocol(): string - { - // TODO: Implement getServerProtocol() method. - } - - /** - * Returns the used HTTP protocol. - * - * @return string HTTP protocol. HTTP/2, HTTP/1.1 or HTTP/1.0. - * @since 8.2.0 - */ - public function getHttpProtocol(): string - { - // TODO: Implement getHttpProtocol() method. - } - - /** - * Returns the request uri, even if the website uses one or more - * reverse proxies - * - * @return string - * @since 8.1.0 - */ - public function getRequestUri(): string - { - // TODO: Implement getRequestUri() method. - } - - /** - * Get raw PathInfo from request (not urldecoded) - * - * @throws \Exception - * @return string Path info - * @since 8.1.0 - */ - public function getRawPathInfo(): string - { - // TODO: Implement getRawPathInfo() method. - } - - /** - * Get PathInfo from request - * - * @throws \Exception - * @return string|false Path info or false when not found - * @since 8.1.0 - */ - public function getPathInfo() - { - // TODO: Implement getPathInfo() method. - } - - /** - * Returns the script name, even if the website uses one or more - * reverse proxies - * - * @return string the script name - * @since 8.1.0 - */ - public function getScriptName(): string - { - // TODO: Implement getScriptName() method. - } - - /** - * Checks whether the user agent matches a given regex - * - * @param array $agent array of agent names - * @return bool true if at least one of the given agent matches, false otherwise - * @since 8.1.0 - */ - public function isUserAgent(array $agent): bool - { - // TODO: Implement isUserAgent() method. - } - - /** - * Returns the unverified server host from the headers without checking - * whether it is a trusted domain - * - * @return string Server host - * @since 8.1.0 - */ - public function getInsecureServerHost(): string - { - // TODO: Implement getInsecureServerHost() method. - } - - /** - * Returns the server host from the headers, or the first configured - * trusted domain if the host isn't in the trusted list - * - * @return string Server host - * @since 8.1.0 - */ - public function getServerHost(): string - { - // TODO: Implement getServerHost() method. - } - - /** - * @param string $name - * - * @return string - * @since 6.0.0 - */ - public function getHeader(string $name): string - { - // TODO: Implement getHeader() method. - } } function exceptionHandler($exception)