Source for file patcher.php

Documentation is available at patcher.php

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

Documentation generated on Tue, 19 Nov 2013 15:10:22 +0100 by phpDocumentor 1.4.3