1<?php 2/* vim: set expandtab tabstop=4 shiftwidth=4: */ 3/** 4 * VersionControl_SVN_Info allows for XML formatted output. XML_Parser is used to 5 * manipulate that output. 6 * 7 * +----------------------------------------------------------------------+ 8 * | This LICENSE is in the BSD license style. | 9 * | http://www.opensource.org/licenses/bsd-license.php | 10 * | | 11 * | Redistribution and use in source and binary forms, with or without | 12 * | modification, are permitted provided that the following conditions | 13 * | are met: | 14 * | | 15 * | * Redistributions of source code must retain the above copyright | 16 * | notice, this list of conditions and the following disclaimer. | 17 * | | 18 * | * Redistributions in binary form must reproduce the above | 19 * | copyright notice, this list of conditions and the following | 20 * | disclaimer in the documentation and/or other materials provided | 21 * | with the distribution. | 22 * | | 23 * | * Neither the name of Clay Loveless nor the names of contributors | 24 * | may be used to endorse or promote products derived from this | 25 * | software without specific prior written permission. | 26 * | | 27 * | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | 28 * | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | 29 * | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | 30 * | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE | 31 * | COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, | 32 * | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | 33 * | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | 34 * | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | 35 * | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | 36 * | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN | 37 * | ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | 38 * | POSSIBILITY OF SUCH DAMAGE. | 39 * +----------------------------------------------------------------------+ 40 * 41 * PHP version 5 42 * 43 * @category VersionControl 44 * @package VersionControl_SVN 45 * @author Clay Loveless <clay@killersoft.com> 46 * @author Michiel Rook <mrook@php.net> 47 * @author Alexander Opitz <opitz.alexander@gmail.com> 48 * @copyright 2004-2007 Clay Loveless 49 * @license http://www.opensource.org/licenses/bsd-license.php BSD License 50 * @link http://pear.php.net/package/VersionControl_SVN 51 */ 52 53require_once 'VersionControl/SVN/Exception.php'; 54require_once 'System.php'; 55 56/** 57 * Ground class for a SVN command. 58 * 59 * @category VersionControl 60 * @package VersionControl_SVN 61 * @author Clay Loveless <clay@killersoft.com> 62 * @author Michiel Rook <mrook@php.net> 63 * @author Alexander Opitz <opitz.alexander@gmail.com> 64 * @copyright 2004-2007 Clay Loveless 65 * @license http://www.opensource.org/licenses/bsd-license.php BSD License 66 * @version 0.5.2 67 * @link http://pear.php.net/package/VersionControl_SVN 68 */ 69abstract class VersionControl_SVN_Command 70{ 71 /** 72 * Indicates whether commands passed to the 73 * {@link http://www.php.net/exec exec()} function in the 74 * {@link run} method should be passed through 75 * {@link http://www.php.net/escapeshellcmd escapeshellcmd()}. 76 * NOTE: this variable is ignored on Windows machines! 77 * 78 * @var boolean $useEscapeshellcmd 79 */ 80 public $useEscapeshellcmd = true; 81 82 /** 83 * Use exec or passthru to get results from command. 84 * 85 * @var bool $passthru 86 */ 87 public $passthru = false; 88 89 /** 90 * Location of the svn client binary installed as part of Subversion 91 * 92 * @var string $binaryPath 93 */ 94 public $binaryPath = '/usr/local/bin/svn'; 95 96 /** 97 * String to prepend to command string. Helpful for setting exec() 98 * environment variables, such as: 99 * export LANG=en_US.utf8 && 100 * ... to support non-ASCII file and directory names. 101 * 102 * @var string $prependCmd 103 */ 104 public $prependCmd = ''; 105 106 /** 107 * Array of switches to use in building svn command 108 * 109 * @var array $switches 110 */ 111 public $switches = array(); 112 113 /** 114 * Runtime options being used. 115 * 116 * @var array $options 117 */ 118 public $options = array(); 119 120 /** 121 * Command-line arguments that should be passed 122 * <b>outside</b> of those specified in {@link switches}. 123 * 124 * @var array $args 125 */ 126 public $args = array(); 127 128 /** 129 * Preferred fetchmode. Note that not all subcommands have output available for 130 * each preferred fetchmode. The default cascade is: 131 * 132 * VERSIONCONTROL_SVN_FETCHMODE_ASSOC 133 * VERSIONCONTROL_SVN_FETCHMODE_RAW 134 * 135 * If the specified fetchmode isn't available, raw output will be returned. 136 * 137 * @var int $fetchmode 138 */ 139 public $fetchmode = VERSIONCONTROL_SVN_FETCHMODE_ASSOC; 140 141 /** 142 * Default username to use for connections. 143 * 144 * @var string $username 145 */ 146 public $username = null; 147 148 /** 149 * Default password to use for connections. 150 * 151 * @var string $password 152 */ 153 public $password = null; 154 155 /** 156 * Default config-dir to use for connections. 157 * 158 * @var string $configDir 159 */ 160 public $configDir = null; 161 162 /** 163 * Default config-option to use for connections. 164 * 165 * @var string $configOption 166 */ 167 public $configOption = null; 168 169 /** 170 * Default no-auth-cache to use for connections. 171 * 172 * @var string $noAuthCache 173 */ 174 public $noAuthCache = null; 175 176 /** 177 * Default trust-server-cert to use for connections. 178 * 179 * @var string $trustServerCert 180 */ 181 public $trustServerCert = false; 182 183 /** 184 * Switches required by this subcommand. 185 * See {@link http://svnbook.red-bean.com/svnbook/ Version Control with Subversion}, 186 * Subversion Complete Reference for details on arguments for this subcommand. 187 * 188 * @var array $requiredSwitches 189 */ 190 protected $requiredSwitches = array(); 191 192 /** 193 * Minimum number of args required by this subcommand. 194 * See {@link http://svnbook.red-bean.com/svnbook/ Version Control with Subversion}, 195 * Subversion Complete Reference for details on arguments for this subcommand. 196 * 197 * @var int $minArgs 198 */ 199 protected $minArgs = 0; 200 201 /** 202 * SVN subcommand to run. 203 * 204 * @var string $commandName 205 */ 206 protected $commandName = ''; 207 208 /** 209 * Fully prepared command string. 210 * 211 * @var string $preparedCmd 212 */ 213 protected $preparedCmd = ''; 214 215 /** 216 * Keep track of whether XML output is available for a command 217 * 218 * @var boolean $xmlAvail 219 */ 220 protected $xmlAvail = false; 221 222 /** 223 * Useable switches for command with parameters. 224 */ 225 protected $validSwitchesValue = array( 226 'username', 227 'password', 228 'config-dir', 229 'config-option', 230 ); 231 232 /** 233 * Useable switches for command without parameters. 234 */ 235 protected $validSwitches = array( 236 'no-auth-cache', 237 'non-interactive', 238 'trust-server-cert', 239 ); 240 241 /** 242 * Constructor. Can't be called directly as class is abstract. 243 */ 244 public function __construct() 245 { 246 $className = get_class($this); 247 $this->commandName = strtolower( 248 substr( 249 $className, 250 strrpos($className, '_') + 1 251 ) 252 ); 253 } 254 255 /** 256 * Allow for overriding of previously declared options. 257 * 258 * @param array $options An associative array of option names and 259 * their values 260 * 261 * @return VersionControl_SVN_Command Themself. 262 * @throws VersionControl_SVN_Exception If option isn't available. 263 */ 264 public function setOptions($options = array()) 265 { 266 $class = new ReflectionClass($this); 267 268 foreach ($options as $option => $value) { 269 try { 270 $property = $class->getProperty($option); 271 } catch (ReflectionException $e) { 272 $property = null; 273 } 274 if (null !== $property && $property->isPublic()) { 275 $this->$option = $value; 276 } else { 277 throw new VersionControl_SVN_Exception( 278 '"' . $option . '" is not a valid option', 279 VersionControl_SVN_Exception::INVALID_OPTION 280 ); 281 } 282 } 283 284 return $this; 285 } 286 287 /** 288 * Prepare the command switches. 289 * 290 * This function should be overloaded by the command class. 291 * 292 * @return void 293 * @throws VersionControl_SVN_Exception If preparing failed. 294 */ 295 public function prepare() 296 { 297 $this->checkCommandRequirements(); 298 $this->preProcessSwitches(); 299 300 $invalidSwitches = array(); 301 $cmdParts = array( 302 $this->binaryPath, 303 $this->commandName 304 ); 305 306 foreach ($this->switches as $switch => $val) { 307 if (1 === strlen($switch)) { 308 $switchPrefix = '-'; 309 } else { 310 $switchPrefix = '--'; 311 } 312 if (in_array($switch, $this->validSwitchesValue)) { 313 $cmdParts[] = $switchPrefix . $switch . ' ' . escapeshellarg($val); 314 } elseif (in_array($switch, $this->validSwitches)) { 315 if (true === $val) { 316 $cmdParts[] = $switchPrefix . $switch; 317 } 318 } else { 319 $invalidSwitches[] = $switch; 320 } 321 } 322 323 $this->postProcessSwitches($invalidSwitches); 324 325 $this->preparedCmd = implode(' ', array_merge($cmdParts, $this->args)); 326 } 327 328 /** 329 * Called after handling switches. 330 * 331 * @param array $invalidSwitches Invalid switches found while processing. 332 * 333 * @return void 334 * @throws VersionControl_SVN_Exception If switch(s) is/are invalid. 335 */ 336 protected function postProcessSwitches($invalidSwitches) 337 { 338 $invalid = count($invalidSwitches); 339 if ($invalid > 0) { 340 $invalides = implode(',', $invalidSwitches); 341 if ($invalid > 1) { 342 $error = '"' . $invalides . '" are invalid switches'; 343 } else { 344 $error = '"' . $invalides . '" is a invalid switch'; 345 } 346 $error .= ' for class "' . get_class($this) . '".'; 347 throw new VersionControl_SVN_Exception( 348 $error, 349 VersionControl_SVN_Exception::INVALID_SWITCH 350 ); 351 } 352 } 353 354 355 /** 356 * Called before handling switches. 357 * 358 * @return void 359 */ 360 protected function preProcessSwitches() 361 { 362 if ($this->xmlAvail 363 && ($this->fetchmode == VERSIONCONTROL_SVN_FETCHMODE_ARRAY 364 || $this->fetchmode == VERSIONCONTROL_SVN_FETCHMODE_ASSOC 365 || $this->fetchmode == VERSIONCONTROL_SVN_FETCHMODE_OBJECT 366 || $this->fetchmode == VERSIONCONTROL_SVN_FETCHMODE_XML) 367 ) { 368 $this->switches['xml'] = true; 369 } else { 370 unset($this->switches['xml']); 371 } 372 373 $this->switches['non-interactive'] = true; 374 375 $this->fillSwitch('username', $this->username); 376 $this->fillSwitch('password', $this->password); 377 $this->fillSwitch('config-dir', $this->configDir); 378 $this->fillSwitch('config-option', $this->configOption); 379 $this->fillSwitch('no-auth-cache', $this->noAuthCache); 380 $this->fillSwitch('trust-server-cert', $this->trustServerCert); 381 } 382 383 /** 384 * Fills the switches array on given name with value if not already set and value is not null. 385 * 386 * @param string $switchName Name of the switch. 387 * @param string $value Value for the switch. 388 * 389 * @return void 390 */ 391 protected function fillSwitch($switchName, $value) 392 { 393 if (!isset($this->switches[$switchName]) 394 && null !== $value 395 ) { 396 $this->switches[$switchName] = $value; 397 } 398 } 399 400 401 /** 402 * Standardized validation of requirements for a command class. 403 * 404 * @return void 405 * @throws VersionControl_SVN_Exception If command requirements not resolved. 406 */ 407 public function checkCommandRequirements() 408 { 409 // Check for minimum arguments 410 if (sizeof($this->args) < $this->minArgs) { 411 throw new VersionControl_SVN_Exception( 412 'svn command requires at least ' . $this->minArgs . ' argument(s)', 413 VersionControl_SVN_Exception::MIN_ARGS 414 ); 415 } 416 417 // Check for presence of required switches 418 if (sizeof($this->requiredSwitches) > 0) { 419 $missing = array(); 420 $switches = $this->switches; 421 $reqsw = $this->requiredSwitches; 422 foreach ($reqsw as $req) { 423 $found = false; 424 $good_switches = explode('|', $req); 425 foreach ($good_switches as $gsw) { 426 if (isset($switches[$gsw])) { 427 $found = true; 428 } 429 } 430 if (!$found) { 431 $missing[] = '(' . $req . ')'; 432 } 433 } 434 $num_missing = count($missing); 435 if ($num_missing > 0) { 436 throw new VersionControl_SVN_Exception( 437 'svn command requires the following switch(es): ' . implode(', ', $missing), 438 VersionControl_SVN_Exception::SWITCH_MISSING 439 ); 440 } 441 } 442 } 443 444 /** 445 * Run the command with the defined switches. 446 * 447 * @param array $args Arguments to pass to Subversion 448 * @param array $switches Switches to pass to Subversion 449 * 450 * @return mixed $fetchmode specified output on success. 451 * @throws VersionControl_SVN_Exception If command failed. 452 */ 453 public function run($args = array(), $switches = array()) 454 { 455 if (!file_exists($this->binaryPath)) { 456 $system = new System(); 457 $this->binaryPath = $system->which('svn'); 458 } 459 460 if (sizeof($switches) > 0) { 461 $this->switches = $switches; 462 } 463 $this->args = array_map('escapeshellarg', $args); 464 465 // Always prepare, allows for obj re-use. (Request #5021) 466 $this->prepare(); 467 468 $out = array(); 469 // @var integer $returnVar Return number from shell execution. 470 $returnVar = null; 471 472 $cmd = $this->preparedCmd; 473 474 // On Windows, don't use escapeshellcmd, and double-quote $cmd 475 // so it's executed as 476 // cmd /c ""C:\Program Files\SVN\bin\svn.exe" info "C:\Program Files\dev\trunk"" 477 if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { 478 $cmd = str_replace( 479 $this->binaryPath, 480 escapeshellarg(str_replace('/', '\\', $this->binaryPath)), 481 $cmd 482 ); 483 484 if (!$this->passthru) { 485 exec("cmd /c \"$cmd 2>&1\"", $out, $returnVar); 486 } else { 487 passthru("cmd /c \"$cmd 2>&1\"", $returnVar); 488 } 489 } else { 490 if ($this->useEscapeshellcmd) { 491 $cmd = escapeshellcmd($cmd); 492 } 493 if (!$this->passthru) { 494 exec("{$this->prependCmd}$cmd 2>&1", $out, $returnVar); 495 } else { 496 passthru("{$this->prependCmd}$cmd 2>&1", $returnVar); 497 } 498 } 499 500 if ($returnVar > 0) { 501 throw new VersionControl_SVN_Exception( 502 'Execution of command failed returning: ' . $returnVar 503 . "\n" . implode("\n", $out), 504 VersionControl_SVN_Exception::EXEC 505 ); 506 } 507 508 return $this->parseOutput($out); 509 } 510 511 /** 512 * Handles output parsing of standard and verbose output of command. 513 * 514 * @param array $out Array of output captured by exec command in {@link run} 515 * 516 * @return mixed Returns output requested by fetchmode (if available), or 517 * raw output if desired fetchmode is not available. 518 */ 519 public function parseOutput($out) 520 { 521 $dir = realpath(dirname(__FILE__)) . '/Parser/XML'; 522 switch($this->fetchmode) { 523 case VERSIONCONTROL_SVN_FETCHMODE_ARRAY: 524 case VERSIONCONTROL_SVN_FETCHMODE_ASSOC: 525 case VERSIONCONTROL_SVN_FETCHMODE_OBJECT: 526 $file = $dir . '/' . ucfirst($this->commandName) . '.php'; 527 if (file_exists($file)) { 528 $class = 'VersionControl_SVN_Parser_XML_' 529 . ucfirst($this->commandName); 530 531 include_once $file; 532 $parser = new $class; 533 $contentVar = $this->commandName; 534 535 $parsedData = $parser->getParsed(join("\n", $out)); 536 if ($this->fetchmode == VERSIONCONTROL_SVN_FETCHMODE_OBJECT) { 537 return (object) $parsedData; 538 } 539 return $parsedData; 540 break; 541 } 542 case VERSIONCONTROL_SVN_FETCHMODE_RAW: 543 case VERSIONCONTROL_SVN_FETCHMODE_XML: 544 default: 545 // What you get with VERSIONCONTROL_SVN_FETCHMODE_DEFAULT 546 return join("\n", $out); 547 break; 548 } 549 } 550} 551?> 552