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