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