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