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