--- /dev/null
+<?php
+/**
+ * PhpPatcher class
+ * @author legolas558
+ * @version 1.0
+ *
+ * improved, code cleanup and PHP5 done by jumpin.banana@gmail.com 2012
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the COMMON DEVELOPMENT AND DISTRIBUTION LICENSE
+ *
+ * You should have received a copy of the
+ * COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0
+ * along with this program. If not, see http://www.sun.com/cddl/cddl.html
+ *
+ *
+ * Facility to merge unified diff files
+ * First use Merge() and then ApplyPatch() to commit changes
+ * files will be created, updated and/or deleted
+ *
+ */
+
+define('_PHPP_INVALID_INPUT', 'Invalid input');
+define('_PHPP_UNEXPECTED_EOF', 'Unexpected end of file');
+define('_PHPP_UNEXPECTED_ADD_LINE', 'Unexpected add line at line %d');
+define('_PHPP_UNEXPECTED_REMOVE_LINE', 'Unexpected remove line at line %d');
+define('_PHPP_INVALID_DIFF', 'Invalid unified diff block');
+define('_PHPP_FAILED_VERIFY', 'Failed source verification of file %s at line %d');
+
+class PhpPatcher {
+
+ var $root;
+ var $msg;
+ var $sources = array();
+ var $destinations = array();
+ var $removals = array();
+ var $newline = "\n";
+
+ public function __construct($root_path) {
+ // if you specify a root path all paths will be intended as relative to it (and not written, too)
+ $this->root = $root_path;
+ }
+
+ public function Merge($udiff) {
+ $lines = $this->_linesplit($udiff);
+ if (!isset($lines)) {
+ $this->msg = _PHPP_INVALID_INPUT;
+ return false;
+ }
+ unset($udiff);
+
+ $line = current($lines);
+ do {
+ if (strlen($line)<5) {
+ continue;
+ }
+ // start recognition when a new diff block is found
+ if (substr($line, 0, 4)!='--- ') {
+ continue;
+ }
+ $p = strpos($line, "\t", 4);
+ if ($p===false) $p = strlen($line);
+ $src = $this->root.substr($line, 4, $p-4);
+ $line = next($lines);
+ if (!isset($line)) {
+ $this->msg = _PHPP_UNEXPECTED_EOF;
+ return false;
+ }
+ if (substr($line, 0, 4)!='+++ ') {
+ $this->msg = _PHPP_INVALID_DIFF;
+ return false;
+ }
+ $p = strpos($line, "\t", 4);
+ if ($p===false) $p = strlen($line);
+ $dst = $this->root.substr($line, 4, $p-4);
+
+ $line = next($lines);
+ if (!isset($line)) {
+ $this->msg = _PHPP_UNEXPECTED_EOF;
+ return false;
+ }
+
+ $done=0;
+ while (preg_match('/@@ -(\\d+)(,(\\d+))?\\s+\\+(\\d+)(,(\\d+))?\\s+@@($)/A', $line, $m)) {
+
+ if ($m[3]==='')
+ $src_size = 1;
+ else $src_size = (int)$m[3];
+ if ($m[6]==='')
+ $dst_size = 1;
+ else $dst_size = (int)$m[6];
+ if (!$this->_apply_diff($lines, $src, $dst,
+ (int)$m[1], $src_size, (int)$m[4],
+ $dst_size))
+ return false;
+ $done++;
+ $line = next($lines);
+ if ($line === FALSE)
+ break 2;
+ }
+ if ($done==0) {
+ $this->msg = _PHPP_INVALID_DIFF;
+ return false;
+ }
+
+ } while (FALSE !== ($line = next($lines)));
+
+ //NOTE: previously opened files are still cached
+ return true;
+ }
+
+ function ClearCache() {
+ $this->sources = array();
+ $this->destinations = array();
+ $this->removals = array();
+ }
+
+ function ApplyPatch() {
+ if (empty($this->destinations))
+ return 0;
+ $done = 0;
+ $files = array_keys($this->destinations);
+ foreach($files as $file) {
+ $f = @fopen($file, 'w');
+ if ($f===null)
+ continue;
+ fwrite($f, implode($this->newline, $this->destinations[$file]));
+ fclose($f);
+ $done++;
+ }
+ foreach($this->removals as $file) {
+ if (@unlink($file))
+ $done++;
+ if (isset($this->sources[$file]))
+ unset($this->sources[$file]);
+ }
+ $this->destinations = array(); // clear the destinations cache
+ $this->removals = array();
+ return $done;
+ }
+
+ private function &_get_source($src) {
+ if (isset($this->sources[$src]))
+ return $this->sources[$src];
+ if (!is_readable($src)) {
+ return null;
+ }
+ $_data = file_get_contents($src);
+ $this->sources[$src] = $this->_linesplit($_data);
+ return $this->sources[$src];
+ }
+
+ private function &_get_destin($dst, $src) {
+ if (isset($this->destinations[$dst]))
+ return $this->destinations[$dst];
+ $this->destinations[$dst] = $this->_get_source($src);
+ return $this->destinations[$dst];
+ }
+
+ // separate CR or CRLF lines
+ private function &_linesplit(&$data) {
+ $lines = preg_split('/(\r\n)|(\r)|(\n)/', $data);
+ return $lines;
+ }
+
+ private function _apply_diff(&$lines, $src, $dst, $src_line, $src_size, $dst_line, $dst_size) {
+ $src_line--;
+ $dst_line--;
+ $line = next($lines);
+ if ($line === false) {
+ $this->msg = _PHPP_UNEXPECTED_EOF;
+ return false;
+ }
+ $source = array(); // source lines (old file)
+ $destin = array(); // new lines (new file)
+ $src_left = $src_size;
+ $dst_left = $dst_size;
+ do {
+ if (!isset($line{0})) {
+ $source[] = '';
+ $destin[] = '';
+ $src_left--;
+ $dst_left--;
+ continue;
+ }
+ if ($line{0}=='-') {
+ if ($src_left==0) {
+ $this->msg = sprintf(_PHPP_UNEXPECTED_REMOVE_LINE, key($lines));
+ return false;
+ }
+ $source[] = substr($line, 1);
+ $src_left--;
+ } else if ($line{0}=='+') {
+ if ($dst_left==0) {
+ $this->msg = sprintf(_PHPP_UNEXPECTED_ADD_LINE, key($lines));
+ return false;
+ }
+ $destin[] = substr($line, 1);
+ $dst_left--;
+ } else {
+ if (!isset($line{1}))
+ $line = '';
+ else if ($line{0}=='\\') {
+ if ($line=='\\ No newline at end of file') {
+ continue;
+ }
+ } else {
+ $line = substr($line, 1);
+ }
+ $source[] = $line;
+ $destin[] = $line;
+ $src_left--;
+ $dst_left--;
+ }
+
+ if (($src_left==0) && ($dst_left==0)) {
+ // now apply the patch, finally!
+ if ($src_size>0) {
+ $src_lines =& $this->_get_source($src);
+ if (!isset($src_lines)) {
+ return false;
+ }
+ }
+ if ($dst_size>0) {
+ if ($src_size>0) {
+ $dst_lines =& $this->_get_destin($dst, $src);
+ if (!isset($dst_lines))
+ return false;
+ $src_bottom=$src_line+count($source);
+ $dst_bottom=$dst_line+count($destin);
+
+ for ($l=$src_line;$l<$src_bottom;$l++) {
+ if ($src_lines[$l]!=$source[$l-$src_line]) {
+ $this->msg = sprintf(_PHPP_FAILED_VERIFY, $src, $l);
+ return false;
+ }
+ }
+ array_splice($dst_lines, $dst_line, count($source), $destin);
+ } else
+ $this->destinations[$dst] = $destin;
+ } else
+ $this->removals[] = $src;
+
+ return true;
+ }
+ } while (FALSE !== ($line = next($lines)));
+
+ $this->msg = _PHPP_UNEXPECTED_EOF;
+ return false;
+ }
+}
+
+?>