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