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