Merge branch '3.0'
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / lib / system / CLIWCF.class.php
CommitLineData
054db725
TD
1<?php
2namespace wcf\system;
054db725 3use phpline\console\ConsoleReader;
b158b3ce 4use phpline\internal\Log;
054db725 5use phpline\TerminalFactory;
b35ebcd2
MS
6use wcf\system\cli\command\CLICommandHandler;
7use wcf\system\cli\command\CLICommandNameCompleter;
8use wcf\system\cli\DatabaseCLICommandHistory;
ec85c193 9use wcf\system\event\EventHandler;
f9e31606 10use wcf\system\exception\IllegalLinkException;
fccfd98c 11use wcf\system\exception\PermissionDeniedException;
054db725 12use wcf\system\exception\UserInputException;
e6e05ba3 13use wcf\system\language\LanguageFactory;
ccc1b3cb 14use wcf\system\package\PackageUpdateDispatcher;
054db725 15use wcf\system\user\authentication\UserAuthenticationFactory;
36586e26 16use wcf\util\CLIUtil;
93cfe146 17use wcf\util\FileUtil;
9814a16e 18use wcf\util\JSON;
054db725 19use wcf\util\StringUtil;
ec85c193 20use Zend\Console\Exception\RuntimeException as ArgvException;
24989d35 21use Zend\Console\Getopt as ArgvParser;
054db725
TD
22use Zend\Loader\StandardAutoloader as ZendLoader;
23
34d66f55 24// set exception handler
157054c9 25set_exception_handler([CLIWCF::class, 'handleCLIException']);
34d66f55 26
054db725
TD
27/**
28 * Extends WCF class with functions for CLI.
54a8f7b5
MS
29 *
30 * @author Tim Duesterhus
c839bd49 31 * @copyright 2001-2018 WoltLab GmbH
054db725 32 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
e71525e4 33 * @package WoltLabSuite\Core\System
054db725
TD
34 */
35class CLIWCF extends WCF {
36 /**
37 * instance of ConsoleReader
4e25add7 38 * @var ConsoleReader
054db725
TD
39 */
40 protected static $consoleReader = null;
41
ec85c193
TD
42 /**
43 * instance of ArgvParser
0ad90fc3 44 * @var \Zend\Console\Getopt
ec85c193
TD
45 */
46 protected static $argvParser = null;
47
e4bda351 48 /** @noinspection PhpMissingParentConstructorInspection */
054db725
TD
49 /**
50 * Calls all init functions of the WCF class.
51 */
52 public function __construct() {
34d66f55
MW
53 // add autoload directory
54 self::$autoloadDirectories['wcf'] = WCF_DIR . 'lib/';
55
56 // define tmp directory
57 if (!defined('TMP_DIR')) define('TMP_DIR', FileUtil::getTempFolder());
58
054db725
TD
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');
058cbd6a 62 $zendLoader = new ZendLoader([ZendLoader::AUTOREGISTER_ZF => true]);
054db725
TD
63 $zendLoader->register();
64
058cbd6a 65 $argv = new ArgvParser([
4509c17f 66 'packageID=i' => ''
058cbd6a 67 ]);
4509c17f
TD
68 $argv->setOption(ArgvParser::CONFIG_FREEFORM_FLAGS, true);
69 $argv->parse();
70 define('PACKAGE_ID', $argv->packageID ?: 1);
71
ad92e033
MS
72 // disable benchmark
73 define('ENABLE_BENCHMARK', 0);
74
34d66f55
MW
75 // start initialization
76 $this->initDB();
77 $this->loadOptions();
78 $this->initSession();
79 $this->initLanguage();
80 $this->initTPL();
81 $this->initCoreObjects();
82 $this->initApplications();
4509c17f
TD
83
84 // the destructor registered in core.functions.php will only call the destructor of the parent class
157054c9 85 register_shutdown_function([self::class, 'destruct']);
4509c17f 86
24989d35 87 $this->initArgv();
054db725
TD
88 $this->initPHPLine();
89 $this->initAuth();
6699bf2b 90 $this->checkForUpdates();
e6e05ba3 91 $this->initCommands();
054db725
TD
92 }
93
09fcd319 94 /**
0fcfe5f6 95 * @inheritDoc
09fcd319
TD
96 */
97 public static function destruct() {
b35ebcd2 98 if (self::getReader() !== null && self::getReader()->getHistory() instanceof DatabaseCLICommandHistory) {
fc52a845
MS
99 /** @var DatabaseCLICommandHistory $history */
100 $history = self::getReader()->getHistory();
101
102 $history->save();
103 $history->autoSave = false;
4be0d7cd 104 }
30f1abe1 105
09fcd319
TD
106 self::getSession()->delete();
107 }
108
34d66f55 109 /**
0fcfe5f6 110 * @inheritDoc
34d66f55
MW
111 */
112 public static final function handleCLIException(\Exception $e) {
113 die($e->getMessage()."\n".$e->getTraceAsString());
114 }
115
24989d35
TD
116 /**
117 * Initializes parsing of command line options.
118 */
119 protected function initArgv() {
698af0cd 120 // initialise ArgvParser
058cbd6a 121 self::$argvParser = new ArgvParser([
698af0cd
TD
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'),
f74e8fc2 125 'h|help-s' => WCF::getLanguage()->get('wcf.cli.help.help'),
e6088ac0 126 'version' => WCF::getLanguage()->get('wcf.cli.help.version'),
5c0b40be 127 'disableUpdateCheck' => WCF::getLanguage()->get('wcf.cli.help.disableUpdateCheck'),
4509c17f
TD
128 'exitOnFail' => WCF::getLanguage()->get('wcf.cli.help.exitOnFail'),
129 'packageID=i' => WCF::getLanguage()->get('wcf.cli.help.packageID')
058cbd6a
MS
130 ]);
131 self::getArgvParser()->setOptions([
e6e05ba3 132 ArgvParser::CONFIG_CUMULATIVE_FLAGS => true,
4e67fb7f 133 ArgvParser::CONFIG_DASHDASH => false
058cbd6a 134 ]);
ec85c193 135
e6e05ba3 136 // parse arguments
ec85c193
TD
137 EventHandler::getInstance()->fireAction($this, 'beforeArgumentParsing');
138 try {
139 self::getArgvParser()->parse();
140 }
141 catch (ArgvException $e) {
698af0cd
TD
142 // show error message and usage
143 echo $e->getMessage().PHP_EOL;
b158b3ce 144 echo self::getArgvParser()->getUsageMessage();
ec85c193
TD
145 exit;
146 }
147 EventHandler::getInstance()->fireAction($this, 'afterArgumentParsing');
148
e6e05ba3 149 // handle arguments
f74e8fc2 150 if (self::getArgvParser()->help === true) {
698af0cd 151 // show usage
ec85c193 152 echo self::getArgvParser()->getUsageMessage();
24989d35
TD
153 exit;
154 }
f74e8fc2
TD
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;
158 else {
058cbd6a 159 echo WCF::getLanguage()->getDynamicVariable('wcf.cli.help.noLongHelp', ['topic' => self::getArgvParser()->help]).PHP_EOL;
f74e8fc2
TD
160 }
161 exit;
162 }
e6e05ba3 163 if (self::getArgvParser()->version) {
698af0cd
TD
164 // show version
165 echo WCF_VERSION.PHP_EOL;
e6e05ba3
TD
166 exit;
167 }
425e0458 168 if (self::getArgvParser()->language) {
698af0cd 169 // set language
425e0458 170 $language = LanguageFactory::getInstance()->getLanguageByCode(self::getArgvParser()->language);
e6e05ba3 171 if ($language === null) {
058cbd6a 172 echo WCF::getLanguage()->getDynamicVariable('wcf.cli.error.language.notFound', ['languageCode' => self::getArgvParser()->language]).PHP_EOL;
e6e05ba3
TD
173 exit;
174 }
175 self::setLanguage($language->languageID);
176 }
b158b3ce 177 if (in_array('moo', self::getArgvParser()->getRemainingArgs())) {
698af0cd 178 echo '...."Have you mooed today?"...'.PHP_EOL;
b158b3ce
TD
179 }
180
e6e05ba3 181 define('VERBOSITY', self::getArgvParser()->v - self::getArgvParser()->q);
24989d35
TD
182 }
183
ec85c193
TD
184 /**
185 * Returns the argv parser.
186 *
0ad90fc3 187 * @return \Zend\Console\Getopt
ec85c193
TD
188 */
189 public static function getArgvParser() {
190 return self::$argvParser;
191 }
192
054db725
TD
193 /**
194 * Initializes PHPLine.
195 */
196 protected function initPHPLine() {
197 $terminal = TerminalFactory::get();
e71525e4 198 self::$consoleReader = new ConsoleReader("WoltLab Suite", null, null, $terminal);
698af0cd
TD
199
200 // don't expand events, as the username and password will follow
054db725
TD
201 self::getReader()->setExpandEvents(false);
202
becd43f4 203 if (VERBOSITY >= 0) {
e71525e4 204 $headline = str_pad("WoltLab Suite (tm) ".WCF_VERSION, self::getTerminal()->getWidth(), " ", STR_PAD_BOTH);
698af0cd
TD
205 self::getReader()->println($headline);
206 }
054db725
TD
207 }
208
209 /**
210 * Returns ConsoleReader.
211 *
4e25add7 212 * @return ConsoleReader
054db725 213 */
f5368a65 214 public static function getReader() {
054db725
TD
215 return self::$consoleReader;
216 }
217
218 /**
219 * Returns the terminal that is attached to ConsoleReader
220 *
0ad90fc3 221 * @return \phpline\Terminal
054db725 222 */
f5368a65 223 public static function getTerminal() {
054db725
TD
224 return self::getReader()->getTerminal();
225 }
226
054db725
TD
227 /**
228 * Does the user authentification.
229 */
230 protected function initAuth() {
231 do {
fccfd98c
TD
232 $line = self::getReader()->readLine(WCF::getLanguage()->get('wcf.user.username').'> ');
233 if ($line === null) exit;
234 $username = StringUtil::trim($line);
054db725
TD
235 }
236 while ($username === '');
fccfd98c 237
054db725 238 do {
fccfd98c
TD
239 $line = self::getReader()->readLine(WCF::getLanguage()->get('wcf.user.password').'> ', '*');
240 if ($line === null) exit;
241 $password = StringUtil::trim($line);
054db725
TD
242 }
243 while ($password === '');
244
080db197 245 // check credentials and switch user
054db725 246 try {
30f1abe1 247 $user = UserAuthenticationFactory::getInstance()->getUserAuthentication()->loginManually($username, $password);
054db725
TD
248 WCF::getSession()->changeUser($user);
249 }
250 catch (UserInputException $e) {
058cbd6a 251 $message = WCF::getLanguage()->getDynamicVariable('wcf.user.'.$e->getField().'.error.'.$e->getType(), ['username' => $username]);
daf5682f 252 self::getReader()->println($message);
f74e8fc2 253 exit(1);
054db725
TD
254 }
255
425e0458 256 // initialize history
b35ebcd2 257 $history = new DatabaseCLICommandHistory();
054db725
TD
258 $history->load();
259 self::getReader()->setHistory($history);
425e0458
TD
260
261 // initialize language
262 if (!self::getArgvParser()->language) $this->initLanguage();
e6e05ba3
TD
263 }
264
265 /**
266 * Initializes command handling.
267 */
268 protected function initCommands() {
698af0cd 269 // add command name completer
b35ebcd2 270 self::getReader()->addCompleter(new CLICommandNameCompleter());
698af0cd 271
f9e31606 272 while (true) {
c781eb27
TD
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.');
276 }
f9fb83ee 277 self::getReader()->setHistoryEnabled(true);
fccfd98c
TD
278 $line = self::getReader()->readLine('>');
279 if ($line === null) exit;
280 $line = StringUtil::trim($line);
f9e31606 281 try {
b35ebcd2
MS
282 $command = CLICommandHandler::getCommand($line);
283 $command->execute(CLICommandHandler::getParameters($line));
f9e31606
TD
284 }
285 catch (IllegalLinkException $e) {
058cbd6a
MS
286 Log::error('notFound:'.JSON::encode(['command' => $line]));
287 self::getReader()->println(WCF::getLanguage()->getDynamicVariable('wcf.cli.error.command.notFound', ['command' => $line]));
5c0b40be
TD
288
289 if (self::getArgvParser()->exitOnFail) {
290 exit(1);
291 }
f9e31606
TD
292 continue;
293 }
fccfd98c 294 catch (PermissionDeniedException $e) {
f9fb83ee 295 Log::error('permissionDenied');
31c199eb 296 self::getReader()->println(WCF::getLanguage()->getDynamicVariable('wcf.page.error.permissionDenied'));
5c0b40be
TD
297
298 if (self::getArgvParser()->exitOnFail) {
299 exit(1);
300 }
fccfd98c
TD
301 continue;
302 }
f5368a65 303 catch (ArgvException $e) {
698af0cd 304 // show error message and usage
4e67fb7f 305 if ($e->getMessage()) echo $e->getMessage().PHP_EOL;
3ff2d0da 306 echo $e->getUsageMessage();
5c0b40be
TD
307
308 if (self::getArgvParser()->exitOnFail) {
309 exit(1);
310 }
f5368a65
TD
311 continue;
312 }
72d5f76f
TD
313 catch (\Exception $e) {
314 Log::error($e);
5c0b40be
TD
315
316 if (self::getArgvParser()->exitOnFail) {
317 exit(1);
318 }
72d5f76f
TD
319 continue;
320 }
f5368a65
TD
321 }
322 }
323
6699bf2b
TD
324 /**
325 * Checks for updates.
326 *
327 * @return string
328 */
329 public function checkForUpdates() {
6476e7a1 330 if (WCF::getSession()->getPermission('admin.configuration.package.canUpdatePackage') && VERBOSITY >= -1 && !self::getArgvParser()->disableUpdateCheck) {
30f1abe1 331 $updates = PackageUpdateDispatcher::getInstance()->getAvailableUpdates();
6699bf2b 332 if (!empty($updates)) {
fbe0c349 333 self::getReader()->println(count($updates) . ' update' . (count($updates) > 1 ? 's are' : ' is') . ' available');
6699bf2b
TD
334
335 if (VERBOSITY >= 1) {
058cbd6a
MS
336 $table = [
337 [
6699bf2b
TD
338 WCF::getLanguage()->get('wcf.acp.package.name'),
339 WCF::getLanguage()->get('wcf.acp.package.version'),
340 WCF::getLanguage()->get('wcf.acp.package.newVersion')
058cbd6a
MS
341 ]
342 ];
6699bf2b 343
6699bf2b 344 foreach ($updates as $update) {
058cbd6a 345 $row = [
6699bf2b
TD
346 WCF::getLanguage()->get($update['packageName']),
347 $update['packageVersion'],
348 $update['version']['packageVersion']
058cbd6a 349 ];
6699bf2b 350
6699bf2b
TD
351 $table[] = $row;
352 }
353
36586e26 354 self::getReader()->println(CLIUtil::generateTable($table));
6699bf2b
TD
355 }
356 }
357 }
358 }
054db725 359}