You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

393 lines
12 KiB

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