Commit | Line | Data |
---|---|---|
054db725 TD |
1 | <?php |
2 | namespace wcf\system; | |
054db725 | 3 | use phpline\console\ConsoleReader; |
b158b3ce | 4 | use phpline\internal\Log; |
054db725 | 5 | use phpline\TerminalFactory; |
b35ebcd2 MS |
6 | use wcf\system\cli\command\CLICommandHandler; |
7 | use wcf\system\cli\command\CLICommandNameCompleter; | |
8 | use wcf\system\cli\DatabaseCLICommandHistory; | |
ec85c193 | 9 | use wcf\system\event\EventHandler; |
f9e31606 | 10 | use wcf\system\exception\IllegalLinkException; |
fccfd98c | 11 | use wcf\system\exception\PermissionDeniedException; |
054db725 | 12 | use wcf\system\exception\UserInputException; |
e6e05ba3 | 13 | use wcf\system\language\LanguageFactory; |
ccc1b3cb | 14 | use wcf\system\package\PackageUpdateDispatcher; |
054db725 | 15 | use wcf\system\user\authentication\UserAuthenticationFactory; |
36586e26 | 16 | use wcf\util\CLIUtil; |
93cfe146 | 17 | use wcf\util\FileUtil; |
9814a16e | 18 | use wcf\util\JSON; |
054db725 | 19 | use wcf\util\StringUtil; |
ec85c193 | 20 | use Zend\Console\Exception\RuntimeException as ArgvException; |
24989d35 | 21 | use Zend\Console\Getopt as ArgvParser; |
054db725 TD |
22 | use Zend\Loader\StandardAutoloader as ZendLoader; |
23 | ||
34d66f55 | 24 | // set exception handler |
157054c9 | 25 | set_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 | */ |
35 | class 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 | } |