[ Index ] |
PHP Cross Reference of Joomla 4.2.2 documentation |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * Part of the Joomla Framework Filesystem Package 4 * 5 * @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved. 6 * @license GNU General Public License version 2 or later; see LICENSE 7 */ 8 9 namespace Joomla\Filesystem; 10 11 /** 12 * A Unified Diff Format Patcher class 13 * 14 * @link http://sourceforge.net/projects/phppatcher/ This has been derived from the PhpPatcher version 0.1.1 written by Giuseppe Mazzotta 15 * @since 1.0 16 */ 17 class Patcher 18 { 19 /** 20 * Regular expression for searching source files 21 * 22 * @var string 23 * @since 1.0 24 */ 25 const SRC_FILE = '/^---\\s+(\\S+)\s+\\d{1,4}-\\d{1,2}-\\d{1,2}\\s+\\d{1,2}:\\d{1,2}:\\d{1,2}(\\.\\d+)?\\s+(\+|-)\\d{4}/A'; 26 27 /** 28 * Regular expression for searching destination files 29 * 30 * @var string 31 * @since 1.0 32 */ 33 const DST_FILE = '/^\\+\\+\\+\\s+(\\S+)\s+\\d{1,4}-\\d{1,2}-\\d{1,2}\\s+\\d{1,2}:\\d{1,2}:\\d{1,2}(\\.\\d+)?\\s+(\+|-)\\d{4}/A'; 34 35 /** 36 * Regular expression for searching hunks of differences 37 * 38 * @var string 39 * @since 1.0 40 */ 41 const HUNK = '/@@ -(\\d+)(,(\\d+))?\\s+\\+(\\d+)(,(\\d+))?\\s+@@($)/A'; 42 43 /** 44 * Regular expression for splitting lines 45 * 46 * @var string 47 * @since 1.0 48 */ 49 const SPLIT = '/(\r\n)|(\r)|(\n)/'; 50 51 /** 52 * Source files 53 * 54 * @var array 55 * @since 1.0 56 */ 57 protected $sources = []; 58 59 /** 60 * Destination files 61 * 62 * @var array 63 * @since 1.0 64 */ 65 protected $destinations = []; 66 67 /** 68 * Removal files 69 * 70 * @var array 71 * @since 1.0 72 */ 73 protected $removals = []; 74 75 /** 76 * Patches 77 * 78 * @var array 79 * @since 1.0 80 */ 81 protected $patches = []; 82 83 /** 84 * Singleton instance of this class 85 * 86 * @var Patcher 87 * @since 1.0 88 */ 89 protected static $instance; 90 91 /** 92 * Constructor 93 * 94 * The constructor is protected to force the use of Patcher::getInstance() 95 * 96 * @since 1.0 97 */ 98 protected function __construct() 99 { 100 } 101 102 /** 103 * Method to get a patcher 104 * 105 * @return Patcher an instance of the patcher 106 * 107 * @since 1.0 108 */ 109 public static function getInstance() 110 { 111 if (!isset(static::$instance)) 112 { 113 static::$instance = new static; 114 } 115 116 return static::$instance; 117 } 118 119 /** 120 * Reset the pacher 121 * 122 * @return Patcher This object for chaining 123 * 124 * @since 1.0 125 */ 126 public function reset() 127 { 128 $this->sources = []; 129 $this->destinations = []; 130 $this->removals = []; 131 $this->patches = []; 132 133 return $this; 134 } 135 136 /** 137 * Apply the patches 138 * 139 * @return integer The number of files patched 140 * 141 * @since 1.0 142 * @throws \RuntimeException 143 */ 144 public function apply() 145 { 146 foreach ($this->patches as $patch) 147 { 148 // Separate the input into lines 149 $lines = self::splitLines($patch['udiff']); 150 151 // Loop for each header 152 while (self::findHeader($lines, $src, $dst)) 153 { 154 $done = false; 155 156 if ($patch['strip'] === null) 157 { 158 $src = $patch['root'] . preg_replace('#^([^/]*/)*#', '', $src); 159 $dst = $patch['root'] . preg_replace('#^([^/]*/)*#', '', $dst); 160 } 161 else 162 { 163 $src = $patch['root'] . preg_replace('#^([^/]*/){' . (int) $patch['strip'] . '}#', '', $src); 164 $dst = $patch['root'] . preg_replace('#^([^/]*/){' . (int) $patch['strip'] . '}#', '', $dst); 165 } 166 167 // Loop for each hunk of differences 168 while (self::findHunk($lines, $srcLine, $srcSize, $dstLine, $dstSize)) 169 { 170 $done = true; 171 172 // Apply the hunk of differences 173 $this->applyHunk($lines, $src, $dst, $srcLine, $srcSize, $dstLine, $dstSize); 174 } 175 176 // If no modifications were found, throw an exception 177 if (!$done) 178 { 179 throw new \RuntimeException('Invalid Diff'); 180 } 181 } 182 } 183 184 // Initialize the counter 185 $done = 0; 186 187 // Patch each destination file 188 foreach ($this->destinations as $file => $content) 189 { 190 $content = implode("\n", $content); 191 192 if (File::write($file, $content)) 193 { 194 if (isset($this->sources[$file])) 195 { 196 $this->sources[$file] = $content; 197 } 198 199 $done++; 200 } 201 } 202 203 // Remove each removed file 204 foreach ($this->removals as $file) 205 { 206 if (File::delete($file)) 207 { 208 if (isset($this->sources[$file])) 209 { 210 unset($this->sources[$file]); 211 } 212 213 $done++; 214 } 215 } 216 217 // Clear the destinations cache 218 $this->destinations = []; 219 220 // Clear the removals 221 $this->removals = []; 222 223 // Clear the patches 224 $this->patches = []; 225 226 return $done; 227 } 228 229 /** 230 * Add a unified diff file to the patcher 231 * 232 * @param string $filename Path to the unified diff file 233 * @param string $root The files root path 234 * @param integer $strip The number of '/' to strip 235 * 236 * @return Patcher $this for chaining 237 * 238 * @since 1.0 239 */ 240 public function addFile($filename, $root, $strip = 0) 241 { 242 return $this->add(file_get_contents($filename), $root, $strip); 243 } 244 245 /** 246 * Add a unified diff string to the patcher 247 * 248 * @param string $udiff Unified diff input string 249 * @param string $root The files root path 250 * @param integer $strip The number of '/' to strip 251 * 252 * @return Patcher $this for chaining 253 * 254 * @since 1.0 255 */ 256 public function add($udiff, $root, $strip = 0) 257 { 258 $this->patches[] = [ 259 'udiff' => $udiff, 260 'root' => isset($root) ? rtrim($root, \DIRECTORY_SEPARATOR) . \DIRECTORY_SEPARATOR : '', 261 'strip' => $strip, 262 ]; 263 264 return $this; 265 } 266 267 /** 268 * Separate CR or CRLF lines 269 * 270 * @param string $data Input string 271 * 272 * @return array The lines of the input destination file 273 * 274 * @since 1.0 275 */ 276 protected static function splitLines($data) 277 { 278 return preg_split(self::SPLIT, $data); 279 } 280 281 /** 282 * Find the diff header 283 * 284 * The internal array pointer of $lines is on the next line after the finding 285 * 286 * @param array $lines The udiff array of lines 287 * @param string $src The source file 288 * @param string $dst The destination file 289 * 290 * @return boolean TRUE in case of success, FALSE in case of failure 291 * 292 * @since 1.0 293 * @throws \RuntimeException 294 */ 295 protected static function findHeader(&$lines, &$src, &$dst) 296 { 297 // Get the current line 298 $line = current($lines); 299 300 // Search for the header 301 while ($line !== false && !preg_match(self::SRC_FILE, $line, $m)) 302 { 303 $line = next($lines); 304 } 305 306 if ($line === false) 307 { 308 // No header found, return false 309 return false; 310 } 311 312 // Set the source file 313 $src = $m[1]; 314 315 // Advance to the next line 316 $line = next($lines); 317 318 if ($line === false) 319 { 320 throw new \RuntimeException('Unexpected EOF'); 321 } 322 323 // Search the destination file 324 if (!preg_match(self::DST_FILE, $line, $m)) 325 { 326 throw new \RuntimeException('Invalid Diff file'); 327 } 328 329 // Set the destination file 330 $dst = $m[1]; 331 332 // Advance to the next line 333 if (next($lines) === false) 334 { 335 throw new \RuntimeException('Unexpected EOF'); 336 } 337 338 return true; 339 } 340 341 /** 342 * Find the next hunk of difference 343 * 344 * The internal array pointer of $lines is on the next line after the finding 345 * 346 * @param array $lines The udiff array of lines 347 * @param string $srcLine The beginning of the patch for the source file 348 * @param string $srcSize The size of the patch for the source file 349 * @param string $dstLine The beginning of the patch for the destination file 350 * @param string $dstSize The size of the patch for the destination file 351 * 352 * @return boolean TRUE in case of success, false in case of failure 353 * 354 * @since 1.0 355 * @throws \RuntimeException 356 */ 357 protected static function findHunk(&$lines, &$srcLine, &$srcSize, &$dstLine, &$dstSize) 358 { 359 $line = current($lines); 360 361 if (preg_match(self::HUNK, $line, $m)) 362 { 363 $srcLine = (int) $m[1]; 364 365 if ($m[3] === '') 366 { 367 $srcSize = 1; 368 } 369 else 370 { 371 $srcSize = (int) $m[3]; 372 } 373 374 $dstLine = (int) $m[4]; 375 376 if ($m[6] === '') 377 { 378 $dstSize = 1; 379 } 380 else 381 { 382 $dstSize = (int) $m[6]; 383 } 384 385 if (next($lines) === false) 386 { 387 throw new \RuntimeException('Unexpected EOF'); 388 } 389 390 return true; 391 } 392 393 return false; 394 } 395 396 /** 397 * Apply the patch 398 * 399 * @param array $lines The udiff array of lines 400 * @param string $src The source file 401 * @param string $dst The destination file 402 * @param string $srcLine The beginning of the patch for the source file 403 * @param string $srcSize The size of the patch for the source file 404 * @param string $dstLine The beginning of the patch for the destination file 405 * @param string $dstSize The size of the patch for the destination file 406 * 407 * @return void 408 * 409 * @since 1.0 410 * @throws \RuntimeException 411 */ 412 protected function applyHunk(&$lines, $src, $dst, $srcLine, $srcSize, $dstLine, $dstSize) 413 { 414 $srcLine--; 415 $dstLine--; 416 $line = current($lines); 417 418 // Source lines (old file) 419 $source = []; 420 421 // New lines (new file) 422 $destin = []; 423 $srcLeft = $srcSize; 424 $dstLeft = $dstSize; 425 426 do 427 { 428 if (!isset($line[0])) 429 { 430 $source[] = ''; 431 $destin[] = ''; 432 $srcLeft--; 433 $dstLeft--; 434 } 435 elseif ($line[0] == '-') 436 { 437 if ($srcLeft == 0) 438 { 439 throw new \RuntimeException('Unexpected remove line at line ' . key($lines)); 440 } 441 442 $source[] = substr($line, 1); 443 $srcLeft--; 444 } 445 elseif ($line[0] == '+') 446 { 447 if ($dstLeft == 0) 448 { 449 throw new \RuntimeException('Unexpected add line at line ' . key($lines)); 450 } 451 452 $destin[] = substr($line, 1); 453 $dstLeft--; 454 } 455 elseif ($line != '\\ No newline at end of file') 456 { 457 $line = substr($line, 1); 458 $source[] = $line; 459 $destin[] = $line; 460 $srcLeft--; 461 $dstLeft--; 462 } 463 464 if ($srcLeft == 0 && $dstLeft == 0) 465 { 466 // Now apply the patch, finally! 467 if ($srcSize > 0) 468 { 469 $srcLines = & $this->getSource($src); 470 471 if (!isset($srcLines)) 472 { 473 throw new \RuntimeException( 474 'Unexisting source file: ' . Path::removeRoot($src) 475 ); 476 } 477 } 478 479 if ($dstSize > 0) 480 { 481 if ($srcSize > 0) 482 { 483 $dstLines = & $this->getDestination($dst, $src); 484 $srcBottom = $srcLine + \count($source); 485 486 for ($l = $srcLine; $l < $srcBottom; $l++) 487 { 488 if ($srcLines[$l] != $source[$l - $srcLine]) 489 { 490 throw new \RuntimeException( 491 sprintf( 492 'Failed source verification of file %1$s at line %2$s', 493 Path::removeRoot($src), 494 $l 495 ) 496 ); 497 } 498 } 499 500 array_splice($dstLines, $dstLine, \count($source), $destin); 501 } 502 else 503 { 504 $this->destinations[$dst] = $destin; 505 } 506 } 507 else 508 { 509 $this->removals[] = $src; 510 } 511 512 next($lines); 513 514 return; 515 } 516 517 $line = next($lines); 518 } 519 while ($line !== false); 520 521 throw new \RuntimeException('Unexpected EOF'); 522 } 523 524 /** 525 * Get the lines of a source file 526 * 527 * @param string $src The path of a file 528 * 529 * @return array The lines of the source file 530 * 531 * @since 1.0 532 */ 533 protected function &getSource($src) 534 { 535 if (!isset($this->sources[$src])) 536 { 537 if (is_readable($src)) 538 { 539 $this->sources[$src] = self::splitLines(file_get_contents($src)); 540 } 541 else 542 { 543 $this->sources[$src] = null; 544 } 545 } 546 547 return $this->sources[$src]; 548 } 549 550 /** 551 * Get the lines of a destination file 552 * 553 * @param string $dst The path of a destination file 554 * @param string $src The path of a source file 555 * 556 * @return array The lines of the destination file 557 * 558 * @since 1.0 559 */ 560 protected function &getDestination($dst, $src) 561 { 562 if (!isset($this->destinations[$dst])) 563 { 564 $this->destinations[$dst] = $this->getSource($src); 565 } 566 567 return $this->destinations[$dst]; 568 } 569 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Wed Sep 7 05:41:13 2022 | Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer |