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; 84fd6a1d7SAndreas Gohruse dokuwiki\plugin\extension\Notice; 97c9966a5SAndreas Gohruse dokuwiki\plugin\extension\Repository; 107c9966a5SAndreas Gohruse splitbrain\phpcli\Colors; 117c9966a5SAndreas Gohruse splitbrain\phpcli\Exception; 12fe2dcfd5SAndreas Gohruse splitbrain\phpcli\Options; 13fe2dcfd5SAndreas Gohruse splitbrain\phpcli\TableFormatter; 14a8d2f3cbSAndreas Gohr 15a8d2f3cbSAndreas Gohr/** 16a8d2f3cbSAndreas Gohr * Class cli_plugin_extension 17a8d2f3cbSAndreas Gohr * 18a8d2f3cbSAndreas Gohr * Command Line component for the extension manager 19a8d2f3cbSAndreas Gohr * 20a8d2f3cbSAndreas Gohr * @license GPL2 21a8d2f3cbSAndreas Gohr * @author Andreas Gohr <andi@splitbrain.org> 22a8d2f3cbSAndreas Gohr */ 238553d24dSAndreas Gohrclass cli_plugin_extension extends CLIPlugin 24a8d2f3cbSAndreas Gohr{ 25a8d2f3cbSAndreas Gohr /** @inheritdoc */ 26fe2dcfd5SAndreas Gohr protected function setup(Options $options) 27a8d2f3cbSAndreas Gohr { 28a8d2f3cbSAndreas Gohr // general setup 297c9966a5SAndreas Gohr $options->useCompactHelp(); 30b9daa2f5SAndreas Gohr $options->setHelp( 31b9daa2f5SAndreas Gohr "Manage plugins and templates for this DokuWiki instance\n\n" . 32b9daa2f5SAndreas Gohr "Status codes:\n" . 33*d2d4b908SAndreas Gohr " i - installed " . Notice::symbol(Notice::SECURITY) . " - security issue\n" . 34*d2d4b908SAndreas Gohr " b - bundled with DokuWiki " . Notice::symbol(Notice::ERROR) . " - extension error\n" . 35*d2d4b908SAndreas Gohr " g - installed via git " . Notice::symbol(Notice::WARNING) . " - extension warning\n" . 36*d2d4b908SAndreas Gohr " d - disabled " . Notice::symbol(Notice::INFO) . " - extension info\n" . 37160d3688SAndreas Gohr " u - update available\n" 38b9daa2f5SAndreas Gohr ); 39a8d2f3cbSAndreas Gohr 40a8d2f3cbSAndreas Gohr // search 41a8d2f3cbSAndreas Gohr $options->registerCommand('search', 'Search for an extension'); 42a8d2f3cbSAndreas Gohr $options->registerOption('max', 'Maximum number of results (default 10)', 'm', 'number', 'search'); 43a8d2f3cbSAndreas Gohr $options->registerOption('verbose', 'Show detailed extension information', 'v', false, 'search'); 44a8d2f3cbSAndreas Gohr $options->registerArgument('query', 'The keyword(s) to search for', true, 'search'); 45a8d2f3cbSAndreas Gohr 46a8d2f3cbSAndreas Gohr // list 47a8d2f3cbSAndreas Gohr $options->registerCommand('list', 'List installed extensions'); 48a8d2f3cbSAndreas Gohr $options->registerOption('verbose', 'Show detailed extension information', 'v', false, 'list'); 49b9daa2f5SAndreas Gohr $options->registerOption('filter', 'Filter by this status', 'f', 'status', 'list'); 50a8d2f3cbSAndreas Gohr 51a8d2f3cbSAndreas Gohr // upgrade 52a8d2f3cbSAndreas Gohr $options->registerCommand('upgrade', 'Update all installed extensions to their latest versions'); 5371eea990SAndreas Gohr $options->registerOption('git-overwrite', 'Do not skip git-controlled extensions', 'g', false, 'upgrade'); 54a8d2f3cbSAndreas Gohr 55a8d2f3cbSAndreas Gohr // install 56a8d2f3cbSAndreas Gohr $options->registerCommand('install', 'Install or upgrade extensions'); 57dccd6b2bSAndreas Gohr $options->registerArgument( 58dccd6b2bSAndreas Gohr 'extensions...', 59dccd6b2bSAndreas Gohr 'One or more extensions to install. Either by name or download URL', 60dccd6b2bSAndreas Gohr true, 61dccd6b2bSAndreas Gohr 'install' 62e2170488SAndreas Gohr ); 63a8d2f3cbSAndreas Gohr 64a8d2f3cbSAndreas Gohr // uninstall 65a8d2f3cbSAndreas Gohr $options->registerCommand('uninstall', 'Uninstall a new extension'); 66a8d2f3cbSAndreas Gohr $options->registerArgument('extensions...', 'One or more extensions to install', true, 'uninstall'); 67a8d2f3cbSAndreas Gohr 68a8d2f3cbSAndreas Gohr // enable 69a8d2f3cbSAndreas Gohr $options->registerCommand('enable', 'Enable installed extensions'); 70a8d2f3cbSAndreas Gohr $options->registerArgument('extensions...', 'One or more extensions to enable', true, 'enable'); 71a8d2f3cbSAndreas Gohr 72a8d2f3cbSAndreas Gohr // disable 73a8d2f3cbSAndreas Gohr $options->registerCommand('disable', 'Disable installed extensions'); 74a8d2f3cbSAndreas Gohr $options->registerArgument('extensions...', 'One or more extensions to disable', true, 'disable'); 75a8d2f3cbSAndreas Gohr } 76a8d2f3cbSAndreas Gohr 77a8d2f3cbSAndreas Gohr /** @inheritdoc */ 78fe2dcfd5SAndreas Gohr protected function main(Options $options) 79a8d2f3cbSAndreas Gohr { 807c9966a5SAndreas Gohr $repo = Repository::getInstance(); 817c9966a5SAndreas Gohr try { 827c9966a5SAndreas Gohr $repo->checkAccess(); 837c9966a5SAndreas Gohr } catch (ExtensionException $e) { 8479ff0cd0SAndreas Gohr $this->warning($e->getMessage()); 85ed3520eeSAndreas Gohr } 86ed3520eeSAndreas Gohr 87a8d2f3cbSAndreas Gohr switch ($options->getCmd()) { 88a8d2f3cbSAndreas Gohr case 'list': 89b9daa2f5SAndreas Gohr $ret = $this->cmdList($options->getOpt('verbose'), $options->getOpt('filter', '')); 90a8d2f3cbSAndreas Gohr break; 91a8d2f3cbSAndreas Gohr case 'search': 92a8d2f3cbSAndreas Gohr $ret = $this->cmdSearch( 93a8d2f3cbSAndreas Gohr implode(' ', $options->getArgs()), 94a8d2f3cbSAndreas Gohr $options->getOpt('verbose'), 95a8d2f3cbSAndreas Gohr (int)$options->getOpt('max', 10) 96a8d2f3cbSAndreas Gohr ); 97a8d2f3cbSAndreas Gohr break; 98a8d2f3cbSAndreas Gohr case 'install': 99a8d2f3cbSAndreas Gohr $ret = $this->cmdInstall($options->getArgs()); 100a8d2f3cbSAndreas Gohr break; 101a8d2f3cbSAndreas Gohr case 'uninstall': 102a8d2f3cbSAndreas Gohr $ret = $this->cmdUnInstall($options->getArgs()); 103a8d2f3cbSAndreas Gohr break; 104a8d2f3cbSAndreas Gohr case 'enable': 105a8d2f3cbSAndreas Gohr $ret = $this->cmdEnable(true, $options->getArgs()); 106a8d2f3cbSAndreas Gohr break; 107a8d2f3cbSAndreas Gohr case 'disable': 108a8d2f3cbSAndreas Gohr $ret = $this->cmdEnable(false, $options->getArgs()); 109a8d2f3cbSAndreas Gohr break; 110a8d2f3cbSAndreas Gohr case 'upgrade': 11171eea990SAndreas Gohr $ret = $this->cmdUpgrade($options->getOpt('git-overwrite', false)); 112a8d2f3cbSAndreas Gohr break; 113a8d2f3cbSAndreas Gohr default: 114a8d2f3cbSAndreas Gohr echo $options->help(); 115a8d2f3cbSAndreas Gohr $ret = 0; 116a8d2f3cbSAndreas Gohr } 117a8d2f3cbSAndreas Gohr 118a8d2f3cbSAndreas Gohr exit($ret); 119a8d2f3cbSAndreas Gohr } 120a8d2f3cbSAndreas Gohr 121a8d2f3cbSAndreas Gohr /** 122a8d2f3cbSAndreas Gohr * Upgrade all extensions 123a8d2f3cbSAndreas Gohr * 124a8d2f3cbSAndreas Gohr * @return int 125a8d2f3cbSAndreas Gohr */ 12671eea990SAndreas Gohr protected function cmdUpgrade($gitOverwrite) 127a8d2f3cbSAndreas Gohr { 128160d3688SAndreas Gohr $local = new Local(); 129160d3688SAndreas Gohr $extensions = []; 130160d3688SAndreas Gohr foreach ($local->getExtensions() as $ext) { 13171eea990SAndreas Gohr if ($ext->isGitControlled() && !$gitOverwrite) continue; // skip git controlled extensions 1324fd6a1d7SAndreas Gohr if ($ext->isUpdateAvailable()) $extensions[] = $ext->getID(); 133a8d2f3cbSAndreas Gohr } 134160d3688SAndreas Gohr return $this->cmdInstall($extensions); 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 $ok = 0; 147a8d2f3cbSAndreas Gohr foreach ($extensions as $extname) { 148160d3688SAndreas Gohr $ext = Extension::createFromId($extname); 149a8d2f3cbSAndreas Gohr 150160d3688SAndreas Gohr try { 151a8d2f3cbSAndreas Gohr if ($set) { 152160d3688SAndreas Gohr $ext->enable(); 153a8d2f3cbSAndreas Gohr $msg = 'msg_enabled'; 154a8d2f3cbSAndreas Gohr } else { 155160d3688SAndreas Gohr $ext->disable(); 156a8d2f3cbSAndreas Gohr $msg = 'msg_disabled'; 157a8d2f3cbSAndreas Gohr } 158160d3688SAndreas Gohr $this->success(sprintf($this->getLang($msg), $ext->getID())); 159160d3688SAndreas Gohr } catch (ExtensionException $e) { 160160d3688SAndreas Gohr $this->error($e->getMessage()); 161fe2dcfd5SAndreas Gohr ++$ok; 162a8d2f3cbSAndreas Gohr continue; 163a8d2f3cbSAndreas Gohr } 164a8d2f3cbSAndreas Gohr } 165a8d2f3cbSAndreas Gohr 166a8d2f3cbSAndreas Gohr return $ok; 167a8d2f3cbSAndreas Gohr } 168a8d2f3cbSAndreas Gohr 169a8d2f3cbSAndreas Gohr /** 170a8d2f3cbSAndreas Gohr * Uninstall one or more extensions 171a8d2f3cbSAndreas Gohr * 172a8d2f3cbSAndreas Gohr * @param string[] $extensions 173a8d2f3cbSAndreas Gohr * @return int 174a8d2f3cbSAndreas Gohr */ 175a8d2f3cbSAndreas Gohr protected function cmdUnInstall($extensions) 176a8d2f3cbSAndreas Gohr { 177160d3688SAndreas Gohr $installer = new Installer(); 178a8d2f3cbSAndreas Gohr 179a8d2f3cbSAndreas Gohr $ok = 0; 180a8d2f3cbSAndreas Gohr foreach ($extensions as $extname) { 181160d3688SAndreas Gohr $ext = Extension::createFromId($extname); 182a8d2f3cbSAndreas Gohr 183160d3688SAndreas Gohr try { 184160d3688SAndreas Gohr $installer->uninstall($ext); 185a8d2f3cbSAndreas Gohr $this->success(sprintf($this->getLang('msg_delete_success'), $ext->getID())); 186160d3688SAndreas Gohr } catch (ExtensionException $e) { 187160d3688SAndreas Gohr $this->debug($e->getTraceAsString()); 188160d3688SAndreas Gohr $this->error($e->getMessage()); 189160d3688SAndreas Gohr $ok++; // error code is number of failed uninstalls 190a8d2f3cbSAndreas Gohr } 191a8d2f3cbSAndreas Gohr } 192a8d2f3cbSAndreas Gohr return $ok; 193a8d2f3cbSAndreas Gohr } 194a8d2f3cbSAndreas Gohr 195a8d2f3cbSAndreas Gohr /** 196a8d2f3cbSAndreas Gohr * Install one or more extensions 197a8d2f3cbSAndreas Gohr * 198a8d2f3cbSAndreas Gohr * @param string[] $extensions 199a8d2f3cbSAndreas Gohr * @return int 200a8d2f3cbSAndreas Gohr */ 201a8d2f3cbSAndreas Gohr protected function cmdInstall($extensions) 202a8d2f3cbSAndreas Gohr { 203a8d2f3cbSAndreas Gohr $ok = 0; 204a8d2f3cbSAndreas Gohr foreach ($extensions as $extname) { 205160d3688SAndreas Gohr $installer = new Installer(true); 206160d3688SAndreas Gohr 20725d28a01SAndreas Gohr try { 208cc16762dSLocness if (preg_match("/^https?:\/\//i", $extname)) { 20925d28a01SAndreas Gohr $installer->installFromURL($extname, true); 210cc16762dSLocness } else { 21125d28a01SAndreas Gohr $installer->installFromId($extname); 212a8d2f3cbSAndreas Gohr } 21325d28a01SAndreas Gohr } catch (ExtensionException $e) { 21425d28a01SAndreas Gohr $this->debug($e->getTraceAsString()); 2155aaea2b0SLocness $this->error($e->getMessage()); 21625d28a01SAndreas Gohr $ok++; // error code is number of failed installs 2175aaea2b0SLocness } 2185aaea2b0SLocness 21925d28a01SAndreas Gohr $processed = $installer->getProcessed(); 22025d28a01SAndreas Gohr foreach ($processed as $id => $status) { 22125d28a01SAndreas Gohr if ($status == Installer::STATUS_INSTALLED) { 22225d28a01SAndreas Gohr $this->success(sprintf($this->getLang('msg_install_success'), $id)); 22325d28a01SAndreas Gohr } elseif ($status == Installer::STATUS_UPDATED) { 22425d28a01SAndreas Gohr $this->success(sprintf($this->getLang('msg_update_success'), $id)); 225a8d2f3cbSAndreas Gohr } 226cc16762dSLocness } 227160d3688SAndreas Gohr } 22825d28a01SAndreas Gohr 229a8d2f3cbSAndreas Gohr return $ok; 230a8d2f3cbSAndreas Gohr } 231a8d2f3cbSAndreas Gohr 232a8d2f3cbSAndreas Gohr /** 233a8d2f3cbSAndreas Gohr * Search for an extension 234a8d2f3cbSAndreas Gohr * 235a8d2f3cbSAndreas Gohr * @param string $query 236a8d2f3cbSAndreas Gohr * @param bool $showdetails 237a8d2f3cbSAndreas Gohr * @param int $max 238a8d2f3cbSAndreas Gohr * @return int 2399b36c1fcSsplitbrain * @throws Exception 240a8d2f3cbSAndreas Gohr */ 241a8d2f3cbSAndreas Gohr protected function cmdSearch($query, $showdetails, $max) 242a8d2f3cbSAndreas Gohr { 2437c9966a5SAndreas Gohr $repo = Repository::getInstance(); 2447c9966a5SAndreas Gohr $result = $repo->searchExtensions($query); 245a8d2f3cbSAndreas Gohr if ($max) { 246a8d2f3cbSAndreas Gohr $result = array_slice($result, 0, $max); 247a8d2f3cbSAndreas Gohr } 248a8d2f3cbSAndreas Gohr 249a8d2f3cbSAndreas Gohr $this->listExtensions($result, $showdetails); 250a8d2f3cbSAndreas Gohr return 0; 251a8d2f3cbSAndreas Gohr } 252a8d2f3cbSAndreas Gohr 253a8d2f3cbSAndreas Gohr /** 254a8d2f3cbSAndreas Gohr * @param bool $showdetails 255b9daa2f5SAndreas Gohr * @param string $filter 256a8d2f3cbSAndreas Gohr * @return int 2579b36c1fcSsplitbrain * @throws Exception 258a8d2f3cbSAndreas Gohr */ 259b9daa2f5SAndreas Gohr protected function cmdList($showdetails, $filter) 260a8d2f3cbSAndreas Gohr { 2617c9966a5SAndreas Gohr $this->listExtensions((new Local())->getExtensions(), $showdetails, $filter); 262a8d2f3cbSAndreas Gohr return 0; 263a8d2f3cbSAndreas Gohr } 264a8d2f3cbSAndreas Gohr 265a8d2f3cbSAndreas Gohr /** 266a8d2f3cbSAndreas Gohr * List the given extensions 267a8d2f3cbSAndreas Gohr * 2687c9966a5SAndreas Gohr * @param Extension[] $list 269a8d2f3cbSAndreas Gohr * @param bool $details display details 270b9daa2f5SAndreas Gohr * @param string $filter filter for this status 2719b36c1fcSsplitbrain * @throws Exception 272160d3688SAndreas Gohr * @todo break into smaller methods 273a8d2f3cbSAndreas Gohr */ 274b9daa2f5SAndreas Gohr protected function listExtensions($list, $details, $filter = '') 275a8d2f3cbSAndreas Gohr { 276fe2dcfd5SAndreas Gohr $tr = new TableFormatter($this->colors); 2777c9966a5SAndreas Gohr foreach ($list as $ext) { 278a8d2f3cbSAndreas Gohr $status = ''; 279a8d2f3cbSAndreas Gohr if ($ext->isInstalled()) { 280a8d2f3cbSAndreas Gohr $date = $ext->getInstalledVersion(); 281a8d2f3cbSAndreas Gohr $avail = $ext->getLastUpdate(); 282a8d2f3cbSAndreas Gohr $status = 'i'; 283a8d2f3cbSAndreas Gohr if ($avail && $avail > $date) { 284e5688dc7SAndreas Gohr $vcolor = Colors::C_RED; 285b9daa2f5SAndreas Gohr $status .= 'u'; 286a8d2f3cbSAndreas Gohr } else { 287e5688dc7SAndreas Gohr $vcolor = Colors::C_GREEN; 288a8d2f3cbSAndreas Gohr } 289a8d2f3cbSAndreas Gohr if ($ext->isGitControlled()) $status = 'g'; 29099fd248dSAndreas Gohr if ($ext->isBundled()) { 29199fd248dSAndreas Gohr $status = 'b'; 29299fd248dSAndreas Gohr $date = '<bundled>'; 29399fd248dSAndreas Gohr $vcolor = null; 29499fd248dSAndreas Gohr } 295e5688dc7SAndreas Gohr if ($ext->isEnabled()) { 296e5688dc7SAndreas Gohr $ecolor = Colors::C_BROWN; 297e5688dc7SAndreas Gohr } else { 298e5688dc7SAndreas Gohr $ecolor = Colors::C_DARKGRAY; 299e5688dc7SAndreas Gohr $status .= 'd'; 300e5688dc7SAndreas Gohr } 301a8d2f3cbSAndreas Gohr } else { 302d915fa09SAndreas Gohr $ecolor = null; 303a8d2f3cbSAndreas Gohr $date = $ext->getLastUpdate(); 304e5688dc7SAndreas Gohr $vcolor = null; 305a8d2f3cbSAndreas Gohr } 306a8d2f3cbSAndreas Gohr 307b9daa2f5SAndreas Gohr if ($filter && strpos($status, $filter) === false) { 308b9daa2f5SAndreas Gohr continue; 309b9daa2f5SAndreas Gohr } 310a8d2f3cbSAndreas Gohr 3114fd6a1d7SAndreas Gohr $notices = Notice::list($ext); 312*d2d4b908SAndreas Gohr if ($notices[Notice::SECURITY]) $status .= Notice::symbol(Notice::SECURITY); 313*d2d4b908SAndreas Gohr if ($notices[Notice::ERROR]) $status .= Notice::symbol(Notice::ERROR); 314*d2d4b908SAndreas Gohr if ($notices[Notice::WARNING]) $status .= Notice::symbol(Notice::WARNING); 315*d2d4b908SAndreas Gohr if ($notices[Notice::INFO]) $status .= Notice::symbol(Notice::INFO); 31699fd248dSAndreas Gohr 317a8d2f3cbSAndreas Gohr echo $tr->format( 318160d3688SAndreas Gohr [20, 5, 12, '*'], 319a8d2f3cbSAndreas Gohr [ 320a8d2f3cbSAndreas Gohr $ext->getID(), 321a8d2f3cbSAndreas Gohr $status, 322a8d2f3cbSAndreas Gohr $date, 323a8d2f3cbSAndreas Gohr strip_tags(sprintf( 324a8d2f3cbSAndreas Gohr $this->getLang('extensionby'), 325a8d2f3cbSAndreas Gohr $ext->getDisplayName(), 326dccd6b2bSAndreas Gohr $this->colors->wrap($ext->getAuthor(), Colors::C_PURPLE) 327dccd6b2bSAndreas Gohr )) 328a8d2f3cbSAndreas Gohr ], 329a8d2f3cbSAndreas Gohr [ 330e5688dc7SAndreas Gohr $ecolor, 331a8d2f3cbSAndreas Gohr Colors::C_YELLOW, 332e5688dc7SAndreas Gohr $vcolor, 333a8d2f3cbSAndreas Gohr null, 334a8d2f3cbSAndreas Gohr ] 335a8d2f3cbSAndreas Gohr ); 336a8d2f3cbSAndreas Gohr 33799fd248dSAndreas Gohr 338a8d2f3cbSAndreas Gohr if (!$details) continue; 339a8d2f3cbSAndreas Gohr 340a8d2f3cbSAndreas Gohr echo $tr->format( 341160d3688SAndreas Gohr [7, '*'], 342a8d2f3cbSAndreas Gohr ['', $ext->getDescription()], 343a8d2f3cbSAndreas Gohr [null, Colors::C_CYAN] 344a8d2f3cbSAndreas Gohr ); 3454fd6a1d7SAndreas Gohr foreach ($notices as $type => $msgs) { 3464fd6a1d7SAndreas Gohr if (!$msgs) continue; 3474fd6a1d7SAndreas Gohr foreach ($msgs as $msg) { 34899fd248dSAndreas Gohr echo $tr->format( 349160d3688SAndreas Gohr [7, '*'], 350*d2d4b908SAndreas Gohr ['', Notice::symbol($type) . ' ' . $msg], 35199fd248dSAndreas Gohr [null, Colors::C_LIGHTBLUE] 35299fd248dSAndreas Gohr ); 35399fd248dSAndreas Gohr } 354160d3688SAndreas Gohr } 355a8d2f3cbSAndreas Gohr } 356a8d2f3cbSAndreas Gohr } 357a8d2f3cbSAndreas Gohr} 358