dwpage.php 9.6 KB

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