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