dokuwiki/lib/plugins/extension/cli.php
<?php
use dokuwiki\Extension\CLIPlugin;
use dokuwiki\plugin\extension\Exception as ExtensionException;
use dokuwiki\plugin\extension\Extension;
use dokuwiki\plugin\extension\Installer;
use dokuwiki\plugin\extension\Local;
use dokuwiki\plugin\extension\Notice;
use dokuwiki\plugin\extension\Repository;
use splitbrain\phpcli\Colors;
use splitbrain\phpcli\Exception;
use splitbrain\phpcli\Options;
use splitbrain\phpcli\TableFormatter;
/**
* Class cli_plugin_extension
*
* Command Line component for the extension manager
*
* @license GPL2
* @author Andreas Gohr <andi@splitbrain.org>
*/
class cli_plugin_extension extends CLIPlugin
{
/** @inheritdoc */
protected function setup(Options $options)
{
// general setup
$options->useCompactHelp();
$options->setHelp(
"Manage plugins and templates for this DokuWiki instance\n\n" .
"Status codes:\n" .
" i - installed " . Notice::symbol(Notice::SECURITY) . " - security issue\n" .
" b - bundled with DokuWiki " . Notice::symbol(Notice::ERROR) . " - extension error\n" .
" g - installed via git " . Notice::symbol(Notice::WARNING) . " - extension warning\n" .
" d - disabled " . Notice::symbol(Notice::INFO) . " - extension info\n" .
" u - update available\n"
);
// search
$options->registerCommand('search', 'Search for an extension');
$options->registerOption('max', 'Maximum number of results (default 10)', 'm', 'number', 'search');
$options->registerOption('verbose', 'Show detailed extension information', 'v', false, 'search');
$options->registerArgument('query', 'The keyword(s) to search for', true, 'search');
// list
$options->registerCommand('list', 'List installed extensions');
$options->registerOption('verbose', 'Show detailed extension information', 'v', false, 'list');
$options->registerOption('filter', 'Filter by this status', 'f', 'status', 'list');
// upgrade
$options->registerCommand('upgrade', 'Update all installed extensions to their latest versions');
$options->registerOption('git-overwrite', 'Do not skip git-controlled extensions', 'g', false, 'upgrade');
// install
$options->registerCommand('install', 'Install or upgrade extensions');
$options->registerArgument(
'extensions...',
'One or more extensions to install. Either by name or download URL',
true,
'install'
);
// uninstall
$options->registerCommand('uninstall', 'Uninstall a new extension');
$options->registerArgument('extensions...', 'One or more extensions to install', true, 'uninstall');
// enable
$options->registerCommand('enable', 'Enable installed extensions');
$options->registerArgument('extensions...', 'One or more extensions to enable', true, 'enable');
// disable
$options->registerCommand('disable', 'Disable installed extensions');
$options->registerArgument('extensions...', 'One or more extensions to disable', true, 'disable');
}
/** @inheritdoc */
protected function main(Options $options)
{
$repo = Repository::getInstance();
try {
$repo->checkAccess();
} catch (ExtensionException $e) {
$this->warning($e->getMessage());
}
switch ($options->getCmd()) {
case 'list':
$ret = $this->cmdList($options->getOpt('verbose'), $options->getOpt('filter', ''));
break;
case 'search':
$ret = $this->cmdSearch(
implode(' ', $options->getArgs()),
$options->getOpt('verbose'),
(int)$options->getOpt('max', 10)
);
break;
case 'install':
$ret = $this->cmdInstall($options->getArgs());
break;
case 'uninstall':
$ret = $this->cmdUnInstall($options->getArgs());
break;
case 'enable':
$ret = $this->cmdEnable(true, $options->getArgs());
break;
case 'disable':
$ret = $this->cmdEnable(false, $options->getArgs());
break;
case 'upgrade':
$ret = $this->cmdUpgrade($options->getOpt('git-overwrite', false));
break;
default:
echo $options->help();
$ret = 0;
}
exit($ret);
}
/**
* Upgrade all extensions
*
* @return int
*/
protected function cmdUpgrade($gitOverwrite)
{
$local = new Local();
$extensions = [];
foreach ($local->getExtensions() as $ext) {
if ($ext->isGitControlled() && !$gitOverwrite) continue; // skip git controlled extensions
if ($ext->isUpdateAvailable()) $extensions[] = $ext->getID();
}
return $this->cmdInstall($extensions);
}
/**
* Enable or disable one or more extensions
*
* @param bool $set
* @param string[] $extensions
* @return int
*/
protected function cmdEnable($set, $extensions)
{
$ok = 0;
foreach ($extensions as $extname) {
$ext = Extension::createFromId($extname);
try {
if ($set) {
$ext->enable();
$msg = 'msg_enabled';
} else {
$ext->disable();
$msg = 'msg_disabled';
}
$this->success(sprintf($this->getLang($msg), $ext->getID()));
} catch (ExtensionException $e) {
$this->error($e->getMessage());
++$ok;
continue;
}
}
return $ok;
}
/**
* Uninstall one or more extensions
*
* @param string[] $extensions
* @return int
*/
protected function cmdUnInstall($extensions)
{
$installer = new Installer();
$ok = 0;
foreach ($extensions as $extname) {
$ext = Extension::createFromId($extname);
try {
$installer->uninstall($ext);
$this->success(sprintf($this->getLang('msg_delete_success'), $ext->getID()));
} catch (ExtensionException $e) {
$this->debug($e->getTraceAsString());
$this->error($e->getMessage());
$ok++; // error code is number of failed uninstalls
}
}
return $ok;
}
/**
* Install one or more extensions
*
* @param string[] $extensions
* @return int
*/
protected function cmdInstall($extensions)
{
$ok = 0;
foreach ($extensions as $extname) {
$installer = new Installer(true);
try {
if (preg_match("/^https?:\/\//i", $extname)) {
$installer->installFromURL($extname, true);
} else {
$installer->installFromId($extname);
}
} catch (ExtensionException $e) {
$this->debug($e->getTraceAsString());
$this->error($e->getMessage());
$ok++; // error code is number of failed installs
}
$processed = $installer->getProcessed();
foreach ($processed as $id => $status) {
if ($status == Installer::STATUS_INSTALLED) {
$this->success(sprintf($this->getLang('msg_install_success'), $id));
} elseif ($status == Installer::STATUS_UPDATED) {
$this->success(sprintf($this->getLang('msg_update_success'), $id));
}
}
}
return $ok;
}
/**
* Search for an extension
*
* @param string $query
* @param bool $showdetails
* @param int $max
* @return int
* @throws Exception
*/
protected function cmdSearch($query, $showdetails, $max)
{
$repo = Repository::getInstance();
$result = $repo->searchExtensions($query);
if ($max) {
$result = array_slice($result, 0, $max);
}
$this->listExtensions($result, $showdetails);
return 0;
}
/**
* @param bool $showdetails
* @param string $filter
* @return int
* @throws Exception
*/
protected function cmdList($showdetails, $filter)
{
$extensions = (new Local())->getExtensions();
// initialize remote data in one go
Repository::getInstance()->initExtensions(array_keys($extensions));
$this->listExtensions($extensions, $showdetails, $filter);
return 0;
}
/**
* List the given extensions
*
* @param Extension[] $list
* @param bool $details display details
* @param string $filter filter for this status
* @throws Exception
* @todo break into smaller methods
*/
protected function listExtensions($list, $details, $filter = '')
{
$tr = new TableFormatter($this->colors);
foreach ($list as $ext) {
$status = '';
if ($ext->isInstalled()) {
$date = $ext->getInstalledVersion();
$avail = $ext->getLastUpdate();
$status = 'i';
if ($avail && $avail > $date) {
$vcolor = Colors::C_RED;
$status .= 'u';
} else {
$vcolor = Colors::C_GREEN;
}
if ($ext->isGitControlled()) $status = 'g';
if ($ext->isBundled()) {
$status = 'b';
$date = '<bundled>';
$vcolor = null;
}
if ($ext->isEnabled()) {
$ecolor = Colors::C_BROWN;
} else {
$ecolor = Colors::C_DARKGRAY;
$status .= 'd';
}
} else {
$ecolor = null;
$date = $ext->getLastUpdate();
$vcolor = null;
}
if ($filter && strpos($status, $filter) === false) {
continue;
}
$notices = Notice::list($ext);
if ($notices[Notice::SECURITY]) $status .= Notice::symbol(Notice::SECURITY);
if ($notices[Notice::ERROR]) $status .= Notice::symbol(Notice::ERROR);
if ($notices[Notice::WARNING]) $status .= Notice::symbol(Notice::WARNING);
if ($notices[Notice::INFO]) $status .= Notice::symbol(Notice::INFO);
echo $tr->format(
[20, 5, 12, '*'],
[
$ext->getID(),
$status,
$date,
strip_tags(sprintf(
$this->getLang('extensionby'),
$ext->getDisplayName(),
$this->colors->wrap($ext->getAuthor(), Colors::C_PURPLE)
))
],
[
$ecolor,
Colors::C_YELLOW,
$vcolor,
null,
]
);
if (!$details) continue;
echo $tr->format(
[7, '*'],
['', $ext->getDescription()],
[null, Colors::C_CYAN]
);
foreach ($notices as $type => $msgs) {
if (!$msgs) continue;
foreach ($msgs as $msg) {
echo $tr->format(
[7, '*'],
['', Notice::symbol($type) . ' ' . $msg],
[null, Colors::C_LIGHTBLUE]
);
}
}
}
}
}