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