xref: /dokuwiki/lib/plugins/extension/cli.php (revision cc16762d8948ed97ac28eb7ce04c10f2ea668fcd)
1<?php
2
3use splitbrain\phpcli\Colors;
4
5/**
6 * Class cli_plugin_extension
7 *
8 * Command Line component for the extension manager
9 *
10 * @license GPL2
11 * @author Andreas Gohr <andi@splitbrain.org>
12 */
13class cli_plugin_extension extends DokuWiki_CLI_Plugin
14{
15    /** @inheritdoc */
16    protected function setup(\splitbrain\phpcli\Options $options)
17    {
18        // general setup
19        $options->setHelp(
20            "Manage plugins and templates for this DokuWiki instance\n\n" .
21            "Status codes:\n" .
22            "   i - installed\n" .
23            "   b - bundled with DokuWiki\n" .
24            "   g - installed via git\n" .
25            "   d - disabled\n" .
26            "   u - update available\n"
27        );
28
29        // search
30        $options->registerCommand('search', 'Search for an extension');
31        $options->registerOption('max', 'Maximum number of results (default 10)', 'm', 'number', 'search');
32        $options->registerOption('verbose', 'Show detailed extension information', 'v', false, 'search');
33        $options->registerArgument('query', 'The keyword(s) to search for', true, 'search');
34
35        // list
36        $options->registerCommand('list', 'List installed extensions');
37        $options->registerOption('verbose', 'Show detailed extension information', 'v', false, 'list');
38        $options->registerOption('filter', 'Filter by this status', 'f', 'status', 'list');
39
40        // upgrade
41        $options->registerCommand('upgrade', 'Update all installed extensions to their latest versions');
42
43        // install
44        $options->registerCommand('install', 'Install or upgrade extensions');
45        $options->registerArgument('extensions...', 'One or more extensions to install', true, 'install');
46
47        // uninstall
48        $options->registerCommand('uninstall', 'Uninstall a new extension');
49        $options->registerArgument('extensions...', 'One or more extensions to install', true, 'uninstall');
50
51        // enable
52        $options->registerCommand('enable', 'Enable installed extensions');
53        $options->registerArgument('extensions...', 'One or more extensions to enable', true, 'enable');
54
55        // disable
56        $options->registerCommand('disable', 'Disable installed extensions');
57        $options->registerArgument('extensions...', 'One or more extensions to disable', true, 'disable');
58
59
60    }
61
62    /** @inheritdoc */
63    protected function main(\splitbrain\phpcli\Options $options)
64    {
65        /** @var helper_plugin_extension_repository $repo */
66        $repo = plugin_load('helper', 'extension_repository');
67        if (!$repo->hasAccess(false)) {
68            $this->warning('Extension Repository API is not accessible, no remote info available!');
69        }
70
71        switch ($options->getCmd()) {
72            case 'list':
73                $ret = $this->cmdList($options->getOpt('verbose'), $options->getOpt('filter', ''));
74                break;
75            case 'search':
76                $ret = $this->cmdSearch(
77                    implode(' ', $options->getArgs()),
78                    $options->getOpt('verbose'),
79                    (int)$options->getOpt('max', 10)
80                );
81                break;
82            case 'install':
83                $ret = $this->cmdInstall($options->getArgs());
84                break;
85            case 'uninstall':
86                $ret = $this->cmdUnInstall($options->getArgs());
87                break;
88            case 'enable':
89                $ret = $this->cmdEnable(true, $options->getArgs());
90                break;
91            case 'disable':
92                $ret = $this->cmdEnable(false, $options->getArgs());
93                break;
94            case 'upgrade':
95                $ret = $this->cmdUpgrade();
96                break;
97            default:
98                echo $options->help();
99                $ret = 0;
100        }
101
102        exit($ret);
103    }
104
105    /**
106     * Upgrade all extensions
107     *
108     * @return int
109     */
110    protected function cmdUpgrade()
111    {
112        /* @var helper_plugin_extension_extension $ext */
113        $ext = $this->loadHelper('extension_extension');
114        $list = $this->getInstalledExtensions();
115
116        $ok = 0;
117        foreach ($list as $extname) {
118            $ext->setExtension($extname);
119            $date = $ext->getInstalledVersion();
120            $avail = $ext->getLastUpdate();
121            if ($avail && $avail > $date && !$ext->isBundled()) {
122                $ok += $this->cmdInstall([$extname]);
123            }
124        }
125
126        return $ok;
127    }
128
129    /**
130     * Enable or disable one or more extensions
131     *
132     * @param bool $set
133     * @param string[] $extensions
134     * @return int
135     */
136    protected function cmdEnable($set, $extensions)
137    {
138        /* @var helper_plugin_extension_extension $ext */
139        $ext = $this->loadHelper('extension_extension');
140
141        $ok = 0;
142        foreach ($extensions as $extname) {
143            $ext->setExtension($extname);
144            if (!$ext->isInstalled()) {
145                $this->error(sprintf('Extension %s is not installed', $ext->getID()));
146                $ok += 1;
147                continue;
148            }
149
150            if ($set) {
151                $status = $ext->enable();
152                $msg = 'msg_enabled';
153            } else {
154                $status = $ext->disable();
155                $msg = 'msg_disabled';
156            }
157
158            if ($status !== true) {
159                $this->error($status);
160                $ok += 1;
161                continue;
162            } else {
163                $this->success(sprintf($this->getLang($msg), $ext->getID()));
164            }
165        }
166
167        return $ok;
168    }
169
170    /**
171     * Uninstall one or more extensions
172     *
173     * @param string[] $extensions
174     * @return int
175     */
176    protected function cmdUnInstall($extensions)
177    {
178        /* @var helper_plugin_extension_extension $ext */
179        $ext = $this->loadHelper('extension_extension');
180
181        $ok = 0;
182        foreach ($extensions as $extname) {
183            $ext->setExtension($extname);
184            if (!$ext->isInstalled()) {
185                $this->error(sprintf('Extension %s is not installed', $ext->getID()));
186                $ok += 1;
187                continue;
188            }
189
190            $status = $ext->uninstall();
191            if ($status) {
192                $this->success(sprintf($this->getLang('msg_delete_success'), $ext->getID()));
193            } else {
194                $this->error(sprintf($this->getLang('msg_delete_failed'), hsc($ext->getID())));
195                $ok = 1;
196            }
197        }
198
199        return $ok;
200    }
201
202    /**
203     * Install one or more extensions
204     *
205     * @param string[] $extensions
206     * @return int
207     */
208    protected function cmdInstall($extensions)
209    {
210        /* @var helper_plugin_extension_extension $ext */
211        $ext = $this->loadHelper('extension_extension');
212
213        $ok = 0;
214        foreach ($extensions as $extname) {
215            if (preg_match("/^https?:\/\//i", $extname)) {
216                try {
217                    $installed = $ext->installFromURL($extname, true);
218                    foreach ($installed as $name => $info) {
219                        $this->success(
220                            sprintf(
221                                $this->getLang('msg_' . $info['type'] . '_' . $info['action'] . '_success'),
222                                $info['base']
223                            )
224                        );
225                    }
226                } catch (Exception $e) {
227                    $this->error($e->getMessage());
228                    $ok += 1;
229                }
230            } else {
231                $ext->setExtension($extname);
232
233                if (!$ext->getDownloadURL()) {
234                    $ok += 1;
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                    foreach ($installed as $name => $info) {
244                        $this->success(
245                            sprintf(
246                                $this->getLang('msg_' . $info['type'] . '_' . $info['action'] . '_success'),
247                                $info['base']
248                            )
249                        );
250                    }
251                } catch (Exception $e) {
252                    $this->error($e->getMessage());
253                    $ok += 1;
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(function ($path) {
308            return 'template:' . basename($path);
309        }, $tpllist);
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 \splitbrain\phpcli\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