1cbfa4829SPhy#!/usr/bin/env php 21caeb00aSHarry Fuecks<?php 3cbeaa4a0SAndreas Gohr 4cbeaa4a0SAndreas Gohruse splitbrain\phpcli\CLI; 5cbeaa4a0SAndreas Gohruse splitbrain\phpcli\Options; 6b1f206e1SAndreas Gohruse dokuwiki\Utf8\PhpString; 7cbeaa4a0SAndreas Gohr 8b1f206e1SAndreas Gohrif (!defined('DOKU_INC')) define('DOKU_INC', realpath(__DIR__ . '/../') . '/'); 9e82b082eSAndreas Gohrdefine('NOSESSION', 1); 10e82b082eSAndreas Gohrrequire_once(DOKU_INC . 'inc/init.php'); 111caeb00aSHarry Fuecks 12e82b082eSAndreas Gohr/** 13e82b082eSAndreas Gohr * Checkout and commit pages from the command line while maintaining the history 14e82b082eSAndreas Gohr */ 158c7c53b0SAndreas Gohrclass PageCLI extends CLI 168c7c53b0SAndreas Gohr{ 17e82b082eSAndreas Gohr protected $force = false; 18e82b082eSAndreas Gohr protected $username = ''; 19e82b082eSAndreas Gohr 20e82b082eSAndreas Gohr /** 21e82b082eSAndreas Gohr * Register options and arguments on the given $options object 22e82b082eSAndreas Gohr * 23cbeaa4a0SAndreas Gohr * @param Options $options 24e82b082eSAndreas Gohr * @return void 25e82b082eSAndreas Gohr */ 26d868eb89SAndreas Gohr protected function setup(Options $options) 27d868eb89SAndreas Gohr { 28e82b082eSAndreas Gohr /* global */ 29e82b082eSAndreas Gohr $options->registerOption( 30e82b082eSAndreas Gohr 'force', 31e82b082eSAndreas Gohr 'force obtaining a lock for the page (generally bad idea)', 32e82b082eSAndreas Gohr 'f' 33e82b082eSAndreas Gohr ); 34e82b082eSAndreas Gohr $options->registerOption( 35e82b082eSAndreas Gohr 'user', 36e82b082eSAndreas Gohr 'work as this user. defaults to current CLI user', 37bf00b396SAndreas Gohr 'u', 38bf00b396SAndreas Gohr 'username' 39e82b082eSAndreas Gohr ); 40e82b082eSAndreas Gohr $options->setHelp( 41e82b082eSAndreas Gohr 'Utility to help command line Dokuwiki page editing, allow ' . 42e82b082eSAndreas Gohr 'pages to be checked out for editing then committed after changes' 43e82b082eSAndreas Gohr ); 44e82b082eSAndreas Gohr 45e82b082eSAndreas Gohr /* checkout command */ 46e82b082eSAndreas Gohr $options->registerCommand( 47e82b082eSAndreas Gohr 'checkout', 48e82b082eSAndreas Gohr 'Checks out a file from the repository, using the wiki id and obtaining ' . 49e82b082eSAndreas Gohr 'a lock for the page. ' . "\n" . 50e82b082eSAndreas Gohr 'If a working_file is specified, this is where the page is copied to. ' . 51e82b082eSAndreas Gohr 'Otherwise defaults to the same as the wiki page in the current ' . 52e82b082eSAndreas Gohr 'working directory.' 53e82b082eSAndreas Gohr ); 54e82b082eSAndreas Gohr $options->registerArgument( 55e82b082eSAndreas Gohr 'wikipage', 56e82b082eSAndreas Gohr 'The wiki page to checkout', 57e82b082eSAndreas Gohr true, 58e82b082eSAndreas Gohr 'checkout' 59e82b082eSAndreas Gohr ); 60e82b082eSAndreas Gohr $options->registerArgument( 61e82b082eSAndreas Gohr 'workingfile', 62e82b082eSAndreas Gohr 'How to name the local checkout', 63e82b082eSAndreas Gohr false, 64e82b082eSAndreas Gohr 'checkout' 65e82b082eSAndreas Gohr ); 66e82b082eSAndreas Gohr 67e82b082eSAndreas Gohr /* commit command */ 68e82b082eSAndreas Gohr $options->registerCommand( 69e82b082eSAndreas Gohr 'commit', 70e82b082eSAndreas Gohr 'Checks in the working_file into the repository using the specified ' . 71e82b082eSAndreas Gohr 'wiki id, archiving the previous version.' 72e82b082eSAndreas Gohr ); 73e82b082eSAndreas Gohr $options->registerArgument( 74e82b082eSAndreas Gohr 'workingfile', 75e82b082eSAndreas Gohr 'The local file to commit', 76e82b082eSAndreas Gohr true, 77e82b082eSAndreas Gohr 'commit' 78e82b082eSAndreas Gohr ); 79e82b082eSAndreas Gohr $options->registerArgument( 80e82b082eSAndreas Gohr 'wikipage', 81e82b082eSAndreas Gohr 'The wiki page to create or update', 82e82b082eSAndreas Gohr true, 83e82b082eSAndreas Gohr 'commit' 84e82b082eSAndreas Gohr ); 85e82b082eSAndreas Gohr $options->registerOption( 86e82b082eSAndreas Gohr 'message', 87e82b082eSAndreas Gohr 'Summary describing the change (required)', 88e82b082eSAndreas Gohr 'm', 89e82b082eSAndreas Gohr 'summary', 90e82b082eSAndreas Gohr 'commit' 91e82b082eSAndreas Gohr ); 92e82b082eSAndreas Gohr $options->registerOption( 93e82b082eSAndreas Gohr 'trivial', 94e82b082eSAndreas Gohr 'minor change', 95e82b082eSAndreas Gohr 't', 96e82b082eSAndreas Gohr false, 97e82b082eSAndreas Gohr 'commit' 98e82b082eSAndreas Gohr ); 99e82b082eSAndreas Gohr 100e82b082eSAndreas Gohr /* lock command */ 101e82b082eSAndreas Gohr $options->registerCommand( 102e82b082eSAndreas Gohr 'lock', 103e82b082eSAndreas Gohr 'Obtains or updates a lock for a wiki page' 104e82b082eSAndreas Gohr ); 105e82b082eSAndreas Gohr $options->registerArgument( 106e82b082eSAndreas Gohr 'wikipage', 107e82b082eSAndreas Gohr 'The wiki page to lock', 108e82b082eSAndreas Gohr true, 109e82b082eSAndreas Gohr 'lock' 110e82b082eSAndreas Gohr ); 111e82b082eSAndreas Gohr 112e82b082eSAndreas Gohr /* unlock command */ 113e82b082eSAndreas Gohr $options->registerCommand( 114e82b082eSAndreas Gohr 'unlock', 115e82b082eSAndreas Gohr 'Removes a lock for a wiki page.' 116e82b082eSAndreas Gohr ); 117e82b082eSAndreas Gohr $options->registerArgument( 118e82b082eSAndreas Gohr 'wikipage', 119e82b082eSAndreas Gohr 'The wiki page to unlock', 120e82b082eSAndreas Gohr true, 121e82b082eSAndreas Gohr 'unlock' 122e82b082eSAndreas Gohr ); 1232c23659dSRobin Getz 1242c23659dSRobin Getz /* gmeta command */ 1252c23659dSRobin Getz $options->registerCommand( 1263fe01844SAndreas Gohr 'getmeta', 1272c23659dSRobin Getz 'Prints metadata value for a page to stdout.' 1282c23659dSRobin Getz ); 1292c23659dSRobin Getz $options->registerArgument( 1302c23659dSRobin Getz 'wikipage', 1312c23659dSRobin Getz 'The wiki page to get the metadata for', 1322c23659dSRobin Getz true, 1333fe01844SAndreas Gohr 'getmeta' 1342c23659dSRobin Getz ); 1352c23659dSRobin Getz $options->registerArgument( 1362c23659dSRobin Getz 'key', 1372c23659dSRobin Getz 'The name of the metadata item to be retrieved.' . "\n" . 1382c23659dSRobin Getz 'If empty, an array of all the metadata items is returned.' . "\n" . 1392c23659dSRobin Getz 'For retrieving items that are stored in sub-arrays, separate the ' . 1402c23659dSRobin Getz 'keys of the different levels by spaces, in quotes, eg "date modified".', 1412c23659dSRobin Getz false, 1423fe01844SAndreas Gohr 'getmeta' 1432c23659dSRobin Getz ); 144e82b082eSAndreas Gohr } 145e82b082eSAndreas Gohr 146e82b082eSAndreas Gohr /** 147e82b082eSAndreas Gohr * Your main program 148e82b082eSAndreas Gohr * 149e82b082eSAndreas Gohr * Arguments and options have been parsed when this is run 150e82b082eSAndreas Gohr * 151cbeaa4a0SAndreas Gohr * @param Options $options 152e82b082eSAndreas Gohr * @return void 153e82b082eSAndreas Gohr */ 154d868eb89SAndreas Gohr protected function main(Options $options) 155d868eb89SAndreas Gohr { 156e82b082eSAndreas Gohr $this->force = $options->getOpt('force', false); 157e82b082eSAndreas Gohr $this->username = $options->getOpt('user', $this->getUser()); 158e82b082eSAndreas Gohr 159e82b082eSAndreas Gohr $command = $options->getCmd(); 160cbeaa4a0SAndreas Gohr $args = $options->getArgs(); 161e82b082eSAndreas Gohr switch ($command) { 1621caeb00aSHarry Fuecks case 'checkout': 163cbeaa4a0SAndreas Gohr $wiki_id = array_shift($args); 164cbeaa4a0SAndreas Gohr $localfile = array_shift($args); 165e82b082eSAndreas Gohr $this->commandCheckout($wiki_id, $localfile); 1661caeb00aSHarry Fuecks break; 1671caeb00aSHarry Fuecks case 'commit': 168cbeaa4a0SAndreas Gohr $localfile = array_shift($args); 169cbeaa4a0SAndreas Gohr $wiki_id = array_shift($args); 170e82b082eSAndreas Gohr $this->commandCommit( 171e82b082eSAndreas Gohr $localfile, 172e82b082eSAndreas Gohr $wiki_id, 173e82b082eSAndreas Gohr $options->getOpt('message', ''), 174e82b082eSAndreas Gohr $options->getOpt('trivial', false) 175e82b082eSAndreas Gohr ); 1761caeb00aSHarry Fuecks break; 1771caeb00aSHarry Fuecks case 'lock': 178cbeaa4a0SAndreas Gohr $wiki_id = array_shift($args); 179e82b082eSAndreas Gohr $this->obtainLock($wiki_id); 180e82b082eSAndreas Gohr $this->success("$wiki_id locked"); 1811caeb00aSHarry Fuecks break; 1821caeb00aSHarry Fuecks case 'unlock': 183cbeaa4a0SAndreas Gohr $wiki_id = array_shift($args); 184e82b082eSAndreas Gohr $this->clearLock($wiki_id); 185e82b082eSAndreas Gohr $this->success("$wiki_id unlocked"); 1861caeb00aSHarry Fuecks break; 1873fe01844SAndreas Gohr case 'getmeta': 1882c23659dSRobin Getz $wiki_id = array_shift($args); 1892c23659dSRobin Getz $key = trim(array_shift($args)); 1908d670f35SAndreas Gohr $meta = p_get_metadata($wiki_id, $key, METADATA_RENDER_UNLIMITED); 1918d670f35SAndreas Gohr echo trim(json_encode($meta, JSON_PRETTY_PRINT)); 1928d670f35SAndreas Gohr echo "\n"; 1932c23659dSRobin Getz break; 1941caeb00aSHarry Fuecks default: 195e82b082eSAndreas Gohr echo $options->help(); 1961caeb00aSHarry Fuecks } 1971caeb00aSHarry Fuecks } 1981caeb00aSHarry Fuecks 199e82b082eSAndreas Gohr /** 200e82b082eSAndreas Gohr * Check out a file 201e82b082eSAndreas Gohr * 202e82b082eSAndreas Gohr * @param string $wiki_id 203e82b082eSAndreas Gohr * @param string $localfile 204e82b082eSAndreas Gohr */ 205d868eb89SAndreas Gohr protected function commandCheckout($wiki_id, $localfile) 206d868eb89SAndreas Gohr { 207e82b082eSAndreas Gohr global $conf; 208e82b082eSAndreas Gohr 209e82b082eSAndreas Gohr $wiki_id = cleanID($wiki_id); 210e82b082eSAndreas Gohr $wiki_fn = wikiFN($wiki_id); 211e82b082eSAndreas Gohr 212e82b082eSAndreas Gohr if (!file_exists($wiki_fn)) { 213e82b082eSAndreas Gohr $this->fatal("$wiki_id does not yet exist"); 214e82b082eSAndreas Gohr } 215e82b082eSAndreas Gohr 216e82b082eSAndreas Gohr if (empty($localfile)) { 217b1f206e1SAndreas Gohr $localfile = getcwd() . '/' . PhpString::basename($wiki_fn); 218e82b082eSAndreas Gohr } 219e82b082eSAndreas Gohr 220*443c53d5SDamien Regad if (is_dir($localfile)) { 221*443c53d5SDamien Regad $this->fatal("Working file $localfile cannot be a directory"); 222*443c53d5SDamien Regad } 223*443c53d5SDamien Regad 224e82b082eSAndreas Gohr if (!file_exists(dirname($localfile))) { 225e82b082eSAndreas Gohr $this->fatal("Directory " . dirname($localfile) . " does not exist"); 226e82b082eSAndreas Gohr } 227e82b082eSAndreas Gohr 228b1f206e1SAndreas Gohr if (stristr(realpath(dirname($localfile)), (string) realpath($conf['datadir'])) !== false) { 229e82b082eSAndreas Gohr $this->fatal("Attempt to check out file into data directory - not allowed"); 230e82b082eSAndreas Gohr } 231e82b082eSAndreas Gohr 232e82b082eSAndreas Gohr $this->obtainLock($wiki_id); 233e82b082eSAndreas Gohr 234e82b082eSAndreas Gohr if (!copy($wiki_fn, $localfile)) { 235e82b082eSAndreas Gohr $this->clearLock($wiki_id); 236e82b082eSAndreas Gohr $this->fatal("Unable to copy $wiki_fn to $localfile"); 237e82b082eSAndreas Gohr } 238e82b082eSAndreas Gohr 239e82b082eSAndreas Gohr $this->success("$wiki_id > $localfile"); 240e82b082eSAndreas Gohr } 241e82b082eSAndreas Gohr 242e82b082eSAndreas Gohr /** 243e82b082eSAndreas Gohr * Save a file as a new page revision 244e82b082eSAndreas Gohr * 245e82b082eSAndreas Gohr * @param string $localfile 246e82b082eSAndreas Gohr * @param string $wiki_id 247e82b082eSAndreas Gohr * @param string $message 248e82b082eSAndreas Gohr * @param bool $minor 249e82b082eSAndreas Gohr */ 250d868eb89SAndreas Gohr protected function commandCommit($localfile, $wiki_id, $message, $minor) 251d868eb89SAndreas Gohr { 252e82b082eSAndreas Gohr $wiki_id = cleanID($wiki_id); 253e82b082eSAndreas Gohr $message = trim($message); 254e82b082eSAndreas Gohr 255e82b082eSAndreas Gohr if (!file_exists($localfile)) { 256e82b082eSAndreas Gohr $this->fatal("$localfile does not exist"); 257e82b082eSAndreas Gohr } 258e82b082eSAndreas Gohr 259e82b082eSAndreas Gohr if (!is_readable($localfile)) { 260e82b082eSAndreas Gohr $this->fatal("Cannot read from $localfile"); 261e82b082eSAndreas Gohr } 262e82b082eSAndreas Gohr 263e82b082eSAndreas Gohr if (!$message) { 264e82b082eSAndreas Gohr $this->fatal("Summary message required"); 265e82b082eSAndreas Gohr } 266e82b082eSAndreas Gohr 267e82b082eSAndreas Gohr $this->obtainLock($wiki_id); 268e82b082eSAndreas Gohr 269e82b082eSAndreas Gohr saveWikiText($wiki_id, file_get_contents($localfile), $message, $minor); 270e82b082eSAndreas Gohr 271e82b082eSAndreas Gohr $this->clearLock($wiki_id); 272e82b082eSAndreas Gohr 273e82b082eSAndreas Gohr $this->success("$localfile > $wiki_id"); 274e82b082eSAndreas Gohr } 275e82b082eSAndreas Gohr 276e82b082eSAndreas Gohr /** 277e82b082eSAndreas Gohr * Lock the given page or exit 278e82b082eSAndreas Gohr * 279e82b082eSAndreas Gohr * @param string $wiki_id 280e82b082eSAndreas Gohr */ 281d868eb89SAndreas Gohr protected function obtainLock($wiki_id) 282d868eb89SAndreas Gohr { 283e82b082eSAndreas Gohr if ($this->force) $this->deleteLock($wiki_id); 284e82b082eSAndreas Gohr 285e82b082eSAndreas Gohr $_SERVER['REMOTE_USER'] = $this->username; 286bf00b396SAndreas Gohr 287e82b082eSAndreas Gohr if (checklock($wiki_id)) { 288e82b082eSAndreas Gohr $this->error("Page $wiki_id is already locked by another user"); 289e82b082eSAndreas Gohr exit(1); 290e82b082eSAndreas Gohr } 291e82b082eSAndreas Gohr 292e82b082eSAndreas Gohr lock($wiki_id); 293e82b082eSAndreas Gohr 294cb62a664SJustinTrouble if (checklock($wiki_id)) { 295e82b082eSAndreas Gohr $this->error("Unable to obtain lock for $wiki_id "); 296e82b082eSAndreas Gohr var_dump(checklock($wiki_id)); 297e82b082eSAndreas Gohr exit(1); 298e82b082eSAndreas Gohr } 299e82b082eSAndreas Gohr } 300e82b082eSAndreas Gohr 301e82b082eSAndreas Gohr /** 302e82b082eSAndreas Gohr * Clear the lock on the given page 303e82b082eSAndreas Gohr * 304e82b082eSAndreas Gohr * @param string $wiki_id 305e82b082eSAndreas Gohr */ 306d868eb89SAndreas Gohr protected function clearLock($wiki_id) 307d868eb89SAndreas Gohr { 308e82b082eSAndreas Gohr if ($this->force) $this->deleteLock($wiki_id); 309e82b082eSAndreas Gohr 310e82b082eSAndreas Gohr $_SERVER['REMOTE_USER'] = $this->username; 311e82b082eSAndreas Gohr if (checklock($wiki_id)) { 312e82b082eSAndreas Gohr $this->error("Page $wiki_id is locked by another user"); 313e82b082eSAndreas Gohr exit(1); 314e82b082eSAndreas Gohr } 315e82b082eSAndreas Gohr 316e82b082eSAndreas Gohr unlock($wiki_id); 317e82b082eSAndreas Gohr 318e82b082eSAndreas Gohr if (file_exists(wikiLockFN($wiki_id))) { 319e82b082eSAndreas Gohr $this->error("Unable to clear lock for $wiki_id"); 320e82b082eSAndreas Gohr exit(1); 321e82b082eSAndreas Gohr } 322e82b082eSAndreas Gohr } 323e82b082eSAndreas Gohr 324e82b082eSAndreas Gohr /** 325e82b082eSAndreas Gohr * Forcefully remove a lock on the page given 326e82b082eSAndreas Gohr * 327e82b082eSAndreas Gohr * @param string $wiki_id 328e82b082eSAndreas Gohr */ 329d868eb89SAndreas Gohr protected function deleteLock($wiki_id) 330d868eb89SAndreas Gohr { 331e82b082eSAndreas Gohr $wikiLockFN = wikiLockFN($wiki_id); 332e82b082eSAndreas Gohr 333e82b082eSAndreas Gohr if (file_exists($wikiLockFN)) { 334e82b082eSAndreas Gohr if (!unlink($wikiLockFN)) { 335e82b082eSAndreas Gohr $this->error("Unable to delete $wikiLockFN"); 336e82b082eSAndreas Gohr exit(1); 337e82b082eSAndreas Gohr } 338e82b082eSAndreas Gohr } 339e82b082eSAndreas Gohr } 340e82b082eSAndreas Gohr 341e82b082eSAndreas Gohr /** 342e82b082eSAndreas Gohr * Get the current user's username from the environment 343e82b082eSAndreas Gohr * 344e82b082eSAndreas Gohr * @return string 345e82b082eSAndreas Gohr */ 346d868eb89SAndreas Gohr protected function getUser() 347d868eb89SAndreas Gohr { 3481caeb00aSHarry Fuecks $user = getenv('USER'); 349feec2ab4SAndreas Gohr if (empty($user)) { 3501caeb00aSHarry Fuecks $user = getenv('USERNAME'); 3511caeb00aSHarry Fuecks } else { 3521caeb00aSHarry Fuecks return $user; 3531caeb00aSHarry Fuecks } 354feec2ab4SAndreas Gohr if (empty($user)) { 3551caeb00aSHarry Fuecks $user = 'admin'; 3561caeb00aSHarry Fuecks } 3571caeb00aSHarry Fuecks return $user; 3581caeb00aSHarry Fuecks } 3591caeb00aSHarry Fuecks} 3601caeb00aSHarry Fuecks 361e82b082eSAndreas Gohr// Main 362e82b082eSAndreas Gohr$cli = new PageCLI(); 363e82b082eSAndreas Gohr$cli->run(); 364