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