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