3 use phpline\console\ConsoleReader
;
4 use phpline\internal\Log
;
5 use phpline\TerminalFactory
;
6 use wcf\system\cli\command\CLICommandHandler
;
7 use wcf\system\cli\command\CLICommandNameCompleter
;
8 use wcf\system\cli\DatabaseCLICommandHistory
;
9 use wcf\system\event\EventHandler
;
10 use wcf\system\exception\IllegalLinkException
;
11 use wcf\system\exception\PermissionDeniedException
;
12 use wcf\system\exception\UserInputException
;
13 use wcf\system\language\LanguageFactory
;
14 use wcf\system\package\PackageUpdateDispatcher
;
15 use wcf\system\user\authentication\UserAuthenticationFactory
;
17 use wcf\util\FileUtil
;
19 use wcf\util\StringUtil
;
20 use Zend\Console\Exception\RuntimeException
as ArgvException
;
21 use Zend\Console\Getopt
as ArgvParser
;
22 use Zend\Loader\StandardAutoloader
as ZendLoader
;
24 // set exception handler
25 set_exception_handler([CLIWCF
::class, 'handleCLIException']);
28 * Extends WCF class with functions for CLI.
30 * @author Tim Duesterhus
31 * @copyright 2001-2018 WoltLab GmbH
32 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
33 * @package WoltLabSuite\Core\System
35 class CLIWCF
extends WCF
{
37 * instance of ConsoleReader
40 protected static $consoleReader = null;
43 * instance of ArgvParser
44 * @var \Zend\Console\Getopt
46 protected static $argvParser = null;
48 /** @noinspection PhpMissingParentConstructorInspection */
50 * Calls all init functions of the WCF class.
52 public function __construct() {
53 // add autoload directory
54 self
::$autoloadDirectories['wcf'] = WCF_DIR
. 'lib/';
56 // define tmp directory
57 if (!defined('TMP_DIR')) define('TMP_DIR', FileUtil
::getTempFolder());
59 // register additional autoloaders
60 require_once(WCF_DIR
.'lib/system/api/phpline/phpline.phar');
61 require_once(WCF_DIR
.'lib/system/api/zend/Loader/StandardAutoloader.php');
62 $zendLoader = new ZendLoader([ZendLoader
::AUTOREGISTER_ZF
=> true]);
63 $zendLoader->register();
65 $argv = new ArgvParser([
68 $argv->setOption(ArgvParser
::CONFIG_FREEFORM_FLAGS
, true);
70 define('PACKAGE_ID', $argv->packageID ?
: 1);
73 define('ENABLE_BENCHMARK', 0);
75 // start initialization
79 $this->initLanguage();
81 $this->initCoreObjects();
82 $this->initApplications();
84 // the destructor registered in core.functions.php will only call the destructor of the parent class
85 register_shutdown_function([self
::class, 'destruct']);
90 $this->checkForUpdates();
91 $this->initCommands();
97 public static function destruct() {
98 if (self
::getReader() !== null && self
::getReader()->getHistory() instanceof DatabaseCLICommandHistory
) {
99 /** @var DatabaseCLICommandHistory $history */
100 $history = self
::getReader()->getHistory();
103 $history->autoSave
= false;
106 self
::getSession()->delete();
112 public static final function handleCLIException(\Exception
$e) {
113 die($e->getMessage()."\n".$e->getTraceAsString());
117 * Initializes parsing of command line options.
119 protected function initArgv() {
120 // initialise ArgvParser
121 self
::$argvParser = new ArgvParser([
122 'language=s' => WCF
::getLanguage()->get('wcf.cli.help.language'),
123 'v' => WCF
::getLanguage()->get('wcf.cli.help.v'),
124 'q' => WCF
::getLanguage()->get('wcf.cli.help.q'),
125 'h|help-s' => WCF
::getLanguage()->get('wcf.cli.help.help'),
126 'version' => WCF
::getLanguage()->get('wcf.cli.help.version'),
127 'disableUpdateCheck' => WCF
::getLanguage()->get('wcf.cli.help.disableUpdateCheck'),
128 'exitOnFail' => WCF
::getLanguage()->get('wcf.cli.help.exitOnFail'),
129 'packageID=i' => WCF
::getLanguage()->get('wcf.cli.help.packageID')
131 self
::getArgvParser()->setOptions([
132 ArgvParser
::CONFIG_CUMULATIVE_FLAGS
=> true,
133 ArgvParser
::CONFIG_DASHDASH
=> false
137 EventHandler
::getInstance()->fireAction($this, 'beforeArgumentParsing');
139 self
::getArgvParser()->parse();
141 catch (ArgvException
$e) {
142 // show error message and usage
143 echo $e->getMessage().PHP_EOL
;
144 echo self
::getArgvParser()->getUsageMessage();
147 EventHandler
::getInstance()->fireAction($this, 'afterArgumentParsing');
150 if (self
::getArgvParser()->help
=== true) {
152 echo self
::getArgvParser()->getUsageMessage();
155 else if (self
::getArgvParser()->help
) {
156 $help = WCF
::getLanguage()->get('wcf.cli.help.'.self
::getArgvParser()->help
.'.description', true);
157 if ($help) echo $help.PHP_EOL
;
159 echo WCF
::getLanguage()->getDynamicVariable('wcf.cli.help.noLongHelp', ['topic' => self
::getArgvParser()->help
]).PHP_EOL
;
163 if (self
::getArgvParser()->version
) {
165 echo WCF_VERSION
.PHP_EOL
;
168 if (self
::getArgvParser()->language
) {
170 $language = LanguageFactory
::getInstance()->getLanguageByCode(self
::getArgvParser()->language
);
171 if ($language === null) {
172 echo WCF
::getLanguage()->getDynamicVariable('wcf.cli.error.language.notFound', ['languageCode' => self
::getArgvParser()->language
]).PHP_EOL
;
175 self
::setLanguage($language->languageID
);
177 if (in_array('moo', self
::getArgvParser()->getRemainingArgs())) {
178 echo '...."Have you mooed today?"...'.PHP_EOL
;
181 define('VERBOSITY', self
::getArgvParser()->v
- self
::getArgvParser()->q
);
185 * Returns the argv parser.
187 * @return \Zend\Console\Getopt
189 public static function getArgvParser() {
190 return self
::$argvParser;
194 * Initializes PHPLine.
196 protected function initPHPLine() {
197 $terminal = TerminalFactory
::get();
198 self
::$consoleReader = new ConsoleReader("WoltLab Suite", null, null, $terminal);
200 // don't expand events, as the username and password will follow
201 self
::getReader()->setExpandEvents(false);
203 if (VERBOSITY
>= 0) {
204 $headline = str_pad("WoltLab Suite (tm) ".WCF_VERSION
, self
::getTerminal()->getWidth(), " ", STR_PAD_BOTH
);
205 self
::getReader()->println($headline);
210 * Returns ConsoleReader.
212 * @return ConsoleReader
214 public static function getReader() {
215 return self
::$consoleReader;
219 * Returns the terminal that is attached to ConsoleReader
221 * @return \phpline\Terminal
223 public static function getTerminal() {
224 return self
::getReader()->getTerminal();
228 * Does the user authentification.
230 protected function initAuth() {
232 $line = self
::getReader()->readLine(WCF
::getLanguage()->get('wcf.user.username').'> ');
233 if ($line === null) exit;
234 $username = StringUtil
::trim($line);
236 while ($username === '');
239 $line = self
::getReader()->readLine(WCF
::getLanguage()->get('wcf.user.password').'> ', '*');
240 if ($line === null) exit;
241 $password = StringUtil
::trim($line);
243 while ($password === '');
245 // check credentials and switch user
247 $user = UserAuthenticationFactory
::getInstance()->getUserAuthentication()->loginManually($username, $password);
248 WCF
::getSession()->changeUser($user);
250 catch (UserInputException
$e) {
251 $message = WCF
::getLanguage()->getDynamicVariable('wcf.user.'.$e->getField().'.error.'.$e->getType(), ['username' => $username]);
252 self
::getReader()->println($message);
256 // initialize history
257 $history = new DatabaseCLICommandHistory();
259 self
::getReader()->setHistory($history);
261 // initialize language
262 if (!self
::getArgvParser()->language
) $this->initLanguage();
266 * Initializes command handling.
268 protected function initCommands() {
269 // add command name completer
270 self
::getReader()->addCompleter(new CLICommandNameCompleter());
273 // roll back open transactions of the previous command, as they are dangerous in a long living script
274 if (WCF
::getDB()->rollBackTransaction()) {
275 Log
::warn('Previous command had an open transaction.');
277 self
::getReader()->setHistoryEnabled(true);
278 $line = self
::getReader()->readLine('>');
279 if ($line === null) exit;
280 $line = StringUtil
::trim($line);
282 $command = CLICommandHandler
::getCommand($line);
283 $command->execute(CLICommandHandler
::getParameters($line));
285 catch (IllegalLinkException
$e) {
286 Log
::error('notFound:'.JSON
::encode(['command' => $line]));
287 self
::getReader()->println(WCF
::getLanguage()->getDynamicVariable('wcf.cli.error.command.notFound', ['command' => $line]));
289 if (self
::getArgvParser()->exitOnFail
) {
294 catch (PermissionDeniedException
$e) {
295 Log
::error('permissionDenied');
296 self
::getReader()->println(WCF
::getLanguage()->getDynamicVariable('wcf.page.error.permissionDenied'));
298 if (self
::getArgvParser()->exitOnFail
) {
303 catch (ArgvException
$e) {
304 // show error message and usage
305 if ($e->getMessage()) echo $e->getMessage().PHP_EOL
;
306 echo $e->getUsageMessage();
308 if (self
::getArgvParser()->exitOnFail
) {
313 catch (\Exception
$e) {
316 if (self
::getArgvParser()->exitOnFail
) {
325 * Checks for updates.
329 public function checkForUpdates() {
330 if (WCF
::getSession()->getPermission('admin.configuration.package.canUpdatePackage') && VERBOSITY
>= -1 && !self
::getArgvParser()->disableUpdateCheck
) {
331 $updates = PackageUpdateDispatcher
::getInstance()->getAvailableUpdates();
332 if (!empty($updates)) {
333 self
::getReader()->println(count($updates) . ' update' . (count($updates) > 1 ?
's are' : ' is') . ' available');
335 if (VERBOSITY
>= 1) {
338 WCF
::getLanguage()->get('wcf.acp.package.name'),
339 WCF
::getLanguage()->get('wcf.acp.package.version'),
340 WCF
::getLanguage()->get('wcf.acp.package.newVersion')
344 foreach ($updates as $update) {
346 WCF
::getLanguage()->get($update['packageName']),
347 $update['packageVersion'],
348 $update['version']['packageVersion']
354 self
::getReader()->println(CLIUtil
::generateTable($table));