2 declare(strict_types
=1);
4 use phpline\console\ConsoleReader
;
5 use phpline\internal\Log
;
6 use phpline\TerminalFactory
;
7 use wcf\system\cli\command\CLICommandHandler
;
8 use wcf\system\cli\command\CLICommandNameCompleter
;
9 use wcf\system\cli\DatabaseCLICommandHistory
;
10 use wcf\system\event\EventHandler
;
11 use wcf\system\exception\IllegalLinkException
;
12 use wcf\system\exception\PermissionDeniedException
;
13 use wcf\system\exception\UserInputException
;
14 use wcf\system\language\LanguageFactory
;
15 use wcf\system\package\PackageUpdateDispatcher
;
16 use wcf\system\user\authentication\UserAuthenticationFactory
;
18 use wcf\util\FileUtil
;
20 use wcf\util\StringUtil
;
21 use Zend\Console\Exception\RuntimeException
as ArgvException
;
22 use Zend\Console\Getopt
as ArgvParser
;
23 use Zend\Loader\StandardAutoloader
as ZendLoader
;
25 // set exception handler
26 set_exception_handler([CLIWCF
::class, 'handleCLIException']);
29 * Extends WCF class with functions for CLI.
31 * @author Tim Duesterhus
32 * @copyright 2001-2018 WoltLab GmbH
33 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
34 * @package WoltLabSuite\Core\System
36 class CLIWCF
extends WCF
{
38 * instance of ConsoleReader
41 protected static $consoleReader = null;
44 * instance of ArgvParser
45 * @var \Zend\Console\Getopt
47 protected static $argvParser = null;
49 /** @noinspection PhpMissingParentConstructorInspection */
51 * Calls all init functions of the WCF class.
53 public function __construct() {
54 // add autoload directory
55 self
::$autoloadDirectories['wcf'] = WCF_DIR
. 'lib/';
57 // define tmp directory
58 if (!defined('TMP_DIR')) define('TMP_DIR', FileUtil
::getTempFolder());
60 // register additional autoloaders
61 require_once(WCF_DIR
.'lib/system/api/phpline/phpline.phar');
62 require_once(WCF_DIR
.'lib/system/api/zend/Loader/StandardAutoloader.php');
63 $zendLoader = new ZendLoader([ZendLoader
::AUTOREGISTER_ZF
=> true]);
64 $zendLoader->register();
66 $argv = new ArgvParser([
69 $argv->setOption(ArgvParser
::CONFIG_FREEFORM_FLAGS
, true);
71 define('PACKAGE_ID', $argv->packageID ?
: 1);
74 define('ENABLE_BENCHMARK', 0);
76 // start initialization
80 $this->initLanguage();
82 $this->initCoreObjects();
83 $this->initApplications();
85 // the destructor registered in core.functions.php will only call the destructor of the parent class
86 register_shutdown_function([self
::class, 'destruct']);
91 $this->checkForUpdates();
92 $this->initCommands();
98 public static function destruct() {
99 if (self
::getReader() !== null && self
::getReader()->getHistory() instanceof DatabaseCLICommandHistory
) {
100 /** @var DatabaseCLICommandHistory $history */
101 $history = self
::getReader()->getHistory();
104 $history->autoSave
= false;
107 self
::getSession()->delete();
113 public static final function handleCLIException(\Exception
$e) {
114 die($e->getMessage()."\n".$e->getTraceAsString());
118 * Initializes parsing of command line options.
120 protected function initArgv() {
121 // initialise ArgvParser
122 self
::$argvParser = new ArgvParser([
123 'language=s' => WCF
::getLanguage()->get('wcf.cli.help.language'),
124 'v' => WCF
::getLanguage()->get('wcf.cli.help.v'),
125 'q' => WCF
::getLanguage()->get('wcf.cli.help.q'),
126 'h|help-s' => WCF
::getLanguage()->get('wcf.cli.help.help'),
127 'version' => WCF
::getLanguage()->get('wcf.cli.help.version'),
128 'disableUpdateCheck' => WCF
::getLanguage()->get('wcf.cli.help.disableUpdateCheck'),
129 'exitOnFail' => WCF
::getLanguage()->get('wcf.cli.help.exitOnFail'),
130 'packageID=i' => WCF
::getLanguage()->get('wcf.cli.help.packageID')
132 self
::getArgvParser()->setOptions([
133 ArgvParser
::CONFIG_CUMULATIVE_FLAGS
=> true,
134 ArgvParser
::CONFIG_DASHDASH
=> false
138 EventHandler
::getInstance()->fireAction($this, 'beforeArgumentParsing');
140 self
::getArgvParser()->parse();
142 catch (ArgvException
$e) {
143 // show error message and usage
144 echo $e->getMessage().PHP_EOL
;
145 echo self
::getArgvParser()->getUsageMessage();
148 EventHandler
::getInstance()->fireAction($this, 'afterArgumentParsing');
151 if (self
::getArgvParser()->help
=== true) {
153 echo self
::getArgvParser()->getUsageMessage();
156 else if (self
::getArgvParser()->help
) {
157 $help = WCF
::getLanguage()->get('wcf.cli.help.'.self
::getArgvParser()->help
.'.description', true);
158 if ($help) echo $help.PHP_EOL
;
160 echo WCF
::getLanguage()->getDynamicVariable('wcf.cli.help.noLongHelp', ['topic' => self
::getArgvParser()->help
]).PHP_EOL
;
164 if (self
::getArgvParser()->version
) {
166 echo WCF_VERSION
.PHP_EOL
;
169 if (self
::getArgvParser()->language
) {
171 $language = LanguageFactory
::getInstance()->getLanguageByCode(self
::getArgvParser()->language
);
172 if ($language === null) {
173 echo WCF
::getLanguage()->getDynamicVariable('wcf.cli.error.language.notFound', ['languageCode' => self
::getArgvParser()->language
]).PHP_EOL
;
176 self
::setLanguage($language->languageID
);
178 if (in_array('moo', self
::getArgvParser()->getRemainingArgs())) {
179 echo '...."Have you mooed today?"...'.PHP_EOL
;
182 define('VERBOSITY', self
::getArgvParser()->v
- self
::getArgvParser()->q
);
186 * Returns the argv parser.
188 * @return \Zend\Console\Getopt
190 public static function getArgvParser() {
191 return self
::$argvParser;
195 * Initializes PHPLine.
197 protected function initPHPLine() {
198 $terminal = TerminalFactory
::get();
199 self
::$consoleReader = new ConsoleReader("WoltLab Suite", null, null, $terminal);
201 // don't expand events, as the username and password will follow
202 self
::getReader()->setExpandEvents(false);
204 if (VERBOSITY
>= 0) {
205 $headline = str_pad("WoltLab Suite (tm) ".WCF_VERSION
, self
::getTerminal()->getWidth(), " ", STR_PAD_BOTH
);
206 self
::getReader()->println($headline);
211 * Returns ConsoleReader.
213 * @return ConsoleReader
215 public static function getReader() {
216 return self
::$consoleReader;
220 * Returns the terminal that is attached to ConsoleReader
222 * @return \phpline\Terminal
224 public static function getTerminal() {
225 return self
::getReader()->getTerminal();
229 * Does the user authentification.
231 protected function initAuth() {
233 $line = self
::getReader()->readLine(WCF
::getLanguage()->get('wcf.user.username').'> ');
234 if ($line === null) exit;
235 $username = StringUtil
::trim($line);
237 while ($username === '');
240 $line = self
::getReader()->readLine(WCF
::getLanguage()->get('wcf.user.password').'> ', '*');
241 if ($line === null) exit;
242 $password = StringUtil
::trim($line);
244 while ($password === '');
246 // check credentials and switch user
248 $user = UserAuthenticationFactory
::getInstance()->getUserAuthentication()->loginManually($username, $password);
249 WCF
::getSession()->changeUser($user);
251 catch (UserInputException
$e) {
252 $message = WCF
::getLanguage()->getDynamicVariable('wcf.user.'.$e->getField().'.error.'.$e->getType(), ['username' => $username]);
253 self
::getReader()->println($message);
257 // initialize history
258 $history = new DatabaseCLICommandHistory();
260 self
::getReader()->setHistory($history);
262 // initialize language
263 if (!self
::getArgvParser()->language
) $this->initLanguage();
267 * Initializes command handling.
269 protected function initCommands() {
270 // add command name completer
271 self
::getReader()->addCompleter(new CLICommandNameCompleter());
274 // roll back open transactions of the previous command, as they are dangerous in a long living script
275 if (WCF
::getDB()->rollBackTransaction()) {
276 Log
::warn('Previous command had an open transaction.');
278 self
::getReader()->setHistoryEnabled(true);
279 $line = self
::getReader()->readLine('>');
280 if ($line === null) exit;
281 $line = StringUtil
::trim($line);
283 $command = CLICommandHandler
::getCommand($line);
284 $command->execute(CLICommandHandler
::getParameters($line));
286 catch (IllegalLinkException
$e) {
287 Log
::error('notFound:'.JSON
::encode(['command' => $line]));
288 self
::getReader()->println(WCF
::getLanguage()->getDynamicVariable('wcf.cli.error.command.notFound', ['command' => $line]));
290 if (self
::getArgvParser()->exitOnFail
) {
295 catch (PermissionDeniedException
$e) {
296 Log
::error('permissionDenied');
297 self
::getReader()->println(WCF
::getLanguage()->getDynamicVariable('wcf.page.error.permissionDenied'));
299 if (self
::getArgvParser()->exitOnFail
) {
304 catch (ArgvException
$e) {
305 // show error message and usage
306 if ($e->getMessage()) echo $e->getMessage().PHP_EOL
;
307 echo $e->getUsageMessage();
309 if (self
::getArgvParser()->exitOnFail
) {
314 catch (\Exception
$e) {
317 if (self
::getArgvParser()->exitOnFail
) {
326 * Checks for updates.
330 public function checkForUpdates() {
331 if (WCF
::getSession()->getPermission('admin.configuration.package.canUpdatePackage') && VERBOSITY
>= -1 && !self
::getArgvParser()->disableUpdateCheck
) {
332 $updates = PackageUpdateDispatcher
::getInstance()->getAvailableUpdates();
333 if (!empty($updates)) {
334 self
::getReader()->println(count($updates) . ' update' . (count($updates) > 1 ?
's are' : ' is') . ' available');
336 if (VERBOSITY
>= 1) {
339 WCF
::getLanguage()->get('wcf.acp.package.name'),
340 WCF
::getLanguage()->get('wcf.acp.package.version'),
341 WCF
::getLanguage()->get('wcf.acp.package.newVersion')
345 foreach ($updates as $update) {
347 WCF
::getLanguage()->get($update['packageName']),
348 $update['packageVersion'],
349 $update['version']['packageVersion']
355 self
::getReader()->println(CLIUtil
::generateTable($table));