1a8d2f3cbSAndreas Gohr<?php 2a8d2f3cbSAndreas Gohr 38553d24dSAndreas Gohruse dokuwiki\Extension\CLIPlugin; 47c9966a5SAndreas Gohruse dokuwiki\plugin\extension\Exception as ExtensionException; 57c9966a5SAndreas Gohruse dokuwiki\plugin\extension\Extension; 625d28a01SAndreas 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" . 32*160d3688SAndreas Gohr " i - installed ☠ - security issue\n" . 33*160d3688SAndreas Gohr " b - bundled with DokuWiki ⚠ - security warning\n" . 34*160d3688SAndreas Gohr " g - installed via git ↯ - update message\n" . 35*160d3688SAndreas Gohr " d - disabled ⮎ - URL changed\n" . 36*160d3688SAndreas Gohr " u - update available\n" 37b9daa2f5SAndreas Gohr ); 38a8d2f3cbSAndreas Gohr 39a8d2f3cbSAndreas Gohr // search 40a8d2f3cbSAndreas Gohr $options->registerCommand('search', 'Search for an extension'); 41a8d2f3cbSAndreas Gohr $options->registerOption('max', 'Maximum number of results (default 10)', 'm', 'number', 'search'); 42a8d2f3cbSAndreas Gohr $options->registerOption('verbose', 'Show detailed extension information', 'v', false, 'search'); 43a8d2f3cbSAndreas Gohr $options->registerArgument('query', 'The keyword(s) to search for', true, 'search'); 44a8d2f3cbSAndreas Gohr 45a8d2f3cbSAndreas Gohr // list 46a8d2f3cbSAndreas Gohr $options->registerCommand('list', 'List installed extensions'); 47a8d2f3cbSAndreas Gohr $options->registerOption('verbose', 'Show detailed extension information', 'v', false, 'list'); 48b9daa2f5SAndreas Gohr $options->registerOption('filter', 'Filter by this status', 'f', 'status', 'list'); 49a8d2f3cbSAndreas Gohr 50a8d2f3cbSAndreas Gohr // upgrade 51a8d2f3cbSAndreas Gohr $options->registerCommand('upgrade', 'Update all installed extensions to their latest versions'); 52a8d2f3cbSAndreas Gohr 53a8d2f3cbSAndreas Gohr // install 54a8d2f3cbSAndreas Gohr $options->registerCommand('install', 'Install or upgrade extensions'); 55dccd6b2bSAndreas Gohr $options->registerArgument( 56dccd6b2bSAndreas Gohr 'extensions...', 57dccd6b2bSAndreas Gohr 'One or more extensions to install. Either by name or download URL', 58dccd6b2bSAndreas Gohr true, 59dccd6b2bSAndreas Gohr 'install' 60e2170488SAndreas Gohr ); 61a8d2f3cbSAndreas Gohr 62a8d2f3cbSAndreas Gohr // uninstall 63a8d2f3cbSAndreas Gohr $options->registerCommand('uninstall', 'Uninstall a new extension'); 64a8d2f3cbSAndreas Gohr $options->registerArgument('extensions...', 'One or more extensions to install', true, 'uninstall'); 65a8d2f3cbSAndreas Gohr 66a8d2f3cbSAndreas Gohr // enable 67a8d2f3cbSAndreas Gohr $options->registerCommand('enable', 'Enable installed extensions'); 68a8d2f3cbSAndreas Gohr $options->registerArgument('extensions...', 'One or more extensions to enable', true, 'enable'); 69a8d2f3cbSAndreas Gohr 70a8d2f3cbSAndreas Gohr // disable 71a8d2f3cbSAndreas Gohr $options->registerCommand('disable', 'Disable installed extensions'); 72a8d2f3cbSAndreas Gohr $options->registerArgument('extensions...', 'One or more extensions to disable', true, 'disable'); 73a8d2f3cbSAndreas Gohr } 74a8d2f3cbSAndreas Gohr 75a8d2f3cbSAndreas Gohr /** @inheritdoc */ 76fe2dcfd5SAndreas Gohr protected function main(Options $options) 77a8d2f3cbSAndreas Gohr { 787c9966a5SAndreas Gohr $repo = Repository::getInstance(); 797c9966a5SAndreas Gohr try { 807c9966a5SAndreas Gohr $repo->checkAccess(); 817c9966a5SAndreas Gohr } catch (ExtensionException $e) { 82ed3520eeSAndreas Gohr $this->warning('Extension Repository API is not accessible, no remote info available!'); 83ed3520eeSAndreas Gohr } 84ed3520eeSAndreas Gohr 85a8d2f3cbSAndreas Gohr switch ($options->getCmd()) { 86a8d2f3cbSAndreas Gohr case 'list': 87b9daa2f5SAndreas Gohr $ret = $this->cmdList($options->getOpt('verbose'), $options->getOpt('filter', '')); 88a8d2f3cbSAndreas Gohr break; 89a8d2f3cbSAndreas Gohr case 'search': 90a8d2f3cbSAndreas Gohr $ret = $this->cmdSearch( 91a8d2f3cbSAndreas Gohr implode(' ', $options->getArgs()), 92a8d2f3cbSAndreas Gohr $options->getOpt('verbose'), 93a8d2f3cbSAndreas Gohr (int)$options->getOpt('max', 10) 94a8d2f3cbSAndreas Gohr ); 95a8d2f3cbSAndreas Gohr break; 96a8d2f3cbSAndreas Gohr case 'install': 97a8d2f3cbSAndreas Gohr $ret = $this->cmdInstall($options->getArgs()); 98a8d2f3cbSAndreas Gohr break; 99a8d2f3cbSAndreas Gohr case 'uninstall': 100a8d2f3cbSAndreas Gohr $ret = $this->cmdUnInstall($options->getArgs()); 101a8d2f3cbSAndreas Gohr break; 102a8d2f3cbSAndreas Gohr case 'enable': 103a8d2f3cbSAndreas Gohr $ret = $this->cmdEnable(true, $options->getArgs()); 104a8d2f3cbSAndreas Gohr break; 105a8d2f3cbSAndreas Gohr case 'disable': 106a8d2f3cbSAndreas Gohr $ret = $this->cmdEnable(false, $options->getArgs()); 107a8d2f3cbSAndreas Gohr break; 108a8d2f3cbSAndreas Gohr case 'upgrade': 109a8d2f3cbSAndreas Gohr $ret = $this->cmdUpgrade(); 110a8d2f3cbSAndreas Gohr break; 111a8d2f3cbSAndreas Gohr default: 112a8d2f3cbSAndreas Gohr echo $options->help(); 113a8d2f3cbSAndreas Gohr $ret = 0; 114a8d2f3cbSAndreas Gohr } 115a8d2f3cbSAndreas Gohr 116a8d2f3cbSAndreas Gohr exit($ret); 117a8d2f3cbSAndreas Gohr } 118a8d2f3cbSAndreas Gohr 119a8d2f3cbSAndreas Gohr /** 120a8d2f3cbSAndreas Gohr * Upgrade all extensions 121a8d2f3cbSAndreas Gohr * 122a8d2f3cbSAndreas Gohr * @return int 123a8d2f3cbSAndreas Gohr */ 124a8d2f3cbSAndreas Gohr protected function cmdUpgrade() 125a8d2f3cbSAndreas Gohr { 126*160d3688SAndreas Gohr $local = new Local(); 127*160d3688SAndreas Gohr $extensions = []; 128*160d3688SAndreas Gohr foreach ($local->getExtensions() as $ext) { 129*160d3688SAndreas Gohr if($ext->updateAvailable()) $extensions[] = $ext->getID(); 130a8d2f3cbSAndreas Gohr } 131*160d3688SAndreas Gohr return $this->cmdInstall($extensions); 132a8d2f3cbSAndreas Gohr } 133a8d2f3cbSAndreas Gohr 134a8d2f3cbSAndreas Gohr /** 135a8d2f3cbSAndreas Gohr * Enable or disable one or more extensions 136a8d2f3cbSAndreas Gohr * 137a8d2f3cbSAndreas Gohr * @param bool $set 138a8d2f3cbSAndreas Gohr * @param string[] $extensions 139a8d2f3cbSAndreas Gohr * @return int 140a8d2f3cbSAndreas Gohr */ 141a8d2f3cbSAndreas Gohr protected function cmdEnable($set, $extensions) 142a8d2f3cbSAndreas Gohr { 143a8d2f3cbSAndreas Gohr $ok = 0; 144a8d2f3cbSAndreas Gohr foreach ($extensions as $extname) { 145*160d3688SAndreas Gohr $ext = Extension::createFromId($extname); 146a8d2f3cbSAndreas Gohr 147*160d3688SAndreas Gohr try { 148a8d2f3cbSAndreas Gohr if ($set) { 149*160d3688SAndreas Gohr $ext->enable(); 150a8d2f3cbSAndreas Gohr $msg = 'msg_enabled'; 151a8d2f3cbSAndreas Gohr } else { 152*160d3688SAndreas Gohr $ext->disable(); 153a8d2f3cbSAndreas Gohr $msg = 'msg_disabled'; 154a8d2f3cbSAndreas Gohr } 155*160d3688SAndreas Gohr $this->success(sprintf($this->getLang($msg), $ext->getID())); 156*160d3688SAndreas Gohr } catch (ExtensionException $e) { 157*160d3688SAndreas Gohr $this->error($e->getMessage()); 158fe2dcfd5SAndreas Gohr ++$ok; 159a8d2f3cbSAndreas Gohr continue; 160a8d2f3cbSAndreas Gohr } 161a8d2f3cbSAndreas Gohr } 162a8d2f3cbSAndreas Gohr 163a8d2f3cbSAndreas Gohr return $ok; 164a8d2f3cbSAndreas Gohr } 165a8d2f3cbSAndreas Gohr 166a8d2f3cbSAndreas Gohr /** 167a8d2f3cbSAndreas Gohr * Uninstall one or more extensions 168a8d2f3cbSAndreas Gohr * 169a8d2f3cbSAndreas Gohr * @param string[] $extensions 170a8d2f3cbSAndreas Gohr * @return int 171a8d2f3cbSAndreas Gohr */ 172a8d2f3cbSAndreas Gohr protected function cmdUnInstall($extensions) 173a8d2f3cbSAndreas Gohr { 174*160d3688SAndreas Gohr $installer = new Installer(); 175a8d2f3cbSAndreas Gohr 176a8d2f3cbSAndreas Gohr $ok = 0; 177a8d2f3cbSAndreas Gohr foreach ($extensions as $extname) { 178*160d3688SAndreas Gohr $ext = Extension::createFromId($extname); 179a8d2f3cbSAndreas Gohr 180*160d3688SAndreas Gohr try { 181*160d3688SAndreas Gohr $installer->uninstall($ext); 182a8d2f3cbSAndreas Gohr $this->success(sprintf($this->getLang('msg_delete_success'), $ext->getID())); 183*160d3688SAndreas Gohr } catch (ExtensionException $e) { 184*160d3688SAndreas Gohr $this->debug($e->getTraceAsString()); 185*160d3688SAndreas Gohr $this->error($e->getMessage()); 186*160d3688SAndreas Gohr $ok++; // error code is number of failed uninstalls 187a8d2f3cbSAndreas Gohr } 188a8d2f3cbSAndreas Gohr } 189a8d2f3cbSAndreas Gohr return $ok; 190a8d2f3cbSAndreas Gohr } 191a8d2f3cbSAndreas Gohr 192a8d2f3cbSAndreas Gohr /** 193a8d2f3cbSAndreas Gohr * Install one or more extensions 194a8d2f3cbSAndreas Gohr * 195a8d2f3cbSAndreas Gohr * @param string[] $extensions 196a8d2f3cbSAndreas Gohr * @return int 197a8d2f3cbSAndreas Gohr */ 198a8d2f3cbSAndreas Gohr protected function cmdInstall($extensions) 199a8d2f3cbSAndreas Gohr { 200a8d2f3cbSAndreas Gohr $ok = 0; 201a8d2f3cbSAndreas Gohr foreach ($extensions as $extname) { 202*160d3688SAndreas Gohr $installer = new Installer(true); 203*160d3688SAndreas Gohr 20425d28a01SAndreas Gohr try { 205cc16762dSLocness if (preg_match("/^https?:\/\//i", $extname)) { 20625d28a01SAndreas Gohr $installer->installFromURL($extname, true); 207cc16762dSLocness } else { 20825d28a01SAndreas Gohr $installer->installFromId($extname); 209a8d2f3cbSAndreas Gohr } 21025d28a01SAndreas Gohr } catch (ExtensionException $e) { 21125d28a01SAndreas Gohr $this->debug($e->getTraceAsString()); 2125aaea2b0SLocness $this->error($e->getMessage()); 21325d28a01SAndreas Gohr $ok++; // error code is number of failed installs 2145aaea2b0SLocness } 2155aaea2b0SLocness 21625d28a01SAndreas Gohr $processed = $installer->getProcessed(); 21725d28a01SAndreas Gohr foreach($processed as $id => $status){ 21825d28a01SAndreas Gohr if($status == Installer::STATUS_INSTALLED) { 21925d28a01SAndreas Gohr $this->success(sprintf($this->getLang('msg_install_success'), $id)); 22025d28a01SAndreas Gohr } else if($status == Installer::STATUS_UPDATED) { 22125d28a01SAndreas Gohr $this->success(sprintf($this->getLang('msg_update_success'), $id)); 222a8d2f3cbSAndreas Gohr } 223cc16762dSLocness } 224*160d3688SAndreas Gohr } 22525d28a01SAndreas Gohr 226a8d2f3cbSAndreas Gohr return $ok; 227a8d2f3cbSAndreas Gohr } 228a8d2f3cbSAndreas Gohr 229a8d2f3cbSAndreas Gohr /** 230a8d2f3cbSAndreas Gohr * Search for an extension 231a8d2f3cbSAndreas Gohr * 232a8d2f3cbSAndreas Gohr * @param string $query 233a8d2f3cbSAndreas Gohr * @param bool $showdetails 234a8d2f3cbSAndreas Gohr * @param int $max 235a8d2f3cbSAndreas Gohr * @return int 2369b36c1fcSsplitbrain * @throws Exception 237a8d2f3cbSAndreas Gohr */ 238a8d2f3cbSAndreas Gohr protected function cmdSearch($query, $showdetails, $max) 239a8d2f3cbSAndreas Gohr { 2407c9966a5SAndreas Gohr $repo = Repository::getInstance(); 2417c9966a5SAndreas Gohr $result = $repo->searchExtensions($query); 242a8d2f3cbSAndreas Gohr if ($max) { 243a8d2f3cbSAndreas Gohr $result = array_slice($result, 0, $max); 244a8d2f3cbSAndreas Gohr } 245a8d2f3cbSAndreas Gohr 246a8d2f3cbSAndreas Gohr $this->listExtensions($result, $showdetails); 247a8d2f3cbSAndreas Gohr return 0; 248a8d2f3cbSAndreas Gohr } 249a8d2f3cbSAndreas Gohr 250a8d2f3cbSAndreas Gohr /** 251a8d2f3cbSAndreas Gohr * @param bool $showdetails 252b9daa2f5SAndreas Gohr * @param string $filter 253a8d2f3cbSAndreas Gohr * @return int 2549b36c1fcSsplitbrain * @throws Exception 255a8d2f3cbSAndreas Gohr */ 256b9daa2f5SAndreas Gohr protected function cmdList($showdetails, $filter) 257a8d2f3cbSAndreas Gohr { 2587c9966a5SAndreas Gohr $this->listExtensions((new Local())->getExtensions(), $showdetails, $filter); 259a8d2f3cbSAndreas Gohr return 0; 260a8d2f3cbSAndreas Gohr } 261a8d2f3cbSAndreas Gohr 262a8d2f3cbSAndreas Gohr /** 263a8d2f3cbSAndreas Gohr * List the given extensions 264a8d2f3cbSAndreas Gohr * 2657c9966a5SAndreas Gohr * @param Extension[] $list 266a8d2f3cbSAndreas Gohr * @param bool $details display details 267b9daa2f5SAndreas Gohr * @param string $filter filter for this status 2689b36c1fcSsplitbrain * @throws Exception 269*160d3688SAndreas Gohr * @todo break into smaller methods 270a8d2f3cbSAndreas Gohr */ 271b9daa2f5SAndreas Gohr protected function listExtensions($list, $details, $filter = '') 272a8d2f3cbSAndreas Gohr { 273fe2dcfd5SAndreas Gohr $tr = new TableFormatter($this->colors); 2747c9966a5SAndreas Gohr foreach ($list as $ext) { 275a8d2f3cbSAndreas Gohr 276a8d2f3cbSAndreas Gohr $status = ''; 277a8d2f3cbSAndreas Gohr if ($ext->isInstalled()) { 278a8d2f3cbSAndreas Gohr $date = $ext->getInstalledVersion(); 279a8d2f3cbSAndreas Gohr $avail = $ext->getLastUpdate(); 280a8d2f3cbSAndreas Gohr $status = 'i'; 281a8d2f3cbSAndreas Gohr if ($avail && $avail > $date) { 282e5688dc7SAndreas Gohr $vcolor = Colors::C_RED; 283b9daa2f5SAndreas Gohr $status .= 'u'; 284a8d2f3cbSAndreas Gohr } else { 285e5688dc7SAndreas Gohr $vcolor = Colors::C_GREEN; 286a8d2f3cbSAndreas Gohr } 287a8d2f3cbSAndreas Gohr if ($ext->isGitControlled()) $status = 'g'; 28899fd248dSAndreas Gohr if ($ext->isBundled()) { 28999fd248dSAndreas Gohr $status = 'b'; 29099fd248dSAndreas Gohr $date = '<bundled>'; 29199fd248dSAndreas Gohr $vcolor = null; 29299fd248dSAndreas Gohr } 293e5688dc7SAndreas Gohr if ($ext->isEnabled()) { 294e5688dc7SAndreas Gohr $ecolor = Colors::C_BROWN; 295e5688dc7SAndreas Gohr } else { 296e5688dc7SAndreas Gohr $ecolor = Colors::C_DARKGRAY; 297e5688dc7SAndreas Gohr $status .= 'd'; 298e5688dc7SAndreas Gohr } 299a8d2f3cbSAndreas Gohr } else { 300d915fa09SAndreas Gohr $ecolor = null; 301a8d2f3cbSAndreas Gohr $date = $ext->getLastUpdate(); 302e5688dc7SAndreas Gohr $vcolor = null; 303a8d2f3cbSAndreas Gohr } 304a8d2f3cbSAndreas Gohr 305b9daa2f5SAndreas Gohr if ($filter && strpos($status, $filter) === false) { 306b9daa2f5SAndreas Gohr continue; 307b9daa2f5SAndreas Gohr } 308a8d2f3cbSAndreas Gohr 30999fd248dSAndreas Gohr 31099fd248dSAndreas Gohr if ($ext->getSecurityIssue()) $status .= '☠'; 31199fd248dSAndreas Gohr if ($ext->getSecurityWarning()) $status .= '⚠'; 312*160d3688SAndreas Gohr if ($ext->getUpdateMessage()) $status .= '↯'; 313*160d3688SAndreas Gohr if ($ext->hasChangedURL()) $status .= '⮎'; 31499fd248dSAndreas Gohr 315a8d2f3cbSAndreas Gohr echo $tr->format( 316*160d3688SAndreas Gohr [20, 5, 12, '*'], 317a8d2f3cbSAndreas Gohr [ 318a8d2f3cbSAndreas Gohr $ext->getID(), 319a8d2f3cbSAndreas Gohr $status, 320a8d2f3cbSAndreas Gohr $date, 321a8d2f3cbSAndreas Gohr strip_tags(sprintf( 322a8d2f3cbSAndreas Gohr $this->getLang('extensionby'), 323a8d2f3cbSAndreas Gohr $ext->getDisplayName(), 324dccd6b2bSAndreas Gohr $this->colors->wrap($ext->getAuthor(), Colors::C_PURPLE) 325dccd6b2bSAndreas Gohr )) 326a8d2f3cbSAndreas Gohr ], 327a8d2f3cbSAndreas Gohr [ 328e5688dc7SAndreas Gohr $ecolor, 329a8d2f3cbSAndreas Gohr Colors::C_YELLOW, 330e5688dc7SAndreas Gohr $vcolor, 331a8d2f3cbSAndreas Gohr null, 332a8d2f3cbSAndreas Gohr ] 333a8d2f3cbSAndreas Gohr ); 334a8d2f3cbSAndreas Gohr 33599fd248dSAndreas Gohr 336a8d2f3cbSAndreas Gohr if (!$details) continue; 337a8d2f3cbSAndreas Gohr 338a8d2f3cbSAndreas Gohr echo $tr->format( 339*160d3688SAndreas Gohr [7, '*'], 340a8d2f3cbSAndreas Gohr ['', $ext->getDescription()], 341a8d2f3cbSAndreas Gohr [null, Colors::C_CYAN] 342a8d2f3cbSAndreas Gohr ); 34399fd248dSAndreas Gohr if ($ext->getSecurityWarning()) { 34499fd248dSAndreas Gohr echo $tr->format( 345*160d3688SAndreas Gohr [7, '*'], 34699fd248dSAndreas Gohr ['', '⚠ ' . $ext->getSecurityWarning()], 34799fd248dSAndreas Gohr [null, Colors::C_YELLOW] 34899fd248dSAndreas Gohr ); 34999fd248dSAndreas Gohr } 35099fd248dSAndreas Gohr if ($ext->getSecurityIssue()) { 35199fd248dSAndreas Gohr echo $tr->format( 352*160d3688SAndreas Gohr [7, '*'], 35399fd248dSAndreas Gohr ['', '☠ ' . $ext->getSecurityIssue()], 35499fd248dSAndreas Gohr [null, Colors::C_LIGHTRED] 35599fd248dSAndreas Gohr ); 35699fd248dSAndreas Gohr } 35799fd248dSAndreas Gohr if ($ext->getUpdateMessage()) { 35899fd248dSAndreas Gohr echo $tr->format( 359*160d3688SAndreas Gohr [7, '*'], 360*160d3688SAndreas Gohr ['', '↯ ' . $ext->getUpdateMessage()], 36199fd248dSAndreas Gohr [null, Colors::C_LIGHTBLUE] 36299fd248dSAndreas Gohr ); 36399fd248dSAndreas Gohr } 364*160d3688SAndreas Gohr if ($ext->hasChangedURL()) { 365*160d3688SAndreas Gohr $msg = $this->getLang('url_change'); 366*160d3688SAndreas Gohr $msg = str_replace('<br>',"\n", $msg); 367*160d3688SAndreas Gohr $msg = str_replace('<br/>',"\n", $msg); 368*160d3688SAndreas Gohr $msg = str_replace('<br />',"\n", $msg); 369*160d3688SAndreas Gohr $msg = strip_tags($msg); 370*160d3688SAndreas Gohr 371*160d3688SAndreas Gohr echo $tr->format( 372*160d3688SAndreas Gohr [7, '*'], 373*160d3688SAndreas Gohr ['', '⮎ ' . sprintf($msg, $ext->getDownloadURL(), $ext->getManager()->getDownloadUrl())], 374*160d3688SAndreas Gohr [null, Colors::C_BLUE] 375*160d3688SAndreas Gohr ); 376*160d3688SAndreas Gohr } 377a8d2f3cbSAndreas Gohr } 378a8d2f3cbSAndreas Gohr } 379a8d2f3cbSAndreas Gohr} 380