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