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