xref: /dokuwiki/lib/plugins/extension/cli.php (revision 25d28a0105a16bf32f2784e80b3cf268074c8c8a)
1a8d2f3cbSAndreas Gohr<?php
2a8d2f3cbSAndreas Gohr
38553d24dSAndreas Gohruse dokuwiki\Extension\CLIPlugin;
47c9966a5SAndreas Gohruse dokuwiki\plugin\extension\Exception as ExtensionException;
57c9966a5SAndreas Gohruse dokuwiki\plugin\extension\Extension;
6*25d28a01SAndreas Gohruse dokuwiki\plugin\extension\Installer;
77c9966a5SAndreas Gohruse dokuwiki\plugin\extension\Local;
87c9966a5SAndreas Gohruse dokuwiki\plugin\extension\Repository;
97c9966a5SAndreas Gohruse splitbrain\phpcli\Colors;
107c9966a5SAndreas Gohruse splitbrain\phpcli\Exception;
11fe2dcfd5SAndreas Gohruse splitbrain\phpcli\Options;
12fe2dcfd5SAndreas Gohruse splitbrain\phpcli\TableFormatter;
13a8d2f3cbSAndreas Gohr
14a8d2f3cbSAndreas Gohr/**
15a8d2f3cbSAndreas Gohr * Class cli_plugin_extension
16a8d2f3cbSAndreas Gohr *
17a8d2f3cbSAndreas Gohr * Command Line component for the extension manager
18a8d2f3cbSAndreas Gohr *
19a8d2f3cbSAndreas Gohr * @license GPL2
20a8d2f3cbSAndreas Gohr * @author Andreas Gohr <andi@splitbrain.org>
21a8d2f3cbSAndreas Gohr */
228553d24dSAndreas Gohrclass cli_plugin_extension extends CLIPlugin
23a8d2f3cbSAndreas Gohr{
24a8d2f3cbSAndreas Gohr    /** @inheritdoc */
25fe2dcfd5SAndreas Gohr    protected function setup(Options $options)
26a8d2f3cbSAndreas Gohr    {
27a8d2f3cbSAndreas Gohr        // general setup
287c9966a5SAndreas Gohr        $options->useCompactHelp();
29b9daa2f5SAndreas Gohr        $options->setHelp(
30b9daa2f5SAndreas Gohr            "Manage plugins and templates for this DokuWiki instance\n\n" .
31b9daa2f5SAndreas Gohr            "Status codes:\n" .
32b9daa2f5SAndreas Gohr            "   i - installed\n" .
33b9daa2f5SAndreas Gohr            "   b - bundled with DokuWiki\n" .
34b9daa2f5SAndreas Gohr            "   g - installed via git\n" .
35b9daa2f5SAndreas Gohr            "   d - disabled\n" .
3699fd248dSAndreas Gohr            "   u - update available\n" .
3799fd248dSAndreas Gohr            "   ☠ - security issue\n" .
3899fd248dSAndreas Gohr            "   ⚠ - security warning\n" .
3999fd248dSAndreas Gohr            "   ▽ - update message\n"
40b9daa2f5SAndreas Gohr        );
41a8d2f3cbSAndreas Gohr
42a8d2f3cbSAndreas Gohr        // search
43a8d2f3cbSAndreas Gohr        $options->registerCommand('search', 'Search for an extension');
44a8d2f3cbSAndreas Gohr        $options->registerOption('max', 'Maximum number of results (default 10)', 'm', 'number', 'search');
45a8d2f3cbSAndreas Gohr        $options->registerOption('verbose', 'Show detailed extension information', 'v', false, 'search');
46a8d2f3cbSAndreas Gohr        $options->registerArgument('query', 'The keyword(s) to search for', true, 'search');
47a8d2f3cbSAndreas Gohr
48a8d2f3cbSAndreas Gohr        // list
49a8d2f3cbSAndreas Gohr        $options->registerCommand('list', 'List installed extensions');
50a8d2f3cbSAndreas Gohr        $options->registerOption('verbose', 'Show detailed extension information', 'v', false, 'list');
51b9daa2f5SAndreas Gohr        $options->registerOption('filter', 'Filter by this status', 'f', 'status', 'list');
52a8d2f3cbSAndreas Gohr
53a8d2f3cbSAndreas Gohr        // upgrade
54a8d2f3cbSAndreas Gohr        $options->registerCommand('upgrade', 'Update all installed extensions to their latest versions');
55a8d2f3cbSAndreas Gohr
56a8d2f3cbSAndreas Gohr        // install
57a8d2f3cbSAndreas Gohr        $options->registerCommand('install', 'Install or upgrade extensions');
58dccd6b2bSAndreas Gohr        $options->registerArgument(
59dccd6b2bSAndreas Gohr            'extensions...',
60dccd6b2bSAndreas Gohr            'One or more extensions to install. Either by name or download URL',
61dccd6b2bSAndreas Gohr            true,
62dccd6b2bSAndreas Gohr            'install'
63e2170488SAndreas Gohr        );
64a8d2f3cbSAndreas Gohr
65a8d2f3cbSAndreas Gohr        // uninstall
66a8d2f3cbSAndreas Gohr        $options->registerCommand('uninstall', 'Uninstall a new extension');
67a8d2f3cbSAndreas Gohr        $options->registerArgument('extensions...', 'One or more extensions to install', true, 'uninstall');
68a8d2f3cbSAndreas Gohr
69a8d2f3cbSAndreas Gohr        // enable
70a8d2f3cbSAndreas Gohr        $options->registerCommand('enable', 'Enable installed extensions');
71a8d2f3cbSAndreas Gohr        $options->registerArgument('extensions...', 'One or more extensions to enable', true, 'enable');
72a8d2f3cbSAndreas Gohr
73a8d2f3cbSAndreas Gohr        // disable
74a8d2f3cbSAndreas Gohr        $options->registerCommand('disable', 'Disable installed extensions');
75a8d2f3cbSAndreas Gohr        $options->registerArgument('extensions...', 'One or more extensions to disable', true, 'disable');
76a8d2f3cbSAndreas Gohr    }
77a8d2f3cbSAndreas Gohr
78a8d2f3cbSAndreas Gohr    /** @inheritdoc */
79fe2dcfd5SAndreas Gohr    protected function main(Options $options)
80a8d2f3cbSAndreas Gohr    {
817c9966a5SAndreas Gohr        $repo = Repository::getInstance();
827c9966a5SAndreas Gohr        try {
837c9966a5SAndreas Gohr            $repo->checkAccess();
847c9966a5SAndreas Gohr        } catch (ExtensionException $e) {
85ed3520eeSAndreas Gohr            $this->warning('Extension Repository API is not accessible, no remote info available!');
86ed3520eeSAndreas Gohr        }
87ed3520eeSAndreas Gohr
88a8d2f3cbSAndreas Gohr        switch ($options->getCmd()) {
89a8d2f3cbSAndreas Gohr            case 'list':
90b9daa2f5SAndreas Gohr                $ret = $this->cmdList($options->getOpt('verbose'), $options->getOpt('filter', ''));
91a8d2f3cbSAndreas Gohr                break;
92a8d2f3cbSAndreas Gohr            case 'search':
93a8d2f3cbSAndreas Gohr                $ret = $this->cmdSearch(
94a8d2f3cbSAndreas Gohr                    implode(' ', $options->getArgs()),
95a8d2f3cbSAndreas Gohr                    $options->getOpt('verbose'),
96a8d2f3cbSAndreas Gohr                    (int)$options->getOpt('max', 10)
97a8d2f3cbSAndreas Gohr                );
98a8d2f3cbSAndreas Gohr                break;
99a8d2f3cbSAndreas Gohr            case 'install':
100a8d2f3cbSAndreas Gohr                $ret = $this->cmdInstall($options->getArgs());
101a8d2f3cbSAndreas Gohr                break;
102a8d2f3cbSAndreas Gohr            case 'uninstall':
103a8d2f3cbSAndreas Gohr                $ret = $this->cmdUnInstall($options->getArgs());
104a8d2f3cbSAndreas Gohr                break;
105a8d2f3cbSAndreas Gohr            case 'enable':
106a8d2f3cbSAndreas Gohr                $ret = $this->cmdEnable(true, $options->getArgs());
107a8d2f3cbSAndreas Gohr                break;
108a8d2f3cbSAndreas Gohr            case 'disable':
109a8d2f3cbSAndreas Gohr                $ret = $this->cmdEnable(false, $options->getArgs());
110a8d2f3cbSAndreas Gohr                break;
111a8d2f3cbSAndreas Gohr            case 'upgrade':
112a8d2f3cbSAndreas Gohr                $ret = $this->cmdUpgrade();
113a8d2f3cbSAndreas Gohr                break;
114a8d2f3cbSAndreas Gohr            default:
115a8d2f3cbSAndreas Gohr                echo $options->help();
116a8d2f3cbSAndreas Gohr                $ret = 0;
117a8d2f3cbSAndreas Gohr        }
118a8d2f3cbSAndreas Gohr
119a8d2f3cbSAndreas Gohr        exit($ret);
120a8d2f3cbSAndreas Gohr    }
121a8d2f3cbSAndreas Gohr
122a8d2f3cbSAndreas Gohr    /**
123a8d2f3cbSAndreas Gohr     * Upgrade all extensions
124a8d2f3cbSAndreas Gohr     *
125a8d2f3cbSAndreas Gohr     * @return int
126a8d2f3cbSAndreas Gohr     */
127a8d2f3cbSAndreas Gohr    protected function cmdUpgrade()
128a8d2f3cbSAndreas Gohr    {
129a8d2f3cbSAndreas Gohr        /* @var helper_plugin_extension_extension $ext */
130a8d2f3cbSAndreas Gohr        $ext = $this->loadHelper('extension_extension');
131a8d2f3cbSAndreas Gohr        $list = $this->getInstalledExtensions();
132a8d2f3cbSAndreas Gohr
133a8d2f3cbSAndreas Gohr        $ok = 0;
134a8d2f3cbSAndreas Gohr        foreach ($list as $extname) {
135a8d2f3cbSAndreas Gohr            $ext->setExtension($extname);
136a8d2f3cbSAndreas Gohr            $date = $ext->getInstalledVersion();
137a8d2f3cbSAndreas Gohr            $avail = $ext->getLastUpdate();
138be15e516SAndreas Gohr            if ($avail && $avail > $date && !$ext->isBundled()) {
139a8d2f3cbSAndreas Gohr                $ok += $this->cmdInstall([$extname]);
140a8d2f3cbSAndreas Gohr            }
141a8d2f3cbSAndreas Gohr        }
142a8d2f3cbSAndreas Gohr
143a8d2f3cbSAndreas Gohr        return $ok;
144a8d2f3cbSAndreas Gohr    }
145a8d2f3cbSAndreas Gohr
146a8d2f3cbSAndreas Gohr    /**
147a8d2f3cbSAndreas Gohr     * Enable or disable one or more extensions
148a8d2f3cbSAndreas Gohr     *
149a8d2f3cbSAndreas Gohr     * @param bool $set
150a8d2f3cbSAndreas Gohr     * @param string[] $extensions
151a8d2f3cbSAndreas Gohr     * @return int
152a8d2f3cbSAndreas Gohr     */
153a8d2f3cbSAndreas Gohr    protected function cmdEnable($set, $extensions)
154a8d2f3cbSAndreas Gohr    {
155a8d2f3cbSAndreas Gohr        /* @var helper_plugin_extension_extension $ext */
156a8d2f3cbSAndreas Gohr        $ext = $this->loadHelper('extension_extension');
157a8d2f3cbSAndreas Gohr
158a8d2f3cbSAndreas Gohr        $ok = 0;
159a8d2f3cbSAndreas Gohr        foreach ($extensions as $extname) {
160a8d2f3cbSAndreas Gohr            $ext->setExtension($extname);
161a8d2f3cbSAndreas Gohr            if (!$ext->isInstalled()) {
162a8d2f3cbSAndreas Gohr                $this->error(sprintf('Extension %s is not installed', $ext->getID()));
163fe2dcfd5SAndreas Gohr                ++$ok;
164a8d2f3cbSAndreas Gohr                continue;
165a8d2f3cbSAndreas Gohr            }
166a8d2f3cbSAndreas Gohr
167a8d2f3cbSAndreas Gohr            if ($set) {
168a8d2f3cbSAndreas Gohr                $status = $ext->enable();
169a8d2f3cbSAndreas Gohr                $msg = 'msg_enabled';
170a8d2f3cbSAndreas Gohr            } else {
171a8d2f3cbSAndreas Gohr                $status = $ext->disable();
172a8d2f3cbSAndreas Gohr                $msg = 'msg_disabled';
173a8d2f3cbSAndreas Gohr            }
174a8d2f3cbSAndreas Gohr
175a8d2f3cbSAndreas Gohr            if ($status !== true) {
176a8d2f3cbSAndreas Gohr                $this->error($status);
177fe2dcfd5SAndreas Gohr                ++$ok;
178a8d2f3cbSAndreas Gohr                continue;
179a8d2f3cbSAndreas Gohr            } else {
180a8d2f3cbSAndreas Gohr                $this->success(sprintf($this->getLang($msg), $ext->getID()));
181a8d2f3cbSAndreas Gohr            }
182a8d2f3cbSAndreas Gohr        }
183a8d2f3cbSAndreas Gohr
184a8d2f3cbSAndreas Gohr        return $ok;
185a8d2f3cbSAndreas Gohr    }
186a8d2f3cbSAndreas Gohr
187a8d2f3cbSAndreas Gohr    /**
188a8d2f3cbSAndreas Gohr     * Uninstall one or more extensions
189a8d2f3cbSAndreas Gohr     *
190a8d2f3cbSAndreas Gohr     * @param string[] $extensions
191a8d2f3cbSAndreas Gohr     * @return int
192a8d2f3cbSAndreas Gohr     */
193a8d2f3cbSAndreas Gohr    protected function cmdUnInstall($extensions)
194a8d2f3cbSAndreas Gohr    {
195a8d2f3cbSAndreas Gohr        /* @var helper_plugin_extension_extension $ext */
196a8d2f3cbSAndreas Gohr        $ext = $this->loadHelper('extension_extension');
197a8d2f3cbSAndreas Gohr
198a8d2f3cbSAndreas Gohr        $ok = 0;
199a8d2f3cbSAndreas Gohr        foreach ($extensions as $extname) {
200a8d2f3cbSAndreas Gohr            $ext->setExtension($extname);
201a8d2f3cbSAndreas Gohr            if (!$ext->isInstalled()) {
202a8d2f3cbSAndreas Gohr                $this->error(sprintf('Extension %s is not installed', $ext->getID()));
203fe2dcfd5SAndreas Gohr                ++$ok;
204a8d2f3cbSAndreas Gohr                continue;
205a8d2f3cbSAndreas Gohr            }
206a8d2f3cbSAndreas Gohr
207a8d2f3cbSAndreas Gohr            $status = $ext->uninstall();
208a8d2f3cbSAndreas Gohr            if ($status) {
209a8d2f3cbSAndreas Gohr                $this->success(sprintf($this->getLang('msg_delete_success'), $ext->getID()));
210a8d2f3cbSAndreas Gohr            } else {
211a8d2f3cbSAndreas Gohr                $this->error(sprintf($this->getLang('msg_delete_failed'), hsc($ext->getID())));
212a8d2f3cbSAndreas Gohr                $ok = 1;
213a8d2f3cbSAndreas Gohr            }
214a8d2f3cbSAndreas Gohr        }
215a8d2f3cbSAndreas Gohr
216a8d2f3cbSAndreas Gohr        return $ok;
217a8d2f3cbSAndreas Gohr    }
218a8d2f3cbSAndreas Gohr
219a8d2f3cbSAndreas Gohr    /**
220a8d2f3cbSAndreas Gohr     * Install one or more extensions
221a8d2f3cbSAndreas Gohr     *
222a8d2f3cbSAndreas Gohr     * @param string[] $extensions
223a8d2f3cbSAndreas Gohr     * @return int
224a8d2f3cbSAndreas Gohr     */
225a8d2f3cbSAndreas Gohr    protected function cmdInstall($extensions)
226a8d2f3cbSAndreas Gohr    {
227a8d2f3cbSAndreas Gohr
228*25d28a01SAndreas Gohr        $installer = new Installer(true);
229*25d28a01SAndreas Gohr
230a8d2f3cbSAndreas Gohr        $ok = 0;
231a8d2f3cbSAndreas Gohr        foreach ($extensions as $extname) {
232*25d28a01SAndreas Gohr            try {
233cc16762dSLocness                if (preg_match("/^https?:\/\//i", $extname)) {
234*25d28a01SAndreas Gohr                    $installer->installFromURL($extname, true);
235cc16762dSLocness                } else {
236*25d28a01SAndreas Gohr                    $installer->installFromId($extname);
237a8d2f3cbSAndreas Gohr                }
238*25d28a01SAndreas Gohr            } catch (ExtensionException $e) {
239*25d28a01SAndreas Gohr                $this->debug($e->getTraceAsString());
2405aaea2b0SLocness                $this->error($e->getMessage());
241*25d28a01SAndreas Gohr                $ok++; // error code is number of failed installs
2425aaea2b0SLocness            }
2435aaea2b0SLocness        }
2445aaea2b0SLocness
245*25d28a01SAndreas Gohr        $processed = $installer->getProcessed();
246*25d28a01SAndreas Gohr        foreach($processed as $id => $status){
247*25d28a01SAndreas Gohr            if($status == Installer::STATUS_INSTALLED) {
248*25d28a01SAndreas Gohr                $this->success(sprintf($this->getLang('msg_install_success'), $id));
249*25d28a01SAndreas Gohr            } else if($status == Installer::STATUS_UPDATED) {
250*25d28a01SAndreas Gohr                $this->success(sprintf($this->getLang('msg_update_success'), $id));
251a8d2f3cbSAndreas Gohr            }
252cc16762dSLocness        }
253*25d28a01SAndreas Gohr
254*25d28a01SAndreas Gohr
255a8d2f3cbSAndreas Gohr        return $ok;
256a8d2f3cbSAndreas Gohr    }
257a8d2f3cbSAndreas Gohr
258a8d2f3cbSAndreas Gohr    /**
259a8d2f3cbSAndreas Gohr     * Search for an extension
260a8d2f3cbSAndreas Gohr     *
261a8d2f3cbSAndreas Gohr     * @param string $query
262a8d2f3cbSAndreas Gohr     * @param bool $showdetails
263a8d2f3cbSAndreas Gohr     * @param int $max
264a8d2f3cbSAndreas Gohr     * @return int
2659b36c1fcSsplitbrain     * @throws Exception
266a8d2f3cbSAndreas Gohr     */
267a8d2f3cbSAndreas Gohr    protected function cmdSearch($query, $showdetails, $max)
268a8d2f3cbSAndreas Gohr    {
2697c9966a5SAndreas Gohr        $repo = Repository::getInstance();
2707c9966a5SAndreas Gohr        $result = $repo->searchExtensions($query);
271a8d2f3cbSAndreas Gohr        if ($max) {
272a8d2f3cbSAndreas Gohr            $result = array_slice($result, 0, $max);
273a8d2f3cbSAndreas Gohr        }
274a8d2f3cbSAndreas Gohr
275a8d2f3cbSAndreas Gohr        $this->listExtensions($result, $showdetails);
276a8d2f3cbSAndreas Gohr        return 0;
277a8d2f3cbSAndreas Gohr    }
278a8d2f3cbSAndreas Gohr
279a8d2f3cbSAndreas Gohr    /**
280a8d2f3cbSAndreas Gohr     * @param bool $showdetails
281b9daa2f5SAndreas Gohr     * @param string $filter
282a8d2f3cbSAndreas Gohr     * @return int
2839b36c1fcSsplitbrain     * @throws Exception
284a8d2f3cbSAndreas Gohr     */
285b9daa2f5SAndreas Gohr    protected function cmdList($showdetails, $filter)
286a8d2f3cbSAndreas Gohr    {
2877c9966a5SAndreas Gohr        $this->listExtensions((new Local())->getExtensions(), $showdetails, $filter);
288a8d2f3cbSAndreas Gohr        return 0;
289a8d2f3cbSAndreas Gohr    }
290a8d2f3cbSAndreas Gohr
291a8d2f3cbSAndreas Gohr    /**
292a8d2f3cbSAndreas Gohr     * List the given extensions
293a8d2f3cbSAndreas Gohr     *
2947c9966a5SAndreas Gohr     * @param Extension[] $list
295a8d2f3cbSAndreas Gohr     * @param bool $details display details
296b9daa2f5SAndreas Gohr     * @param string $filter filter for this status
2979b36c1fcSsplitbrain     * @throws Exception
298a8d2f3cbSAndreas Gohr     */
299b9daa2f5SAndreas Gohr    protected function listExtensions($list, $details, $filter = '')
300a8d2f3cbSAndreas Gohr    {
301fe2dcfd5SAndreas Gohr        $tr = new TableFormatter($this->colors);
3027c9966a5SAndreas Gohr        foreach ($list as $ext) {
303a8d2f3cbSAndreas Gohr
304a8d2f3cbSAndreas Gohr            $status = '';
305a8d2f3cbSAndreas Gohr            if ($ext->isInstalled()) {
306a8d2f3cbSAndreas Gohr                $date = $ext->getInstalledVersion();
307a8d2f3cbSAndreas Gohr                $avail = $ext->getLastUpdate();
308a8d2f3cbSAndreas Gohr                $status = 'i';
309a8d2f3cbSAndreas Gohr                if ($avail && $avail > $date) {
310e5688dc7SAndreas Gohr                    $vcolor = Colors::C_RED;
311b9daa2f5SAndreas Gohr                    $status .= 'u';
312a8d2f3cbSAndreas Gohr                } else {
313e5688dc7SAndreas Gohr                    $vcolor = Colors::C_GREEN;
314a8d2f3cbSAndreas Gohr                }
315a8d2f3cbSAndreas Gohr                if ($ext->isGitControlled()) $status = 'g';
31699fd248dSAndreas Gohr                if ($ext->isBundled()) {
31799fd248dSAndreas Gohr                    $status = 'b';
31899fd248dSAndreas Gohr                    $date = '<bundled>';
31999fd248dSAndreas Gohr                    $vcolor = null;
32099fd248dSAndreas Gohr                }
321e5688dc7SAndreas Gohr                if ($ext->isEnabled()) {
322e5688dc7SAndreas Gohr                    $ecolor = Colors::C_BROWN;
323e5688dc7SAndreas Gohr                } else {
324e5688dc7SAndreas Gohr                    $ecolor = Colors::C_DARKGRAY;
325e5688dc7SAndreas Gohr                    $status .= 'd';
326e5688dc7SAndreas Gohr                }
327a8d2f3cbSAndreas Gohr            } else {
328d915fa09SAndreas Gohr                $ecolor = null;
329a8d2f3cbSAndreas Gohr                $date = $ext->getLastUpdate();
330e5688dc7SAndreas Gohr                $vcolor = null;
331a8d2f3cbSAndreas Gohr            }
332a8d2f3cbSAndreas Gohr
333b9daa2f5SAndreas Gohr            if ($filter && strpos($status, $filter) === false) {
334b9daa2f5SAndreas Gohr                continue;
335b9daa2f5SAndreas Gohr            }
336a8d2f3cbSAndreas Gohr
33799fd248dSAndreas Gohr
33899fd248dSAndreas Gohr            if ($ext->getSecurityIssue()) $status .= '☠';
33999fd248dSAndreas Gohr            if ($ext->getSecurityWarning()) $status .= '⚠';
34099fd248dSAndreas Gohr            if ($ext->getUpdateMessage()) $status .= '▽';
34199fd248dSAndreas Gohr
342a8d2f3cbSAndreas Gohr            echo $tr->format(
343a8d2f3cbSAndreas Gohr                [20, 3, 12, '*'],
344a8d2f3cbSAndreas Gohr                [
345a8d2f3cbSAndreas Gohr                    $ext->getID(),
346a8d2f3cbSAndreas Gohr                    $status,
347a8d2f3cbSAndreas Gohr                    $date,
348a8d2f3cbSAndreas Gohr                    strip_tags(sprintf(
349a8d2f3cbSAndreas Gohr                        $this->getLang('extensionby'),
350a8d2f3cbSAndreas Gohr                        $ext->getDisplayName(),
351dccd6b2bSAndreas Gohr                        $this->colors->wrap($ext->getAuthor(), Colors::C_PURPLE)
352dccd6b2bSAndreas Gohr                    ))
353a8d2f3cbSAndreas Gohr                ],
354a8d2f3cbSAndreas Gohr                [
355e5688dc7SAndreas Gohr                    $ecolor,
356a8d2f3cbSAndreas Gohr                    Colors::C_YELLOW,
357e5688dc7SAndreas Gohr                    $vcolor,
358a8d2f3cbSAndreas Gohr                    null,
359a8d2f3cbSAndreas Gohr                ]
360a8d2f3cbSAndreas Gohr            );
361a8d2f3cbSAndreas Gohr
36299fd248dSAndreas Gohr
363a8d2f3cbSAndreas Gohr            if (!$details) continue;
364a8d2f3cbSAndreas Gohr
365a8d2f3cbSAndreas Gohr            echo $tr->format(
366a8d2f3cbSAndreas Gohr                [5, '*'],
367a8d2f3cbSAndreas Gohr                ['', $ext->getDescription()],
368a8d2f3cbSAndreas Gohr                [null, Colors::C_CYAN]
369a8d2f3cbSAndreas Gohr            );
37099fd248dSAndreas Gohr            if ($ext->getSecurityWarning()) {
37199fd248dSAndreas Gohr                echo $tr->format(
37299fd248dSAndreas Gohr                    [5, '*'],
37399fd248dSAndreas Gohr                    ['', '⚠ ' . $ext->getSecurityWarning()],
37499fd248dSAndreas Gohr                    [null, Colors::C_YELLOW]
37599fd248dSAndreas Gohr                );
37699fd248dSAndreas Gohr            }
37799fd248dSAndreas Gohr            if ($ext->getSecurityIssue()) {
37899fd248dSAndreas Gohr                echo $tr->format(
37999fd248dSAndreas Gohr                    [5, '*'],
38099fd248dSAndreas Gohr                    ['', '☠ ' . $ext->getSecurityIssue()],
38199fd248dSAndreas Gohr                    [null, Colors::C_LIGHTRED]
38299fd248dSAndreas Gohr                );
38399fd248dSAndreas Gohr            }
38499fd248dSAndreas Gohr            if ($ext->getUpdateMessage()) {
38599fd248dSAndreas Gohr                echo $tr->format(
38699fd248dSAndreas Gohr                    [5, '*'],
38799fd248dSAndreas Gohr                    ['', '▽ ' . $ext->getUpdateMessage()],
38899fd248dSAndreas Gohr                    [null, Colors::C_LIGHTBLUE]
38999fd248dSAndreas Gohr                );
39099fd248dSAndreas Gohr            }
391a8d2f3cbSAndreas Gohr        }
392a8d2f3cbSAndreas Gohr    }
393a8d2f3cbSAndreas Gohr}
394