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.
 
 
 
 
 

360 lines
9.7 KiB

  1. #!/usr/bin/env php
  2. <?php
  3. use splitbrain\phpcli\CLI;
  4. use splitbrain\phpcli\Options;
  5. use dokuwiki\Utf8\PhpString;
  6. if (!defined('DOKU_INC')) define('DOKU_INC', realpath(__DIR__ . '/../') . '/');
  7. define('NOSESSION', 1);
  8. require_once(DOKU_INC . 'inc/init.php');
  9. /**
  10. * Checkout and commit pages from the command line while maintaining the history
  11. */
  12. class PageCLI extends CLI
  13. {
  14. protected $force = false;
  15. protected $username = '';
  16. /**
  17. * Register options and arguments on the given $options object
  18. *
  19. * @param Options $options
  20. * @return void
  21. */
  22. protected function setup(Options $options)
  23. {
  24. /* global */
  25. $options->registerOption(
  26. 'force',
  27. 'force obtaining a lock for the page (generally bad idea)',
  28. 'f'
  29. );
  30. $options->registerOption(
  31. 'user',
  32. 'work as this user. defaults to current CLI user',
  33. 'u',
  34. 'username'
  35. );
  36. $options->setHelp(
  37. 'Utility to help command line Dokuwiki page editing, allow ' .
  38. 'pages to be checked out for editing then committed after changes'
  39. );
  40. /* checkout command */
  41. $options->registerCommand(
  42. 'checkout',
  43. 'Checks out a file from the repository, using the wiki id and obtaining ' .
  44. 'a lock for the page. ' . "\n" .
  45. 'If a working_file is specified, this is where the page is copied to. ' .
  46. 'Otherwise defaults to the same as the wiki page in the current ' .
  47. 'working directory.'
  48. );
  49. $options->registerArgument(
  50. 'wikipage',
  51. 'The wiki page to checkout',
  52. true,
  53. 'checkout'
  54. );
  55. $options->registerArgument(
  56. 'workingfile',
  57. 'How to name the local checkout',
  58. false,
  59. 'checkout'
  60. );
  61. /* commit command */
  62. $options->registerCommand(
  63. 'commit',
  64. 'Checks in the working_file into the repository using the specified ' .
  65. 'wiki id, archiving the previous version.'
  66. );
  67. $options->registerArgument(
  68. 'workingfile',
  69. 'The local file to commit',
  70. true,
  71. 'commit'
  72. );
  73. $options->registerArgument(
  74. 'wikipage',
  75. 'The wiki page to create or update',
  76. true,
  77. 'commit'
  78. );
  79. $options->registerOption(
  80. 'message',
  81. 'Summary describing the change (required)',
  82. 'm',
  83. 'summary',
  84. 'commit'
  85. );
  86. $options->registerOption(
  87. 'trivial',
  88. 'minor change',
  89. 't',
  90. false,
  91. 'commit'
  92. );
  93. /* lock command */
  94. $options->registerCommand(
  95. 'lock',
  96. 'Obtains or updates a lock for a wiki page'
  97. );
  98. $options->registerArgument(
  99. 'wikipage',
  100. 'The wiki page to lock',
  101. true,
  102. 'lock'
  103. );
  104. /* unlock command */
  105. $options->registerCommand(
  106. 'unlock',
  107. 'Removes a lock for a wiki page.'
  108. );
  109. $options->registerArgument(
  110. 'wikipage',
  111. 'The wiki page to unlock',
  112. true,
  113. 'unlock'
  114. );
  115. /* gmeta command */
  116. $options->registerCommand(
  117. 'getmeta',
  118. 'Prints metadata value for a page to stdout.'
  119. );
  120. $options->registerArgument(
  121. 'wikipage',
  122. 'The wiki page to get the metadata for',
  123. true,
  124. 'getmeta'
  125. );
  126. $options->registerArgument(
  127. 'key',
  128. 'The name of the metadata item to be retrieved.' . "\n" .
  129. 'If empty, an array of all the metadata items is returned.' . "\n" .
  130. 'For retrieving items that are stored in sub-arrays, separate the ' .
  131. 'keys of the different levels by spaces, in quotes, eg "date modified".',
  132. false,
  133. 'getmeta'
  134. );
  135. }
  136. /**
  137. * Your main program
  138. *
  139. * Arguments and options have been parsed when this is run
  140. *
  141. * @param Options $options
  142. * @return void
  143. */
  144. protected function main(Options $options)
  145. {
  146. $this->force = $options->getOpt('force', false);
  147. $this->username = $options->getOpt('user', $this->getUser());
  148. $command = $options->getCmd();
  149. $args = $options->getArgs();
  150. switch ($command) {
  151. case 'checkout':
  152. $wiki_id = array_shift($args);
  153. $localfile = array_shift($args);
  154. $this->commandCheckout($wiki_id, $localfile);
  155. break;
  156. case 'commit':
  157. $localfile = array_shift($args);
  158. $wiki_id = array_shift($args);
  159. $this->commandCommit(
  160. $localfile,
  161. $wiki_id,
  162. $options->getOpt('message', ''),
  163. $options->getOpt('trivial', false)
  164. );
  165. break;
  166. case 'lock':
  167. $wiki_id = array_shift($args);
  168. $this->obtainLock($wiki_id);
  169. $this->success("$wiki_id locked");
  170. break;
  171. case 'unlock':
  172. $wiki_id = array_shift($args);
  173. $this->clearLock($wiki_id);
  174. $this->success("$wiki_id unlocked");
  175. break;
  176. case 'getmeta':
  177. $wiki_id = array_shift($args);
  178. $key = trim(array_shift($args));
  179. $meta = p_get_metadata($wiki_id, $key, METADATA_RENDER_UNLIMITED);
  180. echo trim(json_encode($meta, JSON_PRETTY_PRINT));
  181. echo "\n";
  182. break;
  183. default:
  184. echo $options->help();
  185. }
  186. }
  187. /**
  188. * Check out a file
  189. *
  190. * @param string $wiki_id
  191. * @param string $localfile
  192. */
  193. protected function commandCheckout($wiki_id, $localfile)
  194. {
  195. global $conf;
  196. $wiki_id = cleanID($wiki_id);
  197. $wiki_fn = wikiFN($wiki_id);
  198. if (!file_exists($wiki_fn)) {
  199. $this->fatal("$wiki_id does not yet exist");
  200. }
  201. if (empty($localfile)) {
  202. $localfile = getcwd() . '/' . PhpString::basename($wiki_fn);
  203. }
  204. if (!file_exists(dirname($localfile))) {
  205. $this->fatal("Directory " . dirname($localfile) . " does not exist");
  206. }
  207. if (stristr(realpath(dirname($localfile)), (string) realpath($conf['datadir'])) !== false) {
  208. $this->fatal("Attempt to check out file into data directory - not allowed");
  209. }
  210. $this->obtainLock($wiki_id);
  211. if (!copy($wiki_fn, $localfile)) {
  212. $this->clearLock($wiki_id);
  213. $this->fatal("Unable to copy $wiki_fn to $localfile");
  214. }
  215. $this->success("$wiki_id > $localfile");
  216. }
  217. /**
  218. * Save a file as a new page revision
  219. *
  220. * @param string $localfile
  221. * @param string $wiki_id
  222. * @param string $message
  223. * @param bool $minor
  224. */
  225. protected function commandCommit($localfile, $wiki_id, $message, $minor)
  226. {
  227. $wiki_id = cleanID($wiki_id);
  228. $message = trim($message);
  229. if (!file_exists($localfile)) {
  230. $this->fatal("$localfile does not exist");
  231. }
  232. if (!is_readable($localfile)) {
  233. $this->fatal("Cannot read from $localfile");
  234. }
  235. if (!$message) {
  236. $this->fatal("Summary message required");
  237. }
  238. $this->obtainLock($wiki_id);
  239. saveWikiText($wiki_id, file_get_contents($localfile), $message, $minor);
  240. $this->clearLock($wiki_id);
  241. $this->success("$localfile > $wiki_id");
  242. }
  243. /**
  244. * Lock the given page or exit
  245. *
  246. * @param string $wiki_id
  247. */
  248. protected function obtainLock($wiki_id)
  249. {
  250. if ($this->force) $this->deleteLock($wiki_id);
  251. $_SERVER['REMOTE_USER'] = $this->username;
  252. if (checklock($wiki_id)) {
  253. $this->error("Page $wiki_id is already locked by another user");
  254. exit(1);
  255. }
  256. lock($wiki_id);
  257. if (checklock($wiki_id)) {
  258. $this->error("Unable to obtain lock for $wiki_id ");
  259. var_dump(checklock($wiki_id));
  260. exit(1);
  261. }
  262. }
  263. /**
  264. * Clear the lock on the given page
  265. *
  266. * @param string $wiki_id
  267. */
  268. protected function clearLock($wiki_id)
  269. {
  270. if ($this->force) $this->deleteLock($wiki_id);
  271. $_SERVER['REMOTE_USER'] = $this->username;
  272. if (checklock($wiki_id)) {
  273. $this->error("Page $wiki_id is locked by another user");
  274. exit(1);
  275. }
  276. unlock($wiki_id);
  277. if (file_exists(wikiLockFN($wiki_id))) {
  278. $this->error("Unable to clear lock for $wiki_id");
  279. exit(1);
  280. }
  281. }
  282. /**
  283. * Forcefully remove a lock on the page given
  284. *
  285. * @param string $wiki_id
  286. */
  287. protected function deleteLock($wiki_id)
  288. {
  289. $wikiLockFN = wikiLockFN($wiki_id);
  290. if (file_exists($wikiLockFN)) {
  291. if (!unlink($wikiLockFN)) {
  292. $this->error("Unable to delete $wikiLockFN");
  293. exit(1);
  294. }
  295. }
  296. }
  297. /**
  298. * Get the current user's username from the environment
  299. *
  300. * @return string
  301. */
  302. protected function getUser()
  303. {
  304. $user = getenv('USER');
  305. if (empty($user)) {
  306. $user = getenv('USERNAME');
  307. } else {
  308. return $user;
  309. }
  310. if (empty($user)) {
  311. $user = 'admin';
  312. }
  313. return $user;
  314. }
  315. }
  316. // Main
  317. $cli = new PageCLI();
  318. $cli->run();