Merge pull request #5989 from WoltLab/wsc-rpc-api-const
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / lib / system / CLIWCF.class.php
CommitLineData
054db725 1<?php
a9229942 2
054db725 3namespace wcf\system;
a9229942 4
054db725 5use phpline\console\ConsoleReader;
24e2bbb4 6use phpline\console\history\MemoryHistory;
b158b3ce 7use phpline\internal\Log;
054db725 8use phpline\TerminalFactory;
acd9f010 9use wcf\data\session\SessionEditor;
b35ebcd2 10use wcf\system\cli\command\CLICommandHandler;
f9e31606 11use wcf\system\exception\IllegalLinkException;
fccfd98c 12use wcf\system\exception\PermissionDeniedException;
054db725
TD
13use wcf\system\exception\UserInputException;
14use wcf\system\user\authentication\UserAuthenticationFactory;
93cfe146 15use wcf\util\FileUtil;
9814a16e 16use wcf\util\JSON;
054db725 17use wcf\util\StringUtil;
ec85c193 18use Zend\Console\Exception\RuntimeException as ArgvException;
24989d35 19use Zend\Console\Getopt as ArgvParser;
054db725
TD
20use Zend\Loader\StandardAutoloader as ZendLoader;
21
82d72850
TD
22// phpcs:disable PSR1.Files.SideEffects
23
34d66f55 24// set exception handler
a9229942 25\set_exception_handler([CLIWCF::class, 'handleCLIException']);
34d66f55 26
054db725
TD
27/**
28 * Extends WCF class with functions for CLI.
a9229942
TD
29 *
30 * @author Tim Duesterhus
31 * @copyright 2001-2020 WoltLab GmbH
32 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
054db725 33 */
a9229942
TD
34class CLIWCF extends WCF
35{
36 /**
37 * instance of ConsoleReader
38 * @var ConsoleReader
39 */
55a3a842 40 protected static $consoleReader;
a9229942
TD
41
42 /**
43 * instance of ArgvParser
44 * @var \Zend\Console\Getopt
45 */
55a3a842 46 protected static $argvParser;
a9229942
TD
47
48 /** @noinspection PhpMissingParentConstructorInspection */
49
50 /**
51 * Calls all init functions of the WCF class.
52 */
53 public function __construct()
54 {
a9229942
TD
55 // define tmp directory
56 if (!\defined('TMP_DIR')) {
57 \define('TMP_DIR', FileUtil::getTempFolder());
58 }
59
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();
65
66 // disable benchmark
67 \define('ENABLE_BENCHMARK', 0);
68
69 // start initialization
70 $this->initDB();
71 $this->loadOptions();
72 $this->initSession();
73 $this->initLanguage();
74 $this->initTPL();
75 $this->initCoreObjects();
76 $this->initApplications();
77
6f77b358
TD
78 $this->runBootstrappers();
79
a9229942
TD
80 // the destructor registered in core.functions.php will only call the destructor of the parent class
81 \register_shutdown_function([self::class, 'destruct']);
82
83 $this->initArgv();
84 $this->initPHPLine();
85 $this->initAuth();
a9229942
TD
86 $this->initCommands();
87 }
88
89 /**
90 * @inheritDoc
91 */
62f1ff72 92 public static function destruct(): void
a9229942 93 {
a9229942
TD
94 if (empty($_ENV['WCF_SESSION_ID'])) {
95 self::getSession()->delete();
96 }
97 }
98
99 /**
100 * @inheritDoc
101 */
62f1ff72 102 final public static function handleCLIException($e): never
a9229942
TD
103 {
104 exit($e->getMessage() . "\n" . $e->getTraceAsString());
105 }
106
107 /**
108 * Initializes parsing of command line options.
109 */
62f1ff72 110 protected function initArgv(): void
a9229942
TD
111 {
112 // initialise ArgvParser
113 self::$argvParser = new ArgvParser([
a9229942
TD
114 'v' => WCF::getLanguage()->get('wcf.cli.help.v'),
115 'q' => WCF::getLanguage()->get('wcf.cli.help.q'),
0973244f 116 'h|help' => WCF::getLanguage()->get('wcf.cli.help.help'),
a9229942 117 'version' => WCF::getLanguage()->get('wcf.cli.help.version'),
a9229942
TD
118 'exitOnFail' => WCF::getLanguage()->get('wcf.cli.help.exitOnFail'),
119 ]);
120 self::getArgvParser()->setOptions([
121 ArgvParser::CONFIG_CUMULATIVE_FLAGS => true,
122 ArgvParser::CONFIG_DASHDASH => false,
123 ]);
124
a9229942
TD
125 try {
126 self::getArgvParser()->parse();
127 } catch (ArgvException $e) {
128 // show error message and usage
129 echo $e->getMessage() . \PHP_EOL;
130 echo self::getArgvParser()->getUsageMessage();
131
132 exit;
133 }
a9229942
TD
134
135 // handle arguments
0973244f 136 if (self::getArgvParser()->help) {
a9229942
TD
137 // show usage
138 echo self::getArgvParser()->getUsageMessage();
139
a9229942
TD
140 exit;
141 }
142 if (self::getArgvParser()->version) {
143 // show version
144 echo WCF_VERSION . \PHP_EOL;
145
146 exit;
147 }
a9229942
TD
148 if (\in_array('moo', self::getArgvParser()->getRemainingArgs())) {
149 echo '...."Have you mooed today?"...' . \PHP_EOL;
150 }
151
152 \define('VERBOSITY', self::getArgvParser()->v - self::getArgvParser()->q);
153 }
154
155 /**
156 * Returns the argv parser.
157 *
158 * @return \Zend\Console\Getopt
159 */
160 public static function getArgvParser()
161 {
162 return self::$argvParser;
163 }
164
165 /**
166 * Initializes PHPLine.
167 */
62f1ff72 168 protected function initPHPLine(): void
a9229942
TD
169 {
170 $terminal = TerminalFactory::get();
171 self::$consoleReader = new ConsoleReader("WoltLab Suite", null, null, $terminal);
172
173 // don't expand events, as the username and password will follow
174 self::getReader()->setExpandEvents(false);
175
176 if (VERBOSITY >= 0) {
177 $headline = \str_pad(
178 "WoltLab Suite (tm) " . WCF_VERSION,
179 self::getTerminal()->getWidth(),
180 " ",
181 \STR_PAD_BOTH
182 );
183 self::getReader()->println($headline);
184 }
185 }
186
187 /**
188 * Returns ConsoleReader.
189 *
190 * @return ConsoleReader
191 */
192 public static function getReader()
193 {
194 return self::$consoleReader;
195 }
196
197 /**
198 * Returns the terminal that is attached to ConsoleReader
199 *
200 * @return \phpline\Terminal
201 */
202 public static function getTerminal()
203 {
204 return self::getReader()->getTerminal();
205 }
206
207 /**
208 * Does the user authentification.
209 */
62f1ff72 210 protected function initAuth(): void
a9229942 211 {
24e2bbb4 212 self::getReader()->setHistoryEnabled(false);
a9229942
TD
213 if (!empty($_ENV['WCF_SESSION_ID'])) {
214 self::getSession()->delete();
215 self::getSession()->load(SessionEditor::class, $_ENV['WCF_SESSION_ID']);
216 if (!self::getUser()->userID) {
217 self::getReader()->println('Invalid sessionID');
218
219 exit(1);
220 }
221 } else {
222 do {
223 $line = self::getReader()->readLine(WCF::getLanguage()->get('wcf.user.username') . '> ');
224 if ($line === null) {
225 exit;
226 }
227 $username = StringUtil::trim($line);
228 } while ($username === '');
229
230 do {
231 $line = self::getReader()->readLine(WCF::getLanguage()->get('wcf.user.password') . '> ', '*');
232 if ($line === null) {
233 exit;
234 }
235 $password = StringUtil::trim($line);
236 } while ($password === '');
237
238 // check credentials and switch user
239 try {
240 $user = UserAuthenticationFactory::getInstance()->getUserAuthentication()->loginManually(
241 $username,
242 $password
243 );
244 WCF::getSession()->changeUser($user);
245 } catch (UserInputException $e) {
246 $message = WCF::getLanguage()->getDynamicVariable(
247 'wcf.user.' . $e->getField() . '.error.' . $e->getType(),
248 ['username' => $username]
249 );
250 self::getReader()->println($message);
251
252 exit(1);
253 }
254 }
24e2bbb4
TD
255 self::getReader()->setHistoryEnabled(true);
256 self::getReader()->setHistory(new MemoryHistory());
a9229942
TD
257
258 // initialize language
259 if (!self::getArgvParser()->language) {
260 $this->initLanguage();
261 }
262 }
263
264 /**
265 * Initializes command handling.
266 */
62f1ff72 267 protected function initCommands(): void
a9229942 268 {
997e237b
TD
269 // Workaround to load commands for CLICommandHandler::getCommand().
270 CLICommandHandler::getCommands();
271
a9229942
TD
272 while (true) {
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 }
277 self::getReader()->setHistoryEnabled(true);
d3ece8e9
TD
278 $now = new \DateTimeImmutable('now', WCF::getUser()->getTimeZone());
279 $line = self::getReader()->readLine(\sprintf(
e127ae19 280 '%s> ',
d3ece8e9
TD
281 $now->format('H:i:s'),
282 ));
a9229942
TD
283 if ($line === null) {
284 exit;
285 }
286 $line = StringUtil::trim($line);
287 try {
288 $command = CLICommandHandler::getCommand($line);
289 $command->execute(CLICommandHandler::getParameters($line));
290 } catch (IllegalLinkException $e) {
291 Log::error('notFound:' . JSON::encode(['command' => $line]));
292 self::getReader()->println(WCF::getLanguage()->getDynamicVariable(
293 'wcf.cli.error.command.notFound',
294 ['command' => $line]
295 ));
296
297 if (self::getArgvParser()->exitOnFail) {
298 exit(1);
299 }
300 continue;
301 } catch (PermissionDeniedException $e) {
302 Log::error('permissionDenied');
303 self::getReader()->println(WCF::getLanguage()->getDynamicVariable('wcf.page.error.permissionDenied'));
304
305 if (self::getArgvParser()->exitOnFail) {
306 exit(1);
307 }
308 continue;
309 } catch (ArgvException $e) {
310 // show error message and usage
311 if ($e->getMessage()) {
312 echo $e->getMessage() . \PHP_EOL;
313 }
314 echo $e->getUsageMessage();
315
316 if (self::getArgvParser()->exitOnFail) {
317 exit(1);
318 }
319 continue;
320 } catch (\Exception $e) {
321 Log::error($e);
322
323 if (self::getArgvParser()->exitOnFail) {
324 exit(1);
325 }
326 continue;
327 }
328 }
329 }
054db725 330}