1#!/usr/bin/php 2<?php 3if(!defined('DOKU_INC')) define('DOKU_INC', realpath(dirname(__FILE__).'/../').'/'); 4define('NOSESSION', 1); 5require_once(DOKU_INC.'inc/init.php'); 6 7/** 8 * Checkout and commit pages from the command line while maintaining the history 9 */ 10class PageCLI extends DokuCLI { 11 12 protected $force = false; 13 protected $username = ''; 14 15 /** 16 * Register options and arguments on the given $options object 17 * 18 * @param DokuCLI_Options $options 19 * @return void 20 */ 21 protected function setup(DokuCLI_Options $options) { 22 /* global */ 23 $options->registerOption( 24 'force', 25 'force obtaining a lock for the page (generally bad idea)', 26 'f' 27 ); 28 $options->registerOption( 29 'user', 30 'work as this user. defaults to current CLI user', 31 'u' 32 ); 33 $options->setHelp( 34 'Utility to help command line Dokuwiki page editing, allow '. 35 'pages to be checked out for editing then committed after changes' 36 ); 37 38 /* checkout command */ 39 $options->registerCommand( 40 'checkout', 41 'Checks out a file from the repository, using the wiki id and obtaining '. 42 'a lock for the page. '."\n". 43 'If a working_file is specified, this is where the page is copied to. '. 44 'Otherwise defaults to the same as the wiki page in the current '. 45 'working directory.' 46 ); 47 $options->registerArgument( 48 'wikipage', 49 'The wiki page to checkout', 50 true, 51 'checkout' 52 ); 53 $options->registerArgument( 54 'workingfile', 55 'How to name the local checkout', 56 false, 57 'checkout' 58 ); 59 60 /* commit command */ 61 $options->registerCommand( 62 'commit', 63 'Checks in the working_file into the repository using the specified '. 64 'wiki id, archiving the previous version.' 65 ); 66 $options->registerArgument( 67 'workingfile', 68 'The local file to commit', 69 true, 70 'commit' 71 ); 72 $options->registerArgument( 73 'wikipage', 74 'The wiki page to create or update', 75 true, 76 'commit' 77 ); 78 $options->registerOption( 79 'message', 80 'Summary describing the change (required)', 81 'm', 82 'summary', 83 'commit' 84 ); 85 $options->registerOption( 86 'trivial', 87 'minor change', 88 't', 89 false, 90 'commit' 91 ); 92 93 /* lock command */ 94 $options->registerCommand( 95 'lock', 96 'Obtains or updates a lock for a wiki page' 97 ); 98 $options->registerArgument( 99 'wikipage', 100 'The wiki page to lock', 101 true, 102 'lock' 103 ); 104 105 /* unlock command */ 106 $options->registerCommand( 107 'unlock', 108 'Removes a lock for a wiki page.' 109 ); 110 $options->registerArgument( 111 'wikipage', 112 'The wiki page to unlock', 113 true, 114 'unlock' 115 ); 116 } 117 118 /** 119 * Your main program 120 * 121 * Arguments and options have been parsed when this is run 122 * 123 * @param DokuCLI_Options $options 124 * @return void 125 */ 126 protected function main(DokuCLI_Options $options) { 127 $this->force = $options->getOpt('force', false); 128 $this->username = $options->getOpt('user', $this->getUser()); 129 130 $command = $options->getCmd(); 131 switch($command) { 132 case 'checkout': 133 $wiki_id = array_shift($options->args); 134 $localfile = array_shift($options->args); 135 $this->commandCheckout($wiki_id, $localfile); 136 break; 137 case 'commit': 138 $localfile = array_shift($options->args); 139 $wiki_id = array_shift($options->args); 140 $this->commandCommit( 141 $localfile, 142 $wiki_id, 143 $options->getOpt('message', ''), 144 $options->getOpt('trivial', false) 145 ); 146 break; 147 case 'lock': 148 $wiki_id = array_shift($options->args); 149 $this->obtainLock($wiki_id); 150 $this->success("$wiki_id locked"); 151 break; 152 case 'unlock': 153 $wiki_id = array_shift($options->args); 154 $this->clearLock($wiki_id); 155 $this->success("$wiki_id unlocked"); 156 break; 157 default: 158 echo $options->help(); 159 } 160 } 161 162 /** 163 * Check out a file 164 * 165 * @param string $wiki_id 166 * @param string $localfile 167 */ 168 protected function commandCheckout($wiki_id, $localfile) { 169 global $conf; 170 171 $wiki_id = cleanID($wiki_id); 172 $wiki_fn = wikiFN($wiki_id); 173 174 if(!file_exists($wiki_fn)) { 175 $this->fatal("$wiki_id does not yet exist"); 176 } 177 178 if(empty($localfile)) { 179 $localfile = getcwd().'/'.utf8_basename($wiki_fn); 180 } 181 182 if(!file_exists(dirname($localfile))) { 183 $this->fatal("Directory ".dirname($localfile)." does not exist"); 184 } 185 186 if(stristr(realpath(dirname($localfile)), realpath($conf['datadir'])) !== false) { 187 $this->fatal("Attempt to check out file into data directory - not allowed"); 188 } 189 190 $this->obtainLock($wiki_id); 191 192 if(!copy($wiki_fn, $localfile)) { 193 $this->clearLock($wiki_id); 194 $this->fatal("Unable to copy $wiki_fn to $localfile"); 195 } 196 197 $this->success("$wiki_id > $localfile"); 198 } 199 200 /** 201 * Save a file as a new page revision 202 * 203 * @param string $localfile 204 * @param string $wiki_id 205 * @param string $message 206 * @param bool $minor 207 */ 208 protected function commandCommit($localfile, $wiki_id, $message, $minor) { 209 $wiki_id = cleanID($wiki_id); 210 $message = trim($message); 211 212 if(!file_exists($localfile)) { 213 $this->fatal("$localfile does not exist"); 214 } 215 216 if(!is_readable($localfile)) { 217 $this->fatal("Cannot read from $localfile"); 218 } 219 220 if(!$message) { 221 $this->fatal("Summary message required"); 222 } 223 224 $this->obtainLock($wiki_id); 225 226 saveWikiText($wiki_id, file_get_contents($localfile), $message, $minor); 227 228 $this->clearLock($wiki_id); 229 230 $this->success("$localfile > $wiki_id"); 231 } 232 233 /** 234 * Lock the given page or exit 235 * 236 * @param string $wiki_id 237 */ 238 protected function obtainLock($wiki_id) { 239 if($this->force) $this->deleteLock($wiki_id); 240 241 $_SERVER['REMOTE_USER'] = $this->username; 242 if(checklock($wiki_id)) { 243 $this->error("Page $wiki_id is already locked by another user"); 244 exit(1); 245 } 246 247 lock($wiki_id); 248 249 $_SERVER['REMOTE_USER'] = '_'.$this->username.'_'; 250 if(checklock($wiki_id) != $this->username) { 251 $this->error("Unable to obtain lock for $wiki_id "); 252 var_dump(checklock($wiki_id)); 253 exit(1); 254 } 255 } 256 257 /** 258 * Clear the lock on the given page 259 * 260 * @param string $wiki_id 261 */ 262 protected function clearLock($wiki_id) { 263 if($this->force) $this->deleteLock($wiki_id); 264 265 $_SERVER['REMOTE_USER'] = $this->username; 266 if(checklock($wiki_id)) { 267 $this->error("Page $wiki_id is locked by another user"); 268 exit(1); 269 } 270 271 unlock($wiki_id); 272 273 if(file_exists(wikiLockFN($wiki_id))) { 274 $this->error("Unable to clear lock for $wiki_id"); 275 exit(1); 276 } 277 } 278 279 /** 280 * Forcefully remove a lock on the page given 281 * 282 * @param string $wiki_id 283 */ 284 protected function deleteLock($wiki_id) { 285 $wikiLockFN = wikiLockFN($wiki_id); 286 287 if(file_exists($wikiLockFN)) { 288 if(!unlink($wikiLockFN)) { 289 $this->error("Unable to delete $wikiLockFN"); 290 exit(1); 291 } 292 } 293 } 294 295 /** 296 * Get the current user's username from the environment 297 * 298 * @return string 299 */ 300 protected function getUser() { 301 $user = getenv('USER'); 302 if(empty ($user)) { 303 $user = getenv('USERNAME'); 304 } else { 305 return $user; 306 } 307 if(empty ($user)) { 308 $user = 'admin'; 309 } 310 return $user; 311 } 312} 313 314 315// Main 316$cli = new PageCLI(); 317$cli->run();