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 18 protected $force = false; 19 protected $username = ''; 20 21 /** 22 * Register options and arguments on the given $options object 23 * 24 * @param Options $options 25 * @return void 26 */ 27 protected function setup(Options $options) { 28 /* global */ 29 $options->registerOption( 30 'force', 31 'force obtaining a lock for the page (generally bad idea)', 32 'f' 33 ); 34 $options->registerOption( 35 'user', 36 'work as this user. defaults to current CLI user', 37 'u', 38 'username' 39 ); 40 $options->setHelp( 41 'Utility to help command line Dokuwiki page editing, allow ' . 42 'pages to be checked out for editing then committed after changes' 43 ); 44 45 /* checkout command */ 46 $options->registerCommand( 47 'checkout', 48 'Checks out a file from the repository, using the wiki id and obtaining ' . 49 'a lock for the page. ' . "\n" . 50 'If a working_file is specified, this is where the page is copied to. ' . 51 'Otherwise defaults to the same as the wiki page in the current ' . 52 'working directory.' 53 ); 54 $options->registerArgument( 55 'wikipage', 56 'The wiki page to checkout', 57 true, 58 'checkout' 59 ); 60 $options->registerArgument( 61 'workingfile', 62 'How to name the local checkout', 63 false, 64 'checkout' 65 ); 66 67 /* commit command */ 68 $options->registerCommand( 69 'commit', 70 'Checks in the working_file into the repository using the specified ' . 71 'wiki id, archiving the previous version.' 72 ); 73 $options->registerArgument( 74 'workingfile', 75 'The local file to commit', 76 true, 77 'commit' 78 ); 79 $options->registerArgument( 80 'wikipage', 81 'The wiki page to create or update', 82 true, 83 'commit' 84 ); 85 $options->registerOption( 86 'message', 87 'Summary describing the change (required)', 88 'm', 89 'summary', 90 'commit' 91 ); 92 $options->registerOption( 93 'trivial', 94 'minor change', 95 't', 96 false, 97 'commit' 98 ); 99 100 /* lock command */ 101 $options->registerCommand( 102 'lock', 103 'Obtains or updates a lock for a wiki page' 104 ); 105 $options->registerArgument( 106 'wikipage', 107 'The wiki page to lock', 108 true, 109 'lock' 110 ); 111 112 /* unlock command */ 113 $options->registerCommand( 114 'unlock', 115 'Removes a lock for a wiki page.' 116 ); 117 $options->registerArgument( 118 'wikipage', 119 'The wiki page to unlock', 120 true, 121 'unlock' 122 ); 123 124 /* gmeta command */ 125 $options->registerCommand( 126 'getmeta', 127 'Prints metadata value for a page to stdout.' 128 ); 129 $options->registerArgument( 130 'wikipage', 131 'The wiki page to get the metadata for', 132 true, 133 'getmeta' 134 ); 135 $options->registerArgument( 136 'key', 137 'The name of the metadata item to be retrieved.' . "\n" . 138 'If empty, an array of all the metadata items is returned.' ."\n" . 139 'For retrieving items that are stored in sub-arrays, separate the ' . 140 'keys of the different levels by spaces, in quotes, eg "date modified".', 141 false, 142 'getmeta' 143 ); 144 } 145 146 /** 147 * Your main program 148 * 149 * Arguments and options have been parsed when this is run 150 * 151 * @param Options $options 152 * @return void 153 */ 154 protected function main(Options $options) { 155 $this->force = $options->getOpt('force', false); 156 $this->username = $options->getOpt('user', $this->getUser()); 157 158 $command = $options->getCmd(); 159 $args = $options->getArgs(); 160 switch($command) { 161 case 'checkout': 162 $wiki_id = array_shift($args); 163 $localfile = array_shift($args); 164 $this->commandCheckout($wiki_id, $localfile); 165 break; 166 case 'commit': 167 $localfile = array_shift($args); 168 $wiki_id = array_shift($args); 169 $this->commandCommit( 170 $localfile, 171 $wiki_id, 172 $options->getOpt('message', ''), 173 $options->getOpt('trivial', false) 174 ); 175 break; 176 case 'lock': 177 $wiki_id = array_shift($args); 178 $this->obtainLock($wiki_id); 179 $this->success("$wiki_id locked"); 180 break; 181 case 'unlock': 182 $wiki_id = array_shift($args); 183 $this->clearLock($wiki_id); 184 $this->success("$wiki_id unlocked"); 185 break; 186 case 'getmeta': 187 $wiki_id = array_shift($args); 188 $key = trim(array_shift($args)); 189 $meta = p_get_metadata($wiki_id, $key, METADATA_RENDER_UNLIMITED); 190 echo trim(json_encode($meta, JSON_PRETTY_PRINT)); 191 echo "\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() . '/' . 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)), (string) 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