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