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