[ Index ]

PHP Cross Reference of Joomla 4.2.2 documentation

title

Body

[close]

/libraries/src/Filesystem/ -> Patcher.php (source)

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


Generated: Wed Sep 7 05:41:13 2022 Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer