[ Index ]

PHP Cross Reference of Joomla 4.2.2 documentation

title

Body

[close]

/libraries/vendor/joomla/filesystem/src/ -> Patcher.php (source)

   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  }


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