🤖 Rector and PHPCS fixes
[dokuwiki.git] / lib / plugins / extension / cli.php
blob019b8d60675897898ff0da592edcff0d22f55a2c
1 <?php
3 use splitbrain\phpcli\Exception;
4 use dokuwiki\Extension\CLIPlugin;
5 use splitbrain\phpcli\Options;
6 use splitbrain\phpcli\TableFormatter;
7 use splitbrain\phpcli\Colors;
9 /**
10 * Class cli_plugin_extension
12 * Command Line component for the extension manager
14 * @license GPL2
15 * @author Andreas Gohr <andi@splitbrain.org>
17 class cli_plugin_extension extends CLIPlugin
19 /** @inheritdoc */
20 protected function setup(Options $options)
22 // general setup
23 $options->setHelp(
24 "Manage plugins and templates for this DokuWiki instance\n\n" .
25 "Status codes:\n" .
26 " i - installed\n" .
27 " b - bundled with DokuWiki\n" .
28 " g - installed via git\n" .
29 " d - disabled\n" .
30 " u - update available\n"
33 // search
34 $options->registerCommand('search', 'Search for an extension');
35 $options->registerOption('max', 'Maximum number of results (default 10)', 'm', 'number', 'search');
36 $options->registerOption('verbose', 'Show detailed extension information', 'v', false, 'search');
37 $options->registerArgument('query', 'The keyword(s) to search for', true, 'search');
39 // list
40 $options->registerCommand('list', 'List installed extensions');
41 $options->registerOption('verbose', 'Show detailed extension information', 'v', false, 'list');
42 $options->registerOption('filter', 'Filter by this status', 'f', 'status', 'list');
44 // upgrade
45 $options->registerCommand('upgrade', 'Update all installed extensions to their latest versions');
47 // install
48 $options->registerCommand('install', 'Install or upgrade extensions');
49 $options->registerArgument(
50 'extensions...',
51 'One or more extensions to install. Either by name or download URL',
52 true,
53 'install'
56 // uninstall
57 $options->registerCommand('uninstall', 'Uninstall a new extension');
58 $options->registerArgument('extensions...', 'One or more extensions to install', true, 'uninstall');
60 // enable
61 $options->registerCommand('enable', 'Enable installed extensions');
62 $options->registerArgument('extensions...', 'One or more extensions to enable', true, 'enable');
64 // disable
65 $options->registerCommand('disable', 'Disable installed extensions');
66 $options->registerArgument('extensions...', 'One or more extensions to disable', true, 'disable');
69 /** @inheritdoc */
70 protected function main(Options $options)
72 /** @var helper_plugin_extension_repository $repo */
73 $repo = plugin_load('helper', 'extension_repository');
74 if (!$repo->hasAccess(false)) {
75 $this->warning('Extension Repository API is not accessible, no remote info available!');
78 switch ($options->getCmd()) {
79 case 'list':
80 $ret = $this->cmdList($options->getOpt('verbose'), $options->getOpt('filter', ''));
81 break;
82 case 'search':
83 $ret = $this->cmdSearch(
84 implode(' ', $options->getArgs()),
85 $options->getOpt('verbose'),
86 (int)$options->getOpt('max', 10)
88 break;
89 case 'install':
90 $ret = $this->cmdInstall($options->getArgs());
91 break;
92 case 'uninstall':
93 $ret = $this->cmdUnInstall($options->getArgs());
94 break;
95 case 'enable':
96 $ret = $this->cmdEnable(true, $options->getArgs());
97 break;
98 case 'disable':
99 $ret = $this->cmdEnable(false, $options->getArgs());
100 break;
101 case 'upgrade':
102 $ret = $this->cmdUpgrade();
103 break;
104 default:
105 echo $options->help();
106 $ret = 0;
109 exit($ret);
113 * Upgrade all extensions
115 * @return int
117 protected function cmdUpgrade()
119 /* @var helper_plugin_extension_extension $ext */
120 $ext = $this->loadHelper('extension_extension');
121 $list = $this->getInstalledExtensions();
123 $ok = 0;
124 foreach ($list as $extname) {
125 $ext->setExtension($extname);
126 $date = $ext->getInstalledVersion();
127 $avail = $ext->getLastUpdate();
128 if ($avail && $avail > $date && !$ext->isBundled()) {
129 $ok += $this->cmdInstall([$extname]);
133 return $ok;
137 * Enable or disable one or more extensions
139 * @param bool $set
140 * @param string[] $extensions
141 * @return int
143 protected function cmdEnable($set, $extensions)
145 /* @var helper_plugin_extension_extension $ext */
146 $ext = $this->loadHelper('extension_extension');
148 $ok = 0;
149 foreach ($extensions as $extname) {
150 $ext->setExtension($extname);
151 if (!$ext->isInstalled()) {
152 $this->error(sprintf('Extension %s is not installed', $ext->getID()));
153 ++$ok;
154 continue;
157 if ($set) {
158 $status = $ext->enable();
159 $msg = 'msg_enabled';
160 } else {
161 $status = $ext->disable();
162 $msg = 'msg_disabled';
165 if ($status !== true) {
166 $this->error($status);
167 ++$ok;
168 continue;
169 } else {
170 $this->success(sprintf($this->getLang($msg), $ext->getID()));
174 return $ok;
178 * Uninstall one or more extensions
180 * @param string[] $extensions
181 * @return int
183 protected function cmdUnInstall($extensions)
185 /* @var helper_plugin_extension_extension $ext */
186 $ext = $this->loadHelper('extension_extension');
188 $ok = 0;
189 foreach ($extensions as $extname) {
190 $ext->setExtension($extname);
191 if (!$ext->isInstalled()) {
192 $this->error(sprintf('Extension %s is not installed', $ext->getID()));
193 ++$ok;
194 continue;
197 $status = $ext->uninstall();
198 if ($status) {
199 $this->success(sprintf($this->getLang('msg_delete_success'), $ext->getID()));
200 } else {
201 $this->error(sprintf($this->getLang('msg_delete_failed'), hsc($ext->getID())));
202 $ok = 1;
206 return $ok;
210 * Install one or more extensions
212 * @param string[] $extensions
213 * @return int
215 protected function cmdInstall($extensions)
217 /* @var helper_plugin_extension_extension $ext */
218 $ext = $this->loadHelper('extension_extension');
220 $ok = 0;
221 foreach ($extensions as $extname) {
222 $installed = [];
224 if (preg_match("/^https?:\/\//i", $extname)) {
225 try {
226 $installed = $ext->installFromURL($extname, true);
227 } catch (Exception $e) {
228 $this->error($e->getMessage());
229 ++$ok;
231 } else {
232 $ext->setExtension($extname);
234 if (!$ext->getDownloadURL()) {
235 ++$ok;
236 $this->error(
237 sprintf('Could not find download for %s', $ext->getID())
239 continue;
242 try {
243 $installed = $ext->installOrUpdate();
244 } catch (Exception $e) {
245 $this->error($e->getMessage());
246 ++$ok;
250 foreach ($installed as $info) {
251 $this->success(
252 sprintf(
253 $this->getLang('msg_' . $info['type'] . '_' . $info['action'] . '_success'),
254 $info['base']
259 return $ok;
263 * Search for an extension
265 * @param string $query
266 * @param bool $showdetails
267 * @param int $max
268 * @return int
269 * @throws Exception
271 protected function cmdSearch($query, $showdetails, $max)
273 /** @var helper_plugin_extension_repository $repository */
274 $repository = $this->loadHelper('extension_repository');
275 $result = $repository->search($query);
276 if ($max) {
277 $result = array_slice($result, 0, $max);
280 $this->listExtensions($result, $showdetails);
281 return 0;
285 * @param bool $showdetails
286 * @param string $filter
287 * @return int
288 * @throws Exception
290 protected function cmdList($showdetails, $filter)
292 $list = $this->getInstalledExtensions();
293 $this->listExtensions($list, $showdetails, $filter);
295 return 0;
299 * Get all installed extensions
301 * @return array
303 protected function getInstalledExtensions()
305 /** @var Doku_Plugin_Controller $plugin_controller */
306 global $plugin_controller;
307 $pluginlist = $plugin_controller->getList('', true);
308 $tpllist = glob(DOKU_INC . 'lib/tpl/*', GLOB_ONLYDIR);
309 $tpllist = array_map(static fn($path) => 'template:' . basename($path), $tpllist);
311 $list = array_merge($pluginlist, $tpllist);
312 sort($list);
313 return $list;
317 * List the given extensions
319 * @param string[] $list
320 * @param bool $details display details
321 * @param string $filter filter for this status
322 * @throws Exception
324 protected function listExtensions($list, $details, $filter = '')
326 /** @var helper_plugin_extension_extension $ext */
327 $ext = $this->loadHelper('extension_extension');
328 $tr = new TableFormatter($this->colors);
331 foreach ($list as $name) {
332 $ext->setExtension($name);
334 $status = '';
335 if ($ext->isInstalled()) {
336 $date = $ext->getInstalledVersion();
337 $avail = $ext->getLastUpdate();
338 $status = 'i';
339 if ($avail && $avail > $date) {
340 $vcolor = Colors::C_RED;
341 $status .= 'u';
342 } else {
343 $vcolor = Colors::C_GREEN;
345 if ($ext->isGitControlled()) $status = 'g';
346 if ($ext->isBundled()) $status = 'b';
347 if ($ext->isEnabled()) {
348 $ecolor = Colors::C_BROWN;
349 } else {
350 $ecolor = Colors::C_DARKGRAY;
351 $status .= 'd';
353 } else {
354 $ecolor = null;
355 $date = $ext->getLastUpdate();
356 $vcolor = null;
359 if ($filter && strpos($status, $filter) === false) {
360 continue;
363 echo $tr->format(
364 [20, 3, 12, '*'],
366 $ext->getID(),
367 $status,
368 $date,
369 strip_tags(sprintf(
370 $this->getLang('extensionby'),
371 $ext->getDisplayName(),
372 $this->colors->wrap($ext->getAuthor(), Colors::C_PURPLE)
376 $ecolor,
377 Colors::C_YELLOW,
378 $vcolor,
379 null,
383 if (!$details) continue;
385 echo $tr->format(
386 [5, '*'],
387 ['', $ext->getDescription()],
388 [null, Colors::C_CYAN]