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 123 /** 124 * Your main program 125 * 126 * Arguments and options have been parsed when this is run 127 * 128 * @param Options $options 129 * @return void 130 */ 131 protected function main(Options $options) { 132 $this->force = $options->getOpt('force', false); 133 $this->username = $options->getOpt('user', $this->getUser()); 134 135 $command = $options->getCmd(); 136 $args = $options->getArgs(); 137 switch($command) { 138 case 'checkout': 139 $wiki_id = array_shift($args); 140 $localfile = array_shift($args); 141 $this->commandCheckout($wiki_id, $localfile); 142 break; 143 case 'commit': 144 $localfile = array_shift($args); 145 $wiki_id = array_shift($args); 146 $this->commandCommit( 147 $localfile, 148 $wiki_id, 149 $options->getOpt('message', ''), 150 $options->getOpt('trivial', false) 151 ); 152 break; 153 case 'lock': 154 $wiki_id = array_shift($args); 155 $this->obtainLock($wiki_id); 156 $this->success("$wiki_id locked"); 157 break; 158 case 'unlock': 159 $wiki_id = array_shift($args); 160 $this->clearLock($wiki_id); 161 $this->success("$wiki_id unlocked"); 162 break; 163 default: 164 echo $options->help(); 165 } 166 } 167 168 /** 169 * Check out a file 170 * 171 * @param string $wiki_id 172 * @param string $localfile 173 */ 174 protected function commandCheckout($wiki_id, $localfile) { 175 global $conf; 176 177 $wiki_id = cleanID($wiki_id); 178 $wiki_fn = wikiFN($wiki_id); 179 180 if(!file_exists($wiki_fn)) { 181 $this->fatal("$wiki_id does not yet exist"); 182 } 183 184 if(empty($localfile)) { 185 $localfile = getcwd() . '/' . \dokuwiki\Utf8\PhpString::basename($wiki_fn); 186 } 187 188 if(!file_exists(dirname($localfile))) { 189 $this->fatal("Directory " . dirname($localfile) . " does not exist"); 190 } 191 192 if(stristr(realpath(dirname($localfile)), realpath($conf['datadir'])) !== false) { 193 $this->fatal("Attempt to check out file into data directory - not allowed"); 194 } 195 196 $this->obtainLock($wiki_id); 197 198 if(!copy($wiki_fn, $localfile)) { 199 $this->clearLock($wiki_id); 200 $this->fatal("Unable to copy $wiki_fn to $localfile"); 201 } 202 203 $this->success("$wiki_id > $localfile"); 204 } 205 206 /** 207 * Save a file as a new page revision 208 * 209 * @param string $localfile 210 * @param string $wiki_id 211 * @param string $message 212 * @param bool $minor 213 */ 214 protected function commandCommit($localfile, $wiki_id, $message, $minor) { 215 $wiki_id = cleanID($wiki_id); 216 $message = trim($message); 217 218 if(!file_exists($localfile)) { 219 $this->fatal("$localfile does not exist"); 220 } 221 222 if(!is_readable($localfile)) { 223 $this->fatal("Cannot read from $localfile"); 224 } 225 226 if(!$message) { 227 $this->fatal("Summary message required"); 228 } 229 230 $this->obtainLock($wiki_id); 231 232 saveWikiText($wiki_id, file_get_contents($localfile), $message, $minor); 233 234 $this->clearLock($wiki_id); 235 236 $this->success("$localfile > $wiki_id"); 237 } 238 239 /** 240 * Lock the given page or exit 241 * 242 * @param string $wiki_id 243 */ 244 protected function obtainLock($wiki_id) { 245 if($this->force) $this->deleteLock($wiki_id); 246 247 $_SERVER['REMOTE_USER'] = $this->username; 248 249 if(checklock($wiki_id)) { 250 $this->error("Page $wiki_id is already locked by another user"); 251 exit(1); 252 } 253 254 lock($wiki_id); 255 256 if(checklock($wiki_id)) { 257 $this->error("Unable to obtain lock for $wiki_id "); 258 var_dump(checklock($wiki_id)); 259 exit(1); 260 } 261 } 262 263 /** 264 * Clear the lock on the given page 265 * 266 * @param string $wiki_id 267 */ 268 protected function clearLock($wiki_id) { 269 if($this->force) $this->deleteLock($wiki_id); 270 271 $_SERVER['REMOTE_USER'] = $this->username; 272 if(checklock($wiki_id)) { 273 $this->error("Page $wiki_id is locked by another user"); 274 exit(1); 275 } 276 277 unlock($wiki_id); 278 279 if(file_exists(wikiLockFN($wiki_id))) { 280 $this->error("Unable to clear lock for $wiki_id"); 281 exit(1); 282 } 283 } 284 285 /** 286 * Forcefully remove a lock on the page given 287 * 288 * @param string $wiki_id 289 */ 290 protected function deleteLock($wiki_id) { 291 $wikiLockFN = wikiLockFN($wiki_id); 292 293 if(file_exists($wikiLockFN)) { 294 if(!unlink($wikiLockFN)) { 295 $this->error("Unable to delete $wikiLockFN"); 296 exit(1); 297 } 298 } 299 } 300 301 /** 302 * Get the current user's username from the environment 303 * 304 * @return string 305 */ 306 protected function getUser() { 307 $user = getenv('USER'); 308 if(empty ($user)) { 309 $user = getenv('USERNAME'); 310 } else { 311 return $user; 312 } 313 if(empty ($user)) { 314 $user = 'admin'; 315 } 316 return $user; 317 } 318} 319 320// Main 321$cli = new PageCLI(); 322$cli->run(); 323