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