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