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