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