Commit | Line | Data |
---|---|---|
054db725 | 1 | <?php |
a9229942 | 2 | |
054db725 | 3 | namespace wcf\system; |
a9229942 | 4 | |
054db725 | 5 | use phpline\console\ConsoleReader; |
24e2bbb4 | 6 | use phpline\console\history\MemoryHistory; |
b158b3ce | 7 | use phpline\internal\Log; |
054db725 | 8 | use phpline\TerminalFactory; |
acd9f010 | 9 | use wcf\data\session\SessionEditor; |
b35ebcd2 | 10 | use wcf\system\cli\command\CLICommandHandler; |
f9e31606 | 11 | use wcf\system\exception\IllegalLinkException; |
fccfd98c | 12 | use wcf\system\exception\PermissionDeniedException; |
054db725 TD |
13 | use wcf\system\exception\UserInputException; |
14 | use wcf\system\user\authentication\UserAuthenticationFactory; | |
93cfe146 | 15 | use wcf\util\FileUtil; |
9814a16e | 16 | use wcf\util\JSON; |
054db725 | 17 | use wcf\util\StringUtil; |
ec85c193 | 18 | use Zend\Console\Exception\RuntimeException as ArgvException; |
24989d35 | 19 | use Zend\Console\Getopt as ArgvParser; |
054db725 TD |
20 | use 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 |
34 | class 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 | } |