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 { 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 { 156 $this->force = $options->getOpt('force', false); 157 $this->username = $options->getOpt('user', $this->getUser()); 158 159 $command = $options->getCmd(); 160 $args = $options->getArgs(); 161 switch ($command) { 162 case 'checkout': 163 $wiki_id = array_shift($args); 164 $localfile = array_shift($args); 165 $this->commandCheckout($wiki_id, $localfile); 166 break; 167 case 'commit': 168 $localfile = array_shift($args); 169 $wiki_id = array_shift($args); 170 $this->commandCommit( 171 $localfile, 172 $wiki_id, 173 $options->getOpt('message', ''), 174 $options->getOpt('trivial', false) 175 ); 176 break; 177 case 'lock': 178 $wiki_id = array_shift($args); 179 $this->obtainLock($wiki_id); 180 $this->success("$wiki_id locked"); 181 break; 182 case 'unlock': 183 $wiki_id = array_shift($args); 184 $this->clearLock($wiki_id); 185 $this->success("$wiki_id unlocked"); 186 break; 187 case 'getmeta': 188 $wiki_id = array_shift($args); 189 $key = trim(array_shift($args)); 190 $meta = p_get_metadata($wiki_id, $key, METADATA_RENDER_UNLIMITED); 191 echo trim(json_encode($meta, JSON_PRETTY_PRINT)); 192 echo "\n"; 193 break; 194 default: 195 echo $options->help(); 196 } 197 } 198 199 /** 200 * Check out a file 201 * 202 * @param string $wiki_id 203 * @param string $localfile 204 */ 205 protected function commandCheckout($wiki_id, $localfile) 206 { 207 global $conf; 208 209 $wiki_id = cleanID($wiki_id); 210 $wiki_fn = wikiFN($wiki_id); 211 212 if (!file_exists($wiki_fn)) { 213 $this->fatal("$wiki_id does not yet exist"); 214 } 215 216 if (empty($localfile)) { 217 $localfile = getcwd() . '/' . PhpString::basename($wiki_fn); 218 } 219 220 if (!file_exists(dirname($localfile))) { 221 $this->fatal("Directory " . dirname($localfile) . " does not exist"); 222 } 223 224 if (stristr(realpath(dirname($localfile)), (string) realpath($conf['datadir'])) !== false) { 225 $this->fatal("Attempt to check out file into data directory - not allowed"); 226 } 227 228 $this->obtainLock($wiki_id); 229 230 if (!copy($wiki_fn, $localfile)) { 231 $this->clearLock($wiki_id); 232 $this->fatal("Unable to copy $wiki_fn to $localfile"); 233 } 234 235 $this->success("$wiki_id > $localfile"); 236 } 237 238 /** 239 * Save a file as a new page revision 240 * 241 * @param string $localfile 242 * @param string $wiki_id 243 * @param string $message 244 * @param bool $minor 245 */ 246 protected function commandCommit($localfile, $wiki_id, $message, $minor) 247 { 248 $wiki_id = cleanID($wiki_id); 249 $message = trim($message); 250 251 if (!file_exists($localfile)) { 252 $this->fatal("$localfile does not exist"); 253 } 254 255 if (!is_readable($localfile)) { 256 $this->fatal("Cannot read from $localfile"); 257 } 258 259 if (!$message) { 260 $this->fatal("Summary message required"); 261 } 262 263 $this->obtainLock($wiki_id); 264 265 saveWikiText($wiki_id, file_get_contents($localfile), $message, $minor); 266 267 $this->clearLock($wiki_id); 268 269 $this->success("$localfile > $wiki_id"); 270 } 271 272 /** 273 * Lock the given page or exit 274 * 275 * @param string $wiki_id 276 */ 277 protected function obtainLock($wiki_id) 278 { 279 if ($this->force) $this->deleteLock($wiki_id); 280 281 $_SERVER['REMOTE_USER'] = $this->username; 282 283 if (checklock($wiki_id)) { 284 $this->error("Page $wiki_id is already locked by another user"); 285 exit(1); 286 } 287 288 lock($wiki_id); 289 290 if (checklock($wiki_id)) { 291 $this->error("Unable to obtain lock for $wiki_id "); 292 var_dump(checklock($wiki_id)); 293 exit(1); 294 } 295 } 296 297 /** 298 * Clear the lock on the given page 299 * 300 * @param string $wiki_id 301 */ 302 protected function clearLock($wiki_id) 303 { 304 if ($this->force) $this->deleteLock($wiki_id); 305 306 $_SERVER['REMOTE_USER'] = $this->username; 307 if (checklock($wiki_id)) { 308 $this->error("Page $wiki_id is locked by another user"); 309 exit(1); 310 } 311 312 unlock($wiki_id); 313 314 if (file_exists(wikiLockFN($wiki_id))) { 315 $this->error("Unable to clear lock for $wiki_id"); 316 exit(1); 317 } 318 } 319 320 /** 321 * Forcefully remove a lock on the page given 322 * 323 * @param string $wiki_id 324 */ 325 protected function deleteLock($wiki_id) 326 { 327 $wikiLockFN = wikiLockFN($wiki_id); 328 329 if (file_exists($wikiLockFN)) { 330 if (!unlink($wikiLockFN)) { 331 $this->error("Unable to delete $wikiLockFN"); 332 exit(1); 333 } 334 } 335 } 336 337 /** 338 * Get the current user's username from the environment 339 * 340 * @return string 341 */ 342 protected function getUser() 343 { 344 $user = getenv('USER'); 345 if (empty($user)) { 346 $user = getenv('USERNAME'); 347 } else { 348 return $user; 349 } 350 if (empty($user)) { 351 $user = 'admin'; 352 } 353 return $user; 354 } 355} 356 357// Main 358$cli = new PageCLI(); 359$cli->run(); 360