From fc99720a882c44059b4b52be2e61cdd2f48afbd0 Mon Sep 17 00:00:00 2001 From: Konstantinos Botonakis Date: Tue, 4 Jun 2024 13:36:32 +0100 Subject: [PATCH 1/2] support custom folder for vqmod install + small enhancements for errors --- .gitignore | 2 + README.md | 17 +- install/.htaccess | 2 + install/index.php | 67 +-- vqmod.php | 1012 ++++++++++++++++++++++++++++++++++++++++ xml/vqmod_opencart.xml | 116 ++--- 6 files changed, 1120 insertions(+), 96 deletions(-) create mode 100644 .gitignore create mode 100644 install/.htaccess create mode 100644 vqmod.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6373079 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.idea +.DS_STORE \ No newline at end of file diff --git a/README.md b/README.md index 3fb0cc3..a1973f7 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,12 @@ -# opencart +# vQmod for OpenCart This is a repository for the opencart vqmod core script files. First download and install vQmod from the main repository, then install the script files from this repository for use with opencart -1. Be sure you've installed the vQmod package first! This package only contains the opencart script files -2. Upload the xml and install folder to your vqmod folder on your server. -3. Overwrite any files that already exist +Steps to install: +1. Download the latest vQmod release from the releases page: https://github.com/vqmod/vqmod/releases +2. Then checkout this repository. +3. Upload the xml and install folder to your vqmod folder on your server. +4. Overwrite any files that already exist +5. Run the installer with https://yourstore.com/vqmod/install -When done, you should see the following files on your server - vqmod/xml/vqmod_opencart.xml - vqmod/install/index.php - -4. Run the installer with https:/yourstore.com/vqmod/install \ No newline at end of file +Where yourstore.com is your domain name and where vqmod is the folder you installed vqmod to (ex: /vqmod/, /custom_vqmod/, /changes/ .... ) \ No newline at end of file diff --git a/install/.htaccess b/install/.htaccess new file mode 100644 index 0000000..2720bc4 --- /dev/null +++ b/install/.htaccess @@ -0,0 +1,2 @@ +Satisfy Any +Allow From All diff --git a/install/index.php b/install/index.php index 1e1080a..4fbd8fd 100644 --- a/install/index.php +++ b/install/index.php @@ -34,29 +34,41 @@ if (!empty($_POST['admin_name'])) { //$admin = 'admin'; - $admin = htmlspecialchars($_POST['admin_name']); + $admin = htmlspecialchars(trim($_POST['admin_name'])); // Counters $changes = 0; $writes = 0; $files = []; // Load class required for installation - require('ugrsr.class.php'); + $main_class = 'ugrsr.class.php'; + if (!file_exists($main_class)) { + die('ERROR - UGRSR CLASS NOT FOUND - Please ensure you have uploaded the vQmod install files correctly'); + } + + require($main_class); + + $vqmod_path = dirname(__FILE__, 2) . '/'; + + // Get the vqmod folder via the path + $vqmod_folder = basename($vqmod_path); + + // OpenCart path + $opencart_path = dirname(__FILE__, 3) . '/'; - // Get directory two above installation directory - $opencart_path = realpath(dirname(__FILE__) . '/../../') . '/'; - // Verify path is correct - if(empty($opencart_path)) die('ERROR - COULD NOT DETERMINE OPENCART PATH CORRECTLY - ' . dirname(__FILE__)); + if(empty($opencart_path)) { + die('ERROR - COULD NOT DETERMINE OPENCART PATH CORRECTLY - ' . __DIR__); + } $write_errors = array(); - if(!is_writeable($opencart_path . 'index.php')) { + if(!is_writable($opencart_path . 'index.php')) { $write_errors[] = 'index.php not writeable'; } - if(!is_writeable($opencart_path . $admin . '/index.php')) { + if(!is_writable($opencart_path . $admin . '/index.php')) { $write_errors[] = 'Administrator index.php not writeable'; } - if(!is_writeable($opencart_path . 'vqmod/pathReplaces.php')) { + if(!is_writable($opencart_path . $vqmod_folder . '/pathReplaces.php')) { $write_errors[] = 'vQmod pathReplaces.php not writeable'; } @@ -73,7 +85,7 @@ // Set file searching to off $u->file_search = false; - // Attempt upgrade if necessary. Otherwise just continue with normal install + // Attempt upgrade if necessary. Otherwise, just continue with normal install $u->addFile('index.php'); $u->addFile($admin . '/index.php'); @@ -82,21 +94,18 @@ $result = $u->run(); - if($result['writes'] > 0) { - if(file_exists('../mods.cache')) { - unlink('../mods.cache'); - } - //die('UPGRADE COMPLETE'); + if(($result['writes'] > 0) && file_exists('../mods.cache')) { + unlink('../mods.cache'); } $u->clearPatterns(); $u->resetFileList(); - // Add catalog index files to files to include + // Add catalog index files to include $u->addFile('index.php'); // Pattern to add vqmod include - $u->addPattern('~// Startup~', "// vQmod\nrequire_once('./vqmod/vqmod.php');\nVQMod::bootup();\n\n// VQMODDED Startup"); + $u->addPattern('~// Startup~', "// vQmod\nrequire_once('./" . $vqmod_folder . "/vqmod.php');\nVQMod::bootup();\n\n// VQMODDED Startup"); $result = $u->run(); $writes += $result['writes']; @@ -110,7 +119,7 @@ $u->addFile($admin . '/index.php'); // Pattern to add vqmod include - $u->addPattern('~// Startup~', "//vQmod\nrequire_once('../vqmod/vqmod.php');\nVQMod::bootup();\n\n// VQMODDED Startup"); + $u->addPattern('~// Startup~', "//vQmod\nrequire_once('../" . $vqmod_folder . "/vqmod.php');\nVQMod::bootup();\n\n// VQMODDED Startup"); $result = $u->run(); $writes += $result['writes']; @@ -127,15 +136,15 @@ $writes += $result['writes']; $changes += $result['changes']; $files = array_merge($files, $result['files']); - + $u->clearPatterns(); $u->resetFileList(); - + // 2022 - Qphoria // pathReplaces install - + // Add vqmod/pathReplaces.php file - $u->addFile('vqmod/pathReplaces.php'); + $u->addFile($vqmod_folder . '/pathReplaces.php'); // Pattern to add vqmod include $u->addPattern('~// START REPLACES //~', "// VQMODDED START REPLACES //\nif (defined('DIR_CATALOG')) { \$replaces[] = array('~^admin\b~', basename(DIR_APPLICATION)); }"); @@ -157,10 +166,10 @@ // output result to user die('VQMOD HAS BEEN INSTALLED ON YOUR SYSTEM!'); -} else { - echo 'vQmod Installer for OpenCart
'; - echo '
- Admin Folder Name: - -
'; -} \ No newline at end of file +} + +echo 'vQmod Installer for OpenCart
'; +echo '
+ Admin Folder Name: + +
'; diff --git a/vqmod.php b/vqmod.php new file mode 100644 index 0000000..6b2bbe1 --- /dev/null +++ b/vqmod.php @@ -0,0 +1,1012 @@ += self::$_lastModifiedTime && filemtime($cacheFile) >= $file_last_modified) { + return $cacheFile; + } + + if(isset(self::$_filesModded[$sourcePath])) { + return self::$_filesModded[$sourcePath]['cached'] ? $cacheFile : $sourceFile; + } + + $changed = false; + $fileHash = sha1_file($sourcePath); + $fileData = file_get_contents($sourcePath); + + foreach(self::$_mods as $modObject) { + foreach($modObject->mods as $path => $mods) { + if(self::_checkMatch($path, $modificationsPath)) { + $modObject->applyMod($mods, $fileData); + } + } + } + + if (sha1($fileData) != $fileHash) { + $writePath = $cacheFile; + if(!file_exists($writePath) || is_writable($writePath)) { + file_put_contents($writePath, $fileData, LOCK_EX); + $changed = true; + } + } else { + file_put_contents(self::path(self::$checkedCache, true), $stripped_filename . PHP_EOL, FILE_APPEND | LOCK_EX); + + // Prevent checked.cache file from duplicating lines when checked folder is above vqmod directory - Thanks adrianolmedo + $lines = file(self::path(self::$checkedCache, true)); + $lines = array_unique($lines); + file_put_contents(self::path(self::$checkedCache, true), implode($lines)); + + self::$_doNotMod[] = $sourcePath; + } + + self::$_filesModded[$sourcePath] = array('cached' => $changed); + return $changed ? $writePath : $sourcePath; + } + + /** + * VQMod::path() + * + * @param string $path File path + * @param bool $skip_real If true path is full not relative + * @return bool, string + * @description Returns the full true path of a file if it exists, otherwise false + */ + public static function path($path, $skip_real = false) { + $tmp = self::$_cwd . $path; + $realpath = $skip_real ? $tmp : self::_realpath($tmp); + if(!$realpath) { + return false; + } + return $realpath; + } + + /** + * VQMod::getCwd() + * + * @return string + * @description Returns current working directory + */ + public static function getCwd() { + return self::$_cwd; + } + + /** + * VQMod::dirCheck() + * + * @param string $path + * @return null + * @description Creates $path folder if it doesn't exist + */ + public static function dirCheck($path) { + if(!is_dir($path)) { + if(!mkdir($path)) { + die('VQMod::dirCheck - CANNOT CREATE "' . $path . '" DIRECTORY'); + } + } + } + + /** + * VQMod::handleXMLError() + * + * @description Error handler for bad XML files + */ + public static function handleXMLError($errno, $errstr, $errfile, $errline) { + if ($errno == E_WARNING && (substr_count($errstr, 'DOMDocument::load()') > 0)) { + throw new DOMException(str_replace('DOMDocument::load()', '', $errstr)); + } else { + return false; + } + } + + /** + * VQMod::_getMods() + * + * @return null + * @description Gets list of XML files in vqmod xml folder for processing + */ + private static function _getMods() { + + // Check and set folder + $vqmod_path = dirname(__FILE__, 1) . '/'; + + // Get the vqmod folder via the path + $vqmod_folder = basename($vqmod_path) . '/'; + + self::$_modFileList = glob(self::path($vqmod_folder . '/xml/', true) . '*.xml'); + + foreach(self::$_modFileList as $file) { + if(file_exists($file)) { + $lastMod = filemtime($file); + if($lastMod > self::$_lastModifiedTime){ + self::$_lastModifiedTime = $lastMod; + } + } + } + + $xml_folder_time = filemtime(self::path($vqmod_folder . '/xml')); + if($xml_folder_time > self::$_lastModifiedTime){ + self::$_lastModifiedTime = $xml_folder_time; + } + + $modCache = self::path(self::$modCache); + if(self::$_devMode || !file_exists($modCache)) { + self::$_lastModifiedTime = time(); + } elseif(file_exists($modCache) && filemtime($modCache) >= self::$_lastModifiedTime) { + $mods = file_get_contents($modCache); + if(!empty($mods)) + self::$_mods = unserialize($mods); + if(self::$_mods !== false) { + return; + } + } + + // Clear checked cache if rebuilding + file_put_contents(self::path(self::$checkedCache, true), '', LOCK_EX); + + if(self::$_modFileList) { + self::_parseMods(); + } else { + self::$log->write('VQMod::_getMods - NO XML FILES READABLE IN XML FOLDER'); + } + } + + /** + * VQMod::_parseMods() + * + * @return null + * @description Loops through xml files and attempts to load them as VQModObject's + */ + private static function _parseMods() { + + set_error_handler(array('VQMod', 'handleXMLError')); + + $dom = new DOMDocument('1.0', 'UTF-8'); + foreach(self::$_modFileList as $modFileKey => $modFile) { + if(file_exists($modFile)) { + try { + $dom->load($modFile); + $mod = $dom->getElementsByTagName('modification')->item(0); + $vqmver = $mod->getElementsByTagName('vqmver')->item(0); + + if($vqmver) { + $version_check = $vqmver->getAttribute('required'); + if(strtolower($version_check) == 'true') { + if(version_compare(self::$_vqversion, $vqmver->nodeValue, '<')) { + self::$log->write('VQMod::_parseMods - FILE "' . $modFile . '" REQUIRES VQMOD "' . $vqmver->nodeValue . '" OR ABOVE AND HAS BEEN SKIPPED'); + continue; + } + } + } + + self::$_mods[] = new VQModObject($mod, $modFile); + } catch (Exception $e) { + self::$log->write('VQMod::_parseMods - INVALID XML FILE: ' . $e->getMessage()); + } + } else { + self::$log->write('VQMod::_parseMods - FILE NOT FOUND: ' . $modFile); + } + } + + restore_error_handler(); + + $modCache = self::path(self::$modCache, true); + $result = file_put_contents($modCache, serialize(self::$_mods), LOCK_EX); + if(!$result) { + die('VQMod::_parseMods - "'.self::$modCache.'" FILE NOT WRITEABLE'); + } + } + + /** + * VQMod::_loadProtected() + * + * @return null + * @description Loads protected list and adds them to _doNotMod array + */ + private static function _loadProtected() { + $file = self::path(self::$protectedFilelist); + if($file && is_file($file)) { + $paths = file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + if(!empty($paths)) { + foreach($paths as $path) { + $fullPath = self::path($path); + if($fullPath && !in_array($fullPath, self::$_doNotMod)) { + self::$_doNotMod[] = $fullPath; + } + } + } + } + } + + /** + * VQMod::_loadChecked() + * + * @return null + * @description Loads already checked files and adds them to _doNotMod array + */ + private static function _loadChecked() { + $file = self::path(self::$checkedCache); + if($file && is_file($file)) { + $paths = file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + if(!empty($paths)) { + foreach($paths as $path) { + $fullPath = self::path($path, true); + if($fullPath) { + self::$_doNotMod[] = $fullPath; + } + } + } + } + } + + /** + * VQMod::_cacheName() + * + * @param string $file Filename to be converted to cache filename + * @return string + * @description Returns cache file name for a path + */ + private static function _cacheName($file) { + return self::$_cachePathFull . 'vq2-' . preg_replace('~[/\\\\]+~', '_', $file); + } + + /** + * VQMod::_setCwd() + * + * @param string $path Path to be used as current working directory + * @return null + * @description Sets the current working directory variable + */ + private static function _setCwd($path) { + self::$_cwd = self::_realpath($path); + } + + /** + * VQMod::_realpath() + * + * @param string $file + * @return string + * @description Returns real path of any path, adding directory slashes if necessary + */ + private static function _realpath($file) { + $path = realpath($file); + if(!$path) { + return false; + } + + if(is_dir($path)) { + $path = rtrim($path, self::$directorySeparator) . self::$directorySeparator; + } + + return $path; + } + + /** + * VQMod::_checkMatch() + * + * @param string $modFilePath Modification path from a node + * @param string $checkFilePath File path + * @return bool + * @description Checks a modification path against a file path + */ + private static function _checkMatch($modFilePath, $checkFilePath) { + $modFilePath = str_replace('\\', '/', $modFilePath); + $checkFilePath = str_replace('\\', '/', $checkFilePath); + + if(self::$windows) { + $modFilePath = strtolower($modFilePath); + $checkFilePath = strtolower($checkFilePath); + } + + if($modFilePath == $checkFilePath) { + $return = true; + } elseif(strpos($modFilePath, '*') !== false) { + $return = true; + $modParts = explode('/', $modFilePath); + $checkParts = explode('/', $checkFilePath); + + if(count($modParts) !== count($checkParts)) { + $return = false; + } else { + + $toCheck = array_diff_assoc($modParts, $checkParts); + + foreach($toCheck as $k => $part) { + if($part === '*') { + continue; + } elseif(strpos($part, '*') !== false) { + $part = preg_replace_callback('~([^*]+)~', 'VQMod::_quotePath', $part); + $part = str_replace('*', '[^/]*', $part); + $part = (bool) preg_match('~^' . $part . '$~', $checkParts[$k]); + + if($part) { + continue; + } + } elseif($part === $checkParts[$k]) { + continue; + } + + $return = false; + break; + } + + } + } else { + $return = false; + } + + return $return; + } + + /** + * VQMod::_quotePath() + * + * @param string $matches callback matches + * @return string + * @description apply's preg_quote to string from callback + */ + private static function _quotePath($matches) { + return preg_quote($matches[1], '~'); + } +} + +/** + * VQModLog + * @description Object to log information to a file + */ +class VQModLog { + private $_sep; + private $_defhash = 'da39a3ee5e6b4b0d3255bfef95601890afd80709'; + private $_logs = array(); + + /** + * VQModLog::__construct() + * + * @return null + * @description Object instantiation method + */ + public function __construct() { + $this->_sep = str_repeat('-', 70); + } + + /** + * VQModLog::__destruct() + * + * @return null + * @description Logs any messages to the log file just before object is destroyed + */ + public function __destruct() { + if(empty($this->_logs) || VQMod::$logging == false) { + return; + } + + $logPath = VQMod::path(VQMod::$logFolder . date('w_D') . '.log', true); + + $txt = array(); + + $txt[] = str_repeat('-', 10) . ' Date: ' . date('Y-m-d H:i:s') . ' ~ IP : ' . (isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : 'N/A') . ' ' . str_repeat('-', 10); + $txt[] = 'REQUEST URI : ' . $_SERVER['REQUEST_URI']; + + foreach($this->_logs as $count => $log) { + if($log['obj']) { + $vars = get_object_vars($log['obj']); + $txt[] = 'MOD DETAILS:'; + foreach($vars as $k => $v) { + if(is_string($v)) { + $txt[] = ' ' . str_pad($k, 10, ' ', STR_PAD_RIGHT) . ': ' . $v; + } + } + + } + + foreach($log['log'] as $msg) { + $txt[] = $msg; + } + + if ($count > count($this->_logs)-1) { + $txt[] = ''; + } + } + + $txt[] = $this->_sep; + $txt[] = str_repeat(PHP_EOL, 2); + $append = true; + + if(!file_exists($logPath)) { + $append = false; + } else { + $content = file_get_contents($logPath); + if(!empty($content) && strpos($content, ' Date: ' . date('Y-m-d ')) === false) { + $append = false; + } + } + + $result = file_put_contents($logPath, implode(PHP_EOL, $txt), ($append ? FILE_APPEND | LOCK_EX : LOCK_EX)); + if(!$result) { + die('VQModLog::__destruct - LOG FILE "' . $logPath . '" COULD NOT BE WRITTEN'); + } + } + + /** + * VQModLog::write() + * + * @param string $data Text to be added to log file + * @param VQModObject $obj Modification the error belongs to + * @return null + * @description Adds error to log object ready to be output + */ + public function write($data, VQModObject $obj = NULL) { + if($obj) { + $hash = sha1($obj->id); + } else { + $hash = $this->_defhash; + } + + if(empty($this->_logs[$hash])) { + $this->_logs[$hash] = array( + 'obj' => $obj, + 'log' => array() + ); + } + + if(VQMod::$fileModding) { + $this->_logs[$hash]['log'][] = PHP_EOL . 'File Name : ' . VQMod::$fileModding; + } + + $this->_logs[$hash]['log'][] = $data; + + } +} + +/** + * VQModObject + * @description Object for the that orchestrates each applied modification + */ +class VQModObject { + public $modFile = ''; + public $id = ''; + public $version = ''; + public $vqmver = ''; + public $author = ''; + public $mods = array(); + + private $_skip = false; + + /** + * VQModObject::__construct() + * + * @param DOMNode $node node + * @param string $modFile File modification is from + * @return null + * @description Loads modification meta information + */ + public function __construct(DOMNode $node, $modFile) { + if($node->hasChildNodes()) { + foreach($node->childNodes as $child) { + $name = (string) $child->nodeName; + if(isset($this->$name)) { + $this->$name = (string) $child->nodeValue; + } + } + } + + $this->modFile = $modFile; + $this->_parseMods($node); + } + + /** + * VQModObject::skip() + * + * @return bool + * @description Returns the skip status of a modification + */ + public function skip() { + return $this->_skip; + } + + /** + * VQModObject::applyMod() + * + * @param array $mods Array of search add nodes + * @param string $data File contents to be altered + * @return null + * @description Applies all modifications to the text data + */ + public function applyMod($mods, &$data) { + if($this->_skip) return; + $tmp = $data; + + foreach($mods as $mod) { + VQMod::$fileModding = $mod['fileToMod'] . '(' . $mod['opIndex'] . ')'; + if(!empty($mod['ignoreif'])) { + if($mod['ignoreif']->regex == 'true') { + if (preg_match($mod['ignoreif']->getContent(), $tmp)) { + continue; + } + } else { + if (strpos($tmp, $mod['ignoreif']->getContent()) !== false) { + continue; + } + } + } + + $indexCount = 0; + + $tmp = $this->_explodeData($tmp); + $lineMax = count($tmp) - 1; + + // tag attributes - Override attributes if set + foreach(array_keys((array)$mod['search']) as $key) { + if ($key == "\x0VQNode\x0_content") { continue; } + if ($key == "trim") { continue; } + if (isset($mod['add']->$key) && $mod['add']->$key) { + $mod['search']->$key = $mod['add']->$key; + } + } + + switch($mod['search']->position) { + case 'top': + $tmp[$mod['search']->offset] = $mod['add']->getContent() . $tmp[$mod['search']->offset]; + break; + + case 'bottom': + $offset = $lineMax - $mod['search']->offset; + if($offset < 0){ + $tmp[-1] = $mod['add']->getContent(); + } else { + $tmp[$offset] .= $mod['add']->getContent(); + } + break; + + default: + + $changed = false; + foreach($tmp as $lineNum => $line) { + if(strlen($mod['search']->getContent()) == 0) { + if($mod['error'] == 'log' || $mod['error'] == 'abort') { + VQMod::$log->write('VQModObject::applyMod - EMPTY SEARCH CONTENT ERROR', $this); + } + break; + } + + if($mod['search']->regex == 'true') { + $pos = @preg_match($mod['search']->getContent(), $line); + if($pos === false) { + if($mod['error'] == 'log' || $mod['error'] == 'abort' ) { + VQMod::$log->write('VQModObject::applyMod - INVALID REGEX ERROR - ' . $mod['search']->getContent(), $this); + } + break 2; + } elseif($pos == 0) { + $pos = false; + } + } else { + $pos = strpos($line, $mod['search']->getContent()); + } + + if($pos !== false) { + $indexCount++; + $changed = true; + + if(!$mod['search']->indexes() || ($mod['search']->indexes() && in_array($indexCount, $mod['search']->indexes()))) { + + switch($mod['search']->position) { + case 'before': + $offset = ($lineNum - $mod['search']->offset < 0) ? -1 : $lineNum - $mod['search']->offset; + $tmp[$offset] = empty($tmp[$offset]) ? $mod['add']->getContent() : $mod['add']->getContent() . "\n" . $tmp[$offset]; + break; + + case 'after': + $offset = ($lineNum + $mod['search']->offset > $lineMax) ? $lineMax : $lineNum + $mod['search']->offset; + $tmp[$offset] = $tmp[$offset] . "\n" . $mod['add']->getContent(); + break; + + case 'ibefore': + $tmp[$lineNum] = str_replace($mod['search']->getContent(), $mod['add']->getContent() . $mod['search']->getContent(), $line); + break; + + case 'iafter': + $tmp[$lineNum] = str_replace($mod['search']->getContent(), $mod['search']->getContent() . $mod['add']->getContent(), $line); + break; + + default: + if(!empty($mod['search']->offset)) { + if($mod['search']->offset > 0) { + for($i = 1; $i <= $mod['search']->offset; $i++) { + if(isset($tmp[$lineNum + $i])) { + $tmp[$lineNum + $i] = ''; + } + } + } elseif($mod['search']->offset < 0) { + for($i = -1; $i >= $mod['search']->offset; $i--) { + if(isset($tmp[$lineNum + $i])) { + $tmp[$lineNum + $i] = ''; + } + } + } + } + + if($mod['search']->regex == 'true') { + $tmp[$lineNum] = preg_replace($mod['search']->getContent(), $mod['add']->getContent(), $line); + } else { + $tmp[$lineNum] = str_replace($mod['search']->getContent(), $mod['add']->getContent(), $line); + } + break; + } + } + } + } + + if(!$changed) { + $skip = ($mod['error'] == 'skip' || $mod['error'] == 'log') ? ' (SKIPPED)' : ' (ABORTING MOD)'; + + if($mod['error'] == 'log' || $mod['error'] == 'abort') { + VQMod::$log->write('VQModObject::applyMod - SEARCH NOT FOUND' . $skip . ': ' . $mod['search']->getContent(), $this); + } + + if($mod['error'] == 'abort') { + $this->_skip = true; + return; + } + + } + + break; + } + ksort($tmp); + $tmp = $this->_implodeData($tmp); + } + + VQMod::$fileModding = false; + + $data = $tmp; + } + + /** + * VQModObject::_parseMods() + * + * @param DOMNode $node node to be parsed + * @return null + * @description Parses modifications in preparation for the applyMod method to work + */ + private function _parseMods(DOMNode $node){ + $files = $node->getElementsByTagName('file'); + + $replaces = VQMod::$replaces; + + foreach($files as $file) { + $path = $file->getAttribute('path') ? $file->getAttribute('path') : ''; + $filesToMod = explode(',', $file->getAttribute('name')); + + foreach($filesToMod as $filename) { + + $fileToMod = $path . $filename; + if(!empty($replaces)) { + foreach($replaces as $r) { + if(count($r) == 2) { + $fileToMod = preg_replace($r[0], $r[1], $fileToMod); + } + } + } + + $error = ($file->hasAttribute('error')) ? $file->getAttribute('error') : 'log'; + $fullPath = VQMod::path($fileToMod); + + if(!$fullPath || !file_exists($fullPath)){ + if(strpos($fileToMod, '*') !== false) { + $fullPath = VQMod::getCwd() . $fileToMod; + } else { + if ($error == 'log' || $error == 'abort') { + $skip = ($error == 'log') ? ' (SKIPPED)' : ' (ABORTING MOD)'; + VQMod::$log->write('VQModObject::parseMods - Could not resolve path for [' . $fileToMod . ']' . $skip, $this); + } + + if ($error == 'log' || $error == 'skip') { + continue; + } elseif ($error == 'abort') { + return false; + } + } + } + + $operations = $file->getElementsByTagName('operation'); + + foreach($operations as $opIndex => $operation) { + VQMod::$fileModding = $fileToMod . '(' . $opIndex . ')'; + $skipOperation = false; + + $error = ($operation->hasAttribute('error')) ? $operation->getAttribute('error') : 'abort'; + $ignoreif = $operation->getElementsByTagName('ignoreif')->item(0); + + if($ignoreif) { + $ignoreif = new VQSearchNode($ignoreif); + } else { + $ignoreif = false; + } + + $search = $operation->getElementsByTagName('search')->item(0); + $add = $operation->getElementsByTagName('add')->item(0); + + if(!$search) { + VQMod::$log->write('Operation tag missing', $this); + $skipOperation = true; + } + + if(!$add) { + VQMod::$log->write('Operation tag missing', $this); + $skipOperation = true; + } + + if(!$skipOperation) { + $this->mods[$fullPath][] = array( + 'search' => new VQSearchNode($search), + 'add' => new VQAddNode($add), + 'ignoreif' => $ignoreif, + 'error' => $error, + 'fileToMod' => $fileToMod, + 'opIndex' => $opIndex, + ); + } + } + VQMod::$fileModding = false; + } + } + } + + /** + * VQModObject::_explodeData() + * + * @param string $data file contents + * @return string + * @description Splits a file into an array of individual lines + */ + private function _explodeData($data) { + return explode("\n", $data); + } + + /** + * VQModObject::_implodeData() + * + * @param array $data Array of lines + * @return string + * @description Joins an array of lines back into a text file + */ + private function _implodeData($data) { + return implode("\n", $data); + } +} + +/** + * VQNode + * @description Basic node object blueprint + */ +class VQNode { + public $regex = 'false'; + public $trim = 'false'; + private $_content = ''; + + /** + * VQNode::__construct() + * + * @param DOMNode $node Search/add node + * @return null + * @description Parses the node attributes and sets the node property + */ + public function __construct(DOMNode $node) { + $this->_content = $node->nodeValue; + + if($node->hasAttributes()) { + foreach($node->attributes as $attr) { + $name = $attr->nodeName; + if(isset($this->$name)) { + $this->$name = $attr->nodeValue; + } + } + } + } + + /** + * VQNode::getContent() + * + * @return string + * @description Returns the content, trimmed if applicable + */ + public function getContent() { + $content = ($this->trim == 'true') ? trim($this->_content) : $this->_content; + return $content; + } +} + +/** + * VQSearchNode + * @description Object for the xml tags + */ +class VQSearchNode extends VQNode { + public $position = 'replace'; + public $offset = 0; + public $index = 'false'; + public $regex = 'false'; + public $trim = 'true'; + + /** + * VQSearchNode::indexes() + * + * @return bool, array + * @description Returns the index values to use the search on, or false if none + */ + public function indexes() { + if($this->index == 'false') { + return false; + } + $tmp = explode(',', $this->index); + foreach($tmp as $k => $v) { + if(!is_int($v)) { + unset($k); + } + } + $tmp = array_unique($tmp); + return empty($tmp) ? false : $tmp; + } +} + +/** + * VQAddNode + * @description Object for the xml tags + */ +class VQAddNode extends VQNode { + public $position = false; + public $offset = false; + public $index = false; + public $regex = false; + public $trim = 'false'; +} diff --git a/xml/vqmod_opencart.xml b/xml/vqmod_opencart.xml index e48d450..f9ed816 100644 --- a/xml/vqmod_opencart.xml +++ b/xml/vqmod_opencart.xml @@ -1,59 +1,59 @@ - - - VQMOD CORE FOR OPENCART - DO NOT REMOVE - 1.4.x and above - 2.6.7 - vqmod.com - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - root) + 1); - ]]> - - - + + + VQMOD CORE FOR OPENCART - DO NOT REMOVE + 1.4.x and above + 2.6.7 + vqmod.com + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + root) + 1); + ]]> + + + \ No newline at end of file From 76cb41f306993a54cc57e3ce96ab8c50e5de9255 Mon Sep 17 00:00:00 2001 From: Konstantinos Botonakis Date: Tue, 4 Jun 2024 13:54:07 +0100 Subject: [PATCH 2/2] Load all xml files in subfolders + fix for extra / --- vqmod.php | 1956 +++++++++++++++++++++++++++-------------------------- 1 file changed, 987 insertions(+), 969 deletions(-) diff --git a/vqmod.php b/vqmod.php index 6b2bbe1..c5c16ae 100644 --- a/vqmod.php +++ b/vqmod.php @@ -5,500 +5,518 @@ */ abstract class VQMod { - public static $_vqversion = '2.6.8'; // Current version number - - private static $_modFileList = array(); // Array of xml files - private static $_mods = array(); // Array of modifications to apply - private static $_filesModded = array(); // Array of already modified files - private static $_doNotMod = array(); // Array of files not to apply modifications to - private static $_cwd = ''; // Current working directory path - private static $_folderChecks = false; // Flag for already checked log/cache folders exist - private static $_cachePathFull = ''; // Full cache folder path - private static $_lastModifiedTime = 0; // Integer representing the last time anything was modified - private static $_devMode = false; // Flag for developer mode - disables caching while true - - public static $logFolder = 'vqmod/logs/'; // Path log folders are stored in - public static $vqCachePath = 'vqmod/vqcache/'; // Relative path to cache file directory - public static $modCache = 'vqmod/mods.cache'; // Relative path to serialized mods array cache file - public static $checkedCache = 'vqmod/checked.cache'; // Relative path to already checked files array cache file - public static $protectedFilelist = 'vqmod/vqprotect.txt'; // Relative path to protected files array cache file - public static $pathReplaces = 'vqmod/pathReplaces.php'; // Relative path to dynamic path replacement file - public static $logging = true; // Flag to enabled/disable logging - public static $log; // Log object reference - public static $fileModding = false; // Reference to the current file being modified by vQmod for logging - public static $directorySeparator = ''; // System directory separator (/ or \ depending on OS) - public static $replaces = array(); // Array of regex replaces to perform on file paths - public static $windows = false; // Flag determining if windows or *nix based - - /** - * VQMod::bootup() - * - * @param bool $path File path to use - * @param bool $logging Enable/disabled logging - * @return null - * @description Startup of VQMod - */ - public static function bootup($path = false, $logging = true) { - if(!class_exists('DOMDocument')) { - die('VQMod::bootup - ERROR - YOU NEED THE PHP "DOMDocument" EXTENSION INSTALLED TO USE VQMod'); - } - - // Check and set folder - $vqmod_path = dirname(__FILE__, 1) . '/'; - - // Get the vqmod folder via the path - $vqmod_folder = basename($vqmod_path) . '/'; - $logs_folder = $vqmod_folder . 'logs/'; - $caches_folder = $vqmod_folder . 'vqcache/'; - $mods_cache = $vqmod_folder . 'mods.cache'; - $checked_caches = $vqmod_folder . 'checked.cache'; - $protected_filelist = $vqmod_folder . 'vqprotect.txt'; - $path_replaces = $vqmod_folder . 'pathReplaces.php'; - - if ($logs_folder !== self::$logFolder) { - self::$logFolder = $logs_folder; - } - if ($caches_folder !== self::$vqCachePath) { - self::$vqCachePath = $caches_folder; - } - if ($mods_cache !== self::$modCache) { - self::$modCache = $mods_cache; - } - if ($checked_caches !== self::$checkedCache) { - self::$checkedCache = $checked_caches; - } - if ($protected_filelist !== self::$protectedFilelist) { - self::$protectedFilelist = $protected_filelist; - } - if ($path_replaces !== self::$pathReplaces) { - self::$pathReplaces = $path_replaces; - } - - - if(strtoupper(substr(PHP_OS, 0, 3)) == 'WIN') { - self::$windows = true; - } - - self::$directorySeparator = defined('DIRECTORY_SEPARATOR') ? DIRECTORY_SEPARATOR : '/'; - - if(!$path){ - $path = dirname(dirname(__FILE__)); - } - self::_setCwd($path); - - self::$logging = (bool) $logging; - self::$log = new VQModLog(); - - $replacesPath = self::path(self::$pathReplaces); - $replaces = array(); - if($replacesPath) { - include_once($replacesPath); - self::$_lastModifiedTime = filemtime($replacesPath); - } - - self::$replaces = !is_array($replaces) ? array() : $replaces; - self::_getMods(); - self::_loadProtected(); - self::_loadChecked(); - } - - /** - * VQMod::modCheck() - * - * @param string $sourceFile path for file to be modified - * @param string $modificationFile path for mods to be applied to file - * @return string - * @description Checks if a file has modifications and applies them, returning cache files or the file name - */ - public static function modCheck($sourceFile, $modificationFile = false) { - - if(!self::$_folderChecks) { - - if(self::$logging) { - // Create log folder if it doesn't exist - $log_folder = self::path(self::$logFolder, true); - self::dirCheck($log_folder); - } - - // Create cache folder if it doesn't exist - $cache_folder = self::path(self::$vqCachePath, true); - self::dirCheck($cache_folder); - - // Store cache folder path to save on repeat checks for path validity - self::$_cachePathFull = self::path(self::$vqCachePath); - - self::$_folderChecks = true; - } - - if(!preg_match('%^([a-z]:)?[\\\\/]%i', $sourceFile)) { - $sourcePath = self::path($sourceFile); - } else { - $sourcePath = self::_realpath($sourceFile); - } - - if($modificationFile !== false) { - if(!preg_match('%^([a-z]:)?[\\\\/]%i', $modificationFile)) { - $modificationsPath = self::path($modificationFile); - } else { - $modificationsPath = self::_realpath($modificationFile); - } - } else { - $modificationsPath = $sourcePath; - } - - if(!$sourcePath || is_dir($sourcePath) || in_array($sourcePath, self::$_doNotMod)) { - return $sourceFile; - } - - $stripped_filename = preg_replace('~^' . preg_quote(self::getCwd(), '~') . '~i', '', $sourcePath); - $cacheFile = self::_cacheName($stripped_filename); - $file_last_modified = filemtime($sourcePath); - - if(file_exists($cacheFile) && filemtime($cacheFile) >= self::$_lastModifiedTime && filemtime($cacheFile) >= $file_last_modified) { - return $cacheFile; - } - - if(isset(self::$_filesModded[$sourcePath])) { - return self::$_filesModded[$sourcePath]['cached'] ? $cacheFile : $sourceFile; - } - - $changed = false; - $fileHash = sha1_file($sourcePath); - $fileData = file_get_contents($sourcePath); - - foreach(self::$_mods as $modObject) { - foreach($modObject->mods as $path => $mods) { - if(self::_checkMatch($path, $modificationsPath)) { - $modObject->applyMod($mods, $fileData); - } - } - } - - if (sha1($fileData) != $fileHash) { - $writePath = $cacheFile; - if(!file_exists($writePath) || is_writable($writePath)) { - file_put_contents($writePath, $fileData, LOCK_EX); - $changed = true; - } - } else { - file_put_contents(self::path(self::$checkedCache, true), $stripped_filename . PHP_EOL, FILE_APPEND | LOCK_EX); - - // Prevent checked.cache file from duplicating lines when checked folder is above vqmod directory - Thanks adrianolmedo - $lines = file(self::path(self::$checkedCache, true)); - $lines = array_unique($lines); - file_put_contents(self::path(self::$checkedCache, true), implode($lines)); - - self::$_doNotMod[] = $sourcePath; - } - - self::$_filesModded[$sourcePath] = array('cached' => $changed); - return $changed ? $writePath : $sourcePath; - } - - /** - * VQMod::path() - * - * @param string $path File path - * @param bool $skip_real If true path is full not relative - * @return bool, string - * @description Returns the full true path of a file if it exists, otherwise false - */ - public static function path($path, $skip_real = false) { - $tmp = self::$_cwd . $path; - $realpath = $skip_real ? $tmp : self::_realpath($tmp); - if(!$realpath) { - return false; - } - return $realpath; - } - - /** - * VQMod::getCwd() - * - * @return string - * @description Returns current working directory - */ - public static function getCwd() { - return self::$_cwd; - } - - /** - * VQMod::dirCheck() - * - * @param string $path - * @return null - * @description Creates $path folder if it doesn't exist - */ - public static function dirCheck($path) { - if(!is_dir($path)) { - if(!mkdir($path)) { - die('VQMod::dirCheck - CANNOT CREATE "' . $path . '" DIRECTORY'); - } - } - } - - /** - * VQMod::handleXMLError() - * - * @description Error handler for bad XML files - */ - public static function handleXMLError($errno, $errstr, $errfile, $errline) { - if ($errno == E_WARNING && (substr_count($errstr, 'DOMDocument::load()') > 0)) { - throw new DOMException(str_replace('DOMDocument::load()', '', $errstr)); - } else { - return false; - } - } - - /** - * VQMod::_getMods() - * - * @return null - * @description Gets list of XML files in vqmod xml folder for processing - */ - private static function _getMods() { - - // Check and set folder - $vqmod_path = dirname(__FILE__, 1) . '/'; - - // Get the vqmod folder via the path - $vqmod_folder = basename($vqmod_path) . '/'; - - self::$_modFileList = glob(self::path($vqmod_folder . '/xml/', true) . '*.xml'); - - foreach(self::$_modFileList as $file) { - if(file_exists($file)) { - $lastMod = filemtime($file); - if($lastMod > self::$_lastModifiedTime){ - self::$_lastModifiedTime = $lastMod; - } - } - } - - $xml_folder_time = filemtime(self::path($vqmod_folder . '/xml')); - if($xml_folder_time > self::$_lastModifiedTime){ - self::$_lastModifiedTime = $xml_folder_time; - } - - $modCache = self::path(self::$modCache); - if(self::$_devMode || !file_exists($modCache)) { - self::$_lastModifiedTime = time(); - } elseif(file_exists($modCache) && filemtime($modCache) >= self::$_lastModifiedTime) { - $mods = file_get_contents($modCache); - if(!empty($mods)) - self::$_mods = unserialize($mods); - if(self::$_mods !== false) { - return; - } - } - - // Clear checked cache if rebuilding - file_put_contents(self::path(self::$checkedCache, true), '', LOCK_EX); - - if(self::$_modFileList) { - self::_parseMods(); - } else { - self::$log->write('VQMod::_getMods - NO XML FILES READABLE IN XML FOLDER'); - } - } - - /** - * VQMod::_parseMods() - * - * @return null - * @description Loops through xml files and attempts to load them as VQModObject's - */ - private static function _parseMods() { - - set_error_handler(array('VQMod', 'handleXMLError')); - - $dom = new DOMDocument('1.0', 'UTF-8'); - foreach(self::$_modFileList as $modFileKey => $modFile) { - if(file_exists($modFile)) { - try { - $dom->load($modFile); - $mod = $dom->getElementsByTagName('modification')->item(0); - $vqmver = $mod->getElementsByTagName('vqmver')->item(0); - - if($vqmver) { - $version_check = $vqmver->getAttribute('required'); - if(strtolower($version_check) == 'true') { - if(version_compare(self::$_vqversion, $vqmver->nodeValue, '<')) { - self::$log->write('VQMod::_parseMods - FILE "' . $modFile . '" REQUIRES VQMOD "' . $vqmver->nodeValue . '" OR ABOVE AND HAS BEEN SKIPPED'); - continue; - } - } - } - - self::$_mods[] = new VQModObject($mod, $modFile); - } catch (Exception $e) { - self::$log->write('VQMod::_parseMods - INVALID XML FILE: ' . $e->getMessage()); - } - } else { - self::$log->write('VQMod::_parseMods - FILE NOT FOUND: ' . $modFile); - } - } - - restore_error_handler(); - - $modCache = self::path(self::$modCache, true); - $result = file_put_contents($modCache, serialize(self::$_mods), LOCK_EX); - if(!$result) { - die('VQMod::_parseMods - "'.self::$modCache.'" FILE NOT WRITEABLE'); - } - } - - /** - * VQMod::_loadProtected() - * - * @return null - * @description Loads protected list and adds them to _doNotMod array - */ - private static function _loadProtected() { - $file = self::path(self::$protectedFilelist); - if($file && is_file($file)) { - $paths = file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); - if(!empty($paths)) { - foreach($paths as $path) { - $fullPath = self::path($path); - if($fullPath && !in_array($fullPath, self::$_doNotMod)) { - self::$_doNotMod[] = $fullPath; - } - } - } - } - } - - /** - * VQMod::_loadChecked() - * - * @return null - * @description Loads already checked files and adds them to _doNotMod array - */ - private static function _loadChecked() { - $file = self::path(self::$checkedCache); - if($file && is_file($file)) { - $paths = file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); - if(!empty($paths)) { - foreach($paths as $path) { - $fullPath = self::path($path, true); - if($fullPath) { - self::$_doNotMod[] = $fullPath; - } - } - } - } - } - - /** - * VQMod::_cacheName() - * - * @param string $file Filename to be converted to cache filename - * @return string - * @description Returns cache file name for a path - */ - private static function _cacheName($file) { - return self::$_cachePathFull . 'vq2-' . preg_replace('~[/\\\\]+~', '_', $file); - } - - /** - * VQMod::_setCwd() - * - * @param string $path Path to be used as current working directory - * @return null - * @description Sets the current working directory variable - */ - private static function _setCwd($path) { - self::$_cwd = self::_realpath($path); - } - - /** - * VQMod::_realpath() - * - * @param string $file - * @return string - * @description Returns real path of any path, adding directory slashes if necessary - */ - private static function _realpath($file) { - $path = realpath($file); - if(!$path) { - return false; - } - - if(is_dir($path)) { - $path = rtrim($path, self::$directorySeparator) . self::$directorySeparator; - } - - return $path; - } - - /** - * VQMod::_checkMatch() - * - * @param string $modFilePath Modification path from a node - * @param string $checkFilePath File path - * @return bool - * @description Checks a modification path against a file path - */ - private static function _checkMatch($modFilePath, $checkFilePath) { - $modFilePath = str_replace('\\', '/', $modFilePath); - $checkFilePath = str_replace('\\', '/', $checkFilePath); - - if(self::$windows) { - $modFilePath = strtolower($modFilePath); - $checkFilePath = strtolower($checkFilePath); - } - - if($modFilePath == $checkFilePath) { - $return = true; - } elseif(strpos($modFilePath, '*') !== false) { - $return = true; - $modParts = explode('/', $modFilePath); - $checkParts = explode('/', $checkFilePath); - - if(count($modParts) !== count($checkParts)) { - $return = false; - } else { - - $toCheck = array_diff_assoc($modParts, $checkParts); - - foreach($toCheck as $k => $part) { - if($part === '*') { - continue; - } elseif(strpos($part, '*') !== false) { - $part = preg_replace_callback('~([^*]+)~', 'VQMod::_quotePath', $part); - $part = str_replace('*', '[^/]*', $part); - $part = (bool) preg_match('~^' . $part . '$~', $checkParts[$k]); - - if($part) { - continue; - } - } elseif($part === $checkParts[$k]) { - continue; - } - - $return = false; - break; - } - - } - } else { - $return = false; - } - - return $return; - } - - /** - * VQMod::_quotePath() - * - * @param string $matches callback matches - * @return string - * @description apply's preg_quote to string from callback - */ - private static function _quotePath($matches) { - return preg_quote($matches[1], '~'); - } + public static $_vqversion = '2.6.8'; // Current version number + + private static $_modFileList = array(); // Array of xml files + private static $_mods = array(); // Array of modifications to apply + private static $_filesModded = array(); // Array of already modified files + private static $_doNotMod = array(); // Array of files not to apply modifications to + private static $_cwd = ''; // Current working directory path + private static $_folderChecks = false; // Flag for already checked log/cache folders exist + private static $_cachePathFull = ''; // Full cache folder path + private static $_lastModifiedTime = 0; // Integer representing the last time anything was modified + private static $_devMode = false; // Flag for developer mode - disables caching while true + + public static $logFolder = 'vqmod/logs/'; // Path log folders are stored in + public static $vqCachePath = 'vqmod/vqcache/'; // Relative path to cache file directory + public static $modCache = 'vqmod/mods.cache'; // Relative path to serialized mods array cache file + public static $checkedCache = 'vqmod/checked.cache'; // Relative path to already checked files array cache file + public static $protectedFilelist = 'vqmod/vqprotect.txt'; // Relative path to protected files array cache file + public static $pathReplaces = 'vqmod/pathReplaces.php'; // Relative path to dynamic path replacement file + public static $logging = true; // Flag to enabled/disable logging + public static $log; // Log object reference + public static $fileModding = false; // Reference to the current file being modified by vQmod for logging + public static $directorySeparator = ''; // System directory separator (/ or \ depending on OS) + public static $replaces = array(); // Array of regex replaces to perform on file paths + public static $windows = false; // Flag determining if windows or *nix based + + /** + * VQMod::bootup() + * + * @param bool $path File path to use + * @param bool $logging Enable/disabled logging + * @return null + * @description Startup of VQMod + */ + public static function bootup($path = false, $logging = true) { + if(!class_exists('DOMDocument')) { + die('VQMod::bootup - ERROR - YOU NEED THE PHP "DOMDocument" EXTENSION INSTALLED TO USE VQMod'); + } + + // Check and set folder + $vqmod_path = dirname(__FILE__, 1) . '/'; + + // Get the vqmod folder via the path + $vqmod_folder = basename($vqmod_path) . '/'; + $logs_folder = $vqmod_folder . 'logs/'; + $caches_folder = $vqmod_folder . 'vqcache/'; + $mods_cache = $vqmod_folder . 'mods.cache'; + $checked_caches = $vqmod_folder . 'checked.cache'; + $protected_filelist = $vqmod_folder . 'vqprotect.txt'; + $path_replaces = $vqmod_folder . 'pathReplaces.php'; + + if ($logs_folder !== self::$logFolder) { + self::$logFolder = $logs_folder; + } + if ($caches_folder !== self::$vqCachePath) { + self::$vqCachePath = $caches_folder; + } + if ($mods_cache !== self::$modCache) { + self::$modCache = $mods_cache; + } + if ($checked_caches !== self::$checkedCache) { + self::$checkedCache = $checked_caches; + } + if ($protected_filelist !== self::$protectedFilelist) { + self::$protectedFilelist = $protected_filelist; + } + if ($path_replaces !== self::$pathReplaces) { + self::$pathReplaces = $path_replaces; + } + + + if(strtoupper(substr(PHP_OS, 0, 3)) == 'WIN') { + self::$windows = true; + } + + self::$directorySeparator = defined('DIRECTORY_SEPARATOR') ? DIRECTORY_SEPARATOR : '/'; + + if(!$path){ + $path = dirname(dirname(__FILE__)); + } + self::_setCwd($path); + + self::$logging = (bool) $logging; + self::$log = new VQModLog(); + + $replacesPath = self::path(self::$pathReplaces); + $replaces = array(); + if($replacesPath) { + include_once($replacesPath); + self::$_lastModifiedTime = filemtime($replacesPath); + } + + self::$replaces = !is_array($replaces) ? array() : $replaces; + self::_getMods(); + self::_loadProtected(); + self::_loadChecked(); + } + + /** + * VQMod::modCheck() + * + * @param string $sourceFile path for file to be modified + * @param string $modificationFile path for mods to be applied to file + * @return string + * @description Checks if a file has modifications and applies them, returning cache files or the file name + */ + public static function modCheck($sourceFile, $modificationFile = false) { + + if(!self::$_folderChecks) { + + if(self::$logging) { + // Create log folder if it doesn't exist + $log_folder = self::path(self::$logFolder, true); + self::dirCheck($log_folder); + } + + // Create cache folder if it doesn't exist + $cache_folder = self::path(self::$vqCachePath, true); + self::dirCheck($cache_folder); + + // Store cache folder path to save on repeat checks for path validity + self::$_cachePathFull = self::path(self::$vqCachePath); + + self::$_folderChecks = true; + } + + if(!preg_match('%^([a-z]:)?[\\\\/]%i', $sourceFile)) { + $sourcePath = self::path($sourceFile); + } else { + $sourcePath = self::_realpath($sourceFile); + } + + if($modificationFile !== false) { + if(!preg_match('%^([a-z]:)?[\\\\/]%i', $modificationFile)) { + $modificationsPath = self::path($modificationFile); + } else { + $modificationsPath = self::_realpath($modificationFile); + } + } else { + $modificationsPath = $sourcePath; + } + + if(!$sourcePath || is_dir($sourcePath) || in_array($sourcePath, self::$_doNotMod)) { + return $sourceFile; + } + + $stripped_filename = preg_replace('~^' . preg_quote(self::getCwd(), '~') . '~i', '', $sourcePath); + $cacheFile = self::_cacheName($stripped_filename); + $file_last_modified = filemtime($sourcePath); + + if(file_exists($cacheFile) && filemtime($cacheFile) >= self::$_lastModifiedTime && filemtime($cacheFile) >= $file_last_modified) { + return $cacheFile; + } + + if(isset(self::$_filesModded[$sourcePath])) { + return self::$_filesModded[$sourcePath]['cached'] ? $cacheFile : $sourceFile; + } + + $changed = false; + $fileHash = sha1_file($sourcePath); + $fileData = file_get_contents($sourcePath); + + foreach(self::$_mods as $modObject) { + foreach($modObject->mods as $path => $mods) { + if(self::_checkMatch($path, $modificationsPath)) { + $modObject->applyMod($mods, $fileData); + } + } + } + + if (sha1($fileData) != $fileHash) { + $writePath = $cacheFile; + if(!file_exists($writePath) || is_writable($writePath)) { + file_put_contents($writePath, $fileData, LOCK_EX); + $changed = true; + } + } else { + file_put_contents(self::path(self::$checkedCache, true), $stripped_filename . PHP_EOL, FILE_APPEND | LOCK_EX); + + // Prevent checked.cache file from duplicating lines when checked folder is above vqmod directory - Thanks adrianolmedo + $lines = file(self::path(self::$checkedCache, true)); + $lines = array_unique($lines); + file_put_contents(self::path(self::$checkedCache, true), implode($lines)); + + self::$_doNotMod[] = $sourcePath; + } + + self::$_filesModded[$sourcePath] = array('cached' => $changed); + return $changed ? $writePath : $sourcePath; + } + + /** + * VQMod::path() + * + * @param string $path File path + * @param bool $skip_real If true path is full not relative + * @return bool, string + * @description Returns the full true path of a file if it exists, otherwise false + */ + public static function path($path, $skip_real = false) { + $tmp = self::$_cwd . $path; + $realpath = $skip_real ? $tmp : self::_realpath($tmp); + if(!$realpath) { + return false; + } + return $realpath; + } + + /** + * VQMod::getCwd() + * + * @return string + * @description Returns current working directory + */ + public static function getCwd() { + return self::$_cwd; + } + + /** + * VQMod::dirCheck() + * + * @param string $path + * @return null + * @description Creates $path folder if it doesn't exist + */ + public static function dirCheck($path) { + if(!is_dir($path)) { + if(!mkdir($path)) { + die('VQMod::dirCheck - CANNOT CREATE "' . $path . '" DIRECTORY'); + } + } + } + + /** + * VQMod::handleXMLError() + * + * @description Error handler for bad XML files + */ + public static function handleXMLError($errno, $errstr, $errfile, $errline) { + if ($errno == E_WARNING && (substr_count($errstr, 'DOMDocument::load()') > 0)) { + throw new DOMException(str_replace('DOMDocument::load()', '', $errstr)); + } else { + return false; + } + } + + /** + * @param $pattern + * @param $flags + * @return array|false + */ + private static function glob_recursive($pattern, $flags = 0) + { + $files = glob($pattern, $flags); + + foreach (glob(dirname($pattern).'/*', GLOB_ONLYDIR|GLOB_NOSORT) as $dir) + { + $files = array_merge($files, self::glob_recursive($dir.'/'.basename($pattern), $flags)); + } + + return $files; + } + + + /** + * VQMod::_getMods() + * + * @return null + * @description Gets list of XML files in vqmod xml folder for processing + */ + private static function _getMods() { + + // Check and set folder + $vqmod_path = dirname(__FILE__, 1) . '/'; + + // Get the vqmod folder via the path + $vqmod_folder = basename($vqmod_path); + + self::$_modFileList = self::glob_recursive(self::path($vqmod_folder . '/xml/', true) . '*.xml'); + + foreach(self::$_modFileList as $file) { + if(file_exists($file)) { + $lastMod = filemtime($file); + if($lastMod > self::$_lastModifiedTime){ + self::$_lastModifiedTime = $lastMod; + } + } + } + + $xml_folder_time = filemtime(self::path($vqmod_folder . '/xml')); + if($xml_folder_time > self::$_lastModifiedTime){ + self::$_lastModifiedTime = $xml_folder_time; + } + + $modCache = self::path(self::$modCache); + if(self::$_devMode || !file_exists($modCache)) { + self::$_lastModifiedTime = time(); + } elseif(file_exists($modCache) && filemtime($modCache) >= self::$_lastModifiedTime) { + $mods = file_get_contents($modCache); + if(!empty($mods)) + self::$_mods = unserialize($mods); + if(self::$_mods !== false) { + return; + } + } + + // Clear checked cache if rebuilding + file_put_contents(self::path(self::$checkedCache, true), '', LOCK_EX); + + if(self::$_modFileList) { + self::_parseMods(); + } else { + self::$log->write('VQMod::_getMods - NO XML FILES READABLE IN XML FOLDER'); + } + } + + /** + * VQMod::_parseMods() + * + * @return null + * @description Loops through xml files and attempts to load them as VQModObject's + */ + private static function _parseMods() { + + set_error_handler(array('VQMod', 'handleXMLError')); + + $dom = new DOMDocument('1.0', 'UTF-8'); + foreach(self::$_modFileList as $modFileKey => $modFile) { + if(file_exists($modFile)) { + try { + $dom->load($modFile); + $mod = $dom->getElementsByTagName('modification')->item(0); + $vqmver = $mod->getElementsByTagName('vqmver')->item(0); + + if($vqmver) { + $version_check = $vqmver->getAttribute('required'); + if(strtolower($version_check) == 'true') { + if(version_compare(self::$_vqversion, $vqmver->nodeValue, '<')) { + self::$log->write('VQMod::_parseMods - FILE "' . $modFile . '" REQUIRES VQMOD "' . $vqmver->nodeValue . '" OR ABOVE AND HAS BEEN SKIPPED'); + continue; + } + } + } + + self::$_mods[] = new VQModObject($mod, $modFile); + } catch (Exception $e) { + self::$log->write('VQMod::_parseMods - INVALID XML FILE: ' . $e->getMessage()); + } + } else { + self::$log->write('VQMod::_parseMods - FILE NOT FOUND: ' . $modFile); + } + } + + restore_error_handler(); + + $modCache = self::path(self::$modCache, true); + $result = file_put_contents($modCache, serialize(self::$_mods), LOCK_EX); + if(!$result) { + die('VQMod::_parseMods - "'.self::$modCache.'" FILE NOT WRITEABLE'); + } + } + + /** + * VQMod::_loadProtected() + * + * @return null + * @description Loads protected list and adds them to _doNotMod array + */ + private static function _loadProtected() { + $file = self::path(self::$protectedFilelist); + if($file && is_file($file)) { + $paths = file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + if(!empty($paths)) { + foreach($paths as $path) { + $fullPath = self::path($path); + if($fullPath && !in_array($fullPath, self::$_doNotMod)) { + self::$_doNotMod[] = $fullPath; + } + } + } + } + } + + /** + * VQMod::_loadChecked() + * + * @return null + * @description Loads already checked files and adds them to _doNotMod array + */ + private static function _loadChecked() { + $file = self::path(self::$checkedCache); + if($file && is_file($file)) { + $paths = file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + if(!empty($paths)) { + foreach($paths as $path) { + $fullPath = self::path($path, true); + if($fullPath) { + self::$_doNotMod[] = $fullPath; + } + } + } + } + } + + /** + * VQMod::_cacheName() + * + * @param string $file Filename to be converted to cache filename + * @return string + * @description Returns cache file name for a path + */ + private static function _cacheName($file) { + return self::$_cachePathFull . 'vq2-' . preg_replace('~[/\\\\]+~', '_', $file); + } + + /** + * VQMod::_setCwd() + * + * @param string $path Path to be used as current working directory + * @return null + * @description Sets the current working directory variable + */ + private static function _setCwd($path) { + self::$_cwd = self::_realpath($path); + } + + /** + * VQMod::_realpath() + * + * @param string $file + * @return string + * @description Returns real path of any path, adding directory slashes if necessary + */ + private static function _realpath($file) { + $path = realpath($file); + if(!$path) { + return false; + } + + if(is_dir($path)) { + $path = rtrim($path, self::$directorySeparator) . self::$directorySeparator; + } + + return $path; + } + + /** + * VQMod::_checkMatch() + * + * @param string $modFilePath Modification path from a node + * @param string $checkFilePath File path + * @return bool + * @description Checks a modification path against a file path + */ + private static function _checkMatch($modFilePath, $checkFilePath) { + $modFilePath = str_replace('\\', '/', $modFilePath); + $checkFilePath = str_replace('\\', '/', $checkFilePath); + + if(self::$windows) { + $modFilePath = strtolower($modFilePath); + $checkFilePath = strtolower($checkFilePath); + } + + if($modFilePath == $checkFilePath) { + $return = true; + } elseif(strpos($modFilePath, '*') !== false) { + $return = true; + $modParts = explode('/', $modFilePath); + $checkParts = explode('/', $checkFilePath); + + if(count($modParts) !== count($checkParts)) { + $return = false; + } else { + + $toCheck = array_diff_assoc($modParts, $checkParts); + + foreach($toCheck as $k => $part) { + if($part === '*') { + continue; + } elseif(strpos($part, '*') !== false) { + $part = preg_replace_callback('~([^*]+)~', 'VQMod::_quotePath', $part); + $part = str_replace('*', '[^/]*', $part); + $part = (bool) preg_match('~^' . $part . '$~', $checkParts[$k]); + + if($part) { + continue; + } + } elseif($part === $checkParts[$k]) { + continue; + } + + $return = false; + break; + } + + } + } else { + $return = false; + } + + return $return; + } + + /** + * VQMod::_quotePath() + * + * @param string $matches callback matches + * @return string + * @description apply's preg_quote to string from callback + */ + private static function _quotePath($matches) { + return preg_quote($matches[1], '~'); + } } /** @@ -506,107 +524,107 @@ private static function _quotePath($matches) { * @description Object to log information to a file */ class VQModLog { - private $_sep; - private $_defhash = 'da39a3ee5e6b4b0d3255bfef95601890afd80709'; - private $_logs = array(); - - /** - * VQModLog::__construct() - * - * @return null - * @description Object instantiation method - */ - public function __construct() { - $this->_sep = str_repeat('-', 70); - } - - /** - * VQModLog::__destruct() - * - * @return null - * @description Logs any messages to the log file just before object is destroyed - */ - public function __destruct() { - if(empty($this->_logs) || VQMod::$logging == false) { - return; - } - - $logPath = VQMod::path(VQMod::$logFolder . date('w_D') . '.log', true); - - $txt = array(); - - $txt[] = str_repeat('-', 10) . ' Date: ' . date('Y-m-d H:i:s') . ' ~ IP : ' . (isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : 'N/A') . ' ' . str_repeat('-', 10); - $txt[] = 'REQUEST URI : ' . $_SERVER['REQUEST_URI']; - - foreach($this->_logs as $count => $log) { - if($log['obj']) { - $vars = get_object_vars($log['obj']); - $txt[] = 'MOD DETAILS:'; - foreach($vars as $k => $v) { - if(is_string($v)) { - $txt[] = ' ' . str_pad($k, 10, ' ', STR_PAD_RIGHT) . ': ' . $v; - } - } - - } - - foreach($log['log'] as $msg) { - $txt[] = $msg; - } - - if ($count > count($this->_logs)-1) { - $txt[] = ''; - } - } - - $txt[] = $this->_sep; - $txt[] = str_repeat(PHP_EOL, 2); - $append = true; - - if(!file_exists($logPath)) { - $append = false; - } else { - $content = file_get_contents($logPath); - if(!empty($content) && strpos($content, ' Date: ' . date('Y-m-d ')) === false) { - $append = false; - } - } - - $result = file_put_contents($logPath, implode(PHP_EOL, $txt), ($append ? FILE_APPEND | LOCK_EX : LOCK_EX)); - if(!$result) { - die('VQModLog::__destruct - LOG FILE "' . $logPath . '" COULD NOT BE WRITTEN'); - } - } - - /** - * VQModLog::write() - * - * @param string $data Text to be added to log file - * @param VQModObject $obj Modification the error belongs to - * @return null - * @description Adds error to log object ready to be output - */ - public function write($data, VQModObject $obj = NULL) { - if($obj) { - $hash = sha1($obj->id); - } else { - $hash = $this->_defhash; - } - - if(empty($this->_logs[$hash])) { - $this->_logs[$hash] = array( - 'obj' => $obj, - 'log' => array() - ); - } - - if(VQMod::$fileModding) { - $this->_logs[$hash]['log'][] = PHP_EOL . 'File Name : ' . VQMod::$fileModding; - } - - $this->_logs[$hash]['log'][] = $data; - - } + private $_sep; + private $_defhash = 'da39a3ee5e6b4b0d3255bfef95601890afd80709'; + private $_logs = array(); + + /** + * VQModLog::__construct() + * + * @return null + * @description Object instantiation method + */ + public function __construct() { + $this->_sep = str_repeat('-', 70); + } + + /** + * VQModLog::__destruct() + * + * @return null + * @description Logs any messages to the log file just before object is destroyed + */ + public function __destruct() { + if(empty($this->_logs) || VQMod::$logging == false) { + return; + } + + $logPath = VQMod::path(VQMod::$logFolder . date('w_D') . '.log', true); + + $txt = array(); + + $txt[] = str_repeat('-', 10) . ' Date: ' . date('Y-m-d H:i:s') . ' ~ IP : ' . (isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : 'N/A') . ' ' . str_repeat('-', 10); + $txt[] = 'REQUEST URI : ' . $_SERVER['REQUEST_URI']; + + foreach($this->_logs as $count => $log) { + if($log['obj']) { + $vars = get_object_vars($log['obj']); + $txt[] = 'MOD DETAILS:'; + foreach($vars as $k => $v) { + if(is_string($v)) { + $txt[] = ' ' . str_pad($k, 10, ' ', STR_PAD_RIGHT) . ': ' . $v; + } + } + + } + + foreach($log['log'] as $msg) { + $txt[] = $msg; + } + + if ($count > count($this->_logs)-1) { + $txt[] = ''; + } + } + + $txt[] = $this->_sep; + $txt[] = str_repeat(PHP_EOL, 2); + $append = true; + + if(!file_exists($logPath)) { + $append = false; + } else { + $content = file_get_contents($logPath); + if(!empty($content) && strpos($content, ' Date: ' . date('Y-m-d ')) === false) { + $append = false; + } + } + + $result = file_put_contents($logPath, implode(PHP_EOL, $txt), ($append ? FILE_APPEND | LOCK_EX : LOCK_EX)); + if(!$result) { + die('VQModLog::__destruct - LOG FILE "' . $logPath . '" COULD NOT BE WRITTEN'); + } + } + + /** + * VQModLog::write() + * + * @param string $data Text to be added to log file + * @param VQModObject $obj Modification the error belongs to + * @return null + * @description Adds error to log object ready to be output + */ + public function write($data, VQModObject $obj = NULL) { + if($obj) { + $hash = sha1($obj->id); + } else { + $hash = $this->_defhash; + } + + if(empty($this->_logs[$hash])) { + $this->_logs[$hash] = array( + 'obj' => $obj, + 'log' => array() + ); + } + + if(VQMod::$fileModding) { + $this->_logs[$hash]['log'][] = PHP_EOL . 'File Name : ' . VQMod::$fileModding; + } + + $this->_logs[$hash]['log'][] = $data; + + } } /** @@ -614,316 +632,316 @@ public function write($data, VQModObject $obj = NULL) { * @description Object for the that orchestrates each applied modification */ class VQModObject { - public $modFile = ''; - public $id = ''; - public $version = ''; - public $vqmver = ''; - public $author = ''; - public $mods = array(); - - private $_skip = false; - - /** - * VQModObject::__construct() - * - * @param DOMNode $node node - * @param string $modFile File modification is from - * @return null - * @description Loads modification meta information - */ - public function __construct(DOMNode $node, $modFile) { - if($node->hasChildNodes()) { - foreach($node->childNodes as $child) { - $name = (string) $child->nodeName; - if(isset($this->$name)) { - $this->$name = (string) $child->nodeValue; - } - } - } - - $this->modFile = $modFile; - $this->_parseMods($node); - } - - /** - * VQModObject::skip() - * - * @return bool - * @description Returns the skip status of a modification - */ - public function skip() { - return $this->_skip; - } - - /** - * VQModObject::applyMod() - * - * @param array $mods Array of search add nodes - * @param string $data File contents to be altered - * @return null - * @description Applies all modifications to the text data - */ - public function applyMod($mods, &$data) { - if($this->_skip) return; - $tmp = $data; - - foreach($mods as $mod) { - VQMod::$fileModding = $mod['fileToMod'] . '(' . $mod['opIndex'] . ')'; - if(!empty($mod['ignoreif'])) { - if($mod['ignoreif']->regex == 'true') { - if (preg_match($mod['ignoreif']->getContent(), $tmp)) { - continue; - } - } else { - if (strpos($tmp, $mod['ignoreif']->getContent()) !== false) { - continue; - } - } - } - - $indexCount = 0; - - $tmp = $this->_explodeData($tmp); - $lineMax = count($tmp) - 1; - - // tag attributes - Override attributes if set - foreach(array_keys((array)$mod['search']) as $key) { - if ($key == "\x0VQNode\x0_content") { continue; } - if ($key == "trim") { continue; } - if (isset($mod['add']->$key) && $mod['add']->$key) { - $mod['search']->$key = $mod['add']->$key; - } - } - - switch($mod['search']->position) { - case 'top': - $tmp[$mod['search']->offset] = $mod['add']->getContent() . $tmp[$mod['search']->offset]; - break; - - case 'bottom': - $offset = $lineMax - $mod['search']->offset; - if($offset < 0){ - $tmp[-1] = $mod['add']->getContent(); - } else { - $tmp[$offset] .= $mod['add']->getContent(); - } - break; - - default: - - $changed = false; - foreach($tmp as $lineNum => $line) { - if(strlen($mod['search']->getContent()) == 0) { - if($mod['error'] == 'log' || $mod['error'] == 'abort') { - VQMod::$log->write('VQModObject::applyMod - EMPTY SEARCH CONTENT ERROR', $this); - } - break; - } - - if($mod['search']->regex == 'true') { - $pos = @preg_match($mod['search']->getContent(), $line); - if($pos === false) { - if($mod['error'] == 'log' || $mod['error'] == 'abort' ) { - VQMod::$log->write('VQModObject::applyMod - INVALID REGEX ERROR - ' . $mod['search']->getContent(), $this); - } - break 2; - } elseif($pos == 0) { - $pos = false; - } - } else { - $pos = strpos($line, $mod['search']->getContent()); - } - - if($pos !== false) { - $indexCount++; - $changed = true; - - if(!$mod['search']->indexes() || ($mod['search']->indexes() && in_array($indexCount, $mod['search']->indexes()))) { - - switch($mod['search']->position) { - case 'before': - $offset = ($lineNum - $mod['search']->offset < 0) ? -1 : $lineNum - $mod['search']->offset; - $tmp[$offset] = empty($tmp[$offset]) ? $mod['add']->getContent() : $mod['add']->getContent() . "\n" . $tmp[$offset]; - break; - - case 'after': - $offset = ($lineNum + $mod['search']->offset > $lineMax) ? $lineMax : $lineNum + $mod['search']->offset; - $tmp[$offset] = $tmp[$offset] . "\n" . $mod['add']->getContent(); - break; - - case 'ibefore': - $tmp[$lineNum] = str_replace($mod['search']->getContent(), $mod['add']->getContent() . $mod['search']->getContent(), $line); - break; - - case 'iafter': - $tmp[$lineNum] = str_replace($mod['search']->getContent(), $mod['search']->getContent() . $mod['add']->getContent(), $line); - break; - - default: - if(!empty($mod['search']->offset)) { - if($mod['search']->offset > 0) { - for($i = 1; $i <= $mod['search']->offset; $i++) { - if(isset($tmp[$lineNum + $i])) { - $tmp[$lineNum + $i] = ''; - } - } - } elseif($mod['search']->offset < 0) { - for($i = -1; $i >= $mod['search']->offset; $i--) { - if(isset($tmp[$lineNum + $i])) { - $tmp[$lineNum + $i] = ''; - } - } - } - } - - if($mod['search']->regex == 'true') { - $tmp[$lineNum] = preg_replace($mod['search']->getContent(), $mod['add']->getContent(), $line); - } else { - $tmp[$lineNum] = str_replace($mod['search']->getContent(), $mod['add']->getContent(), $line); - } - break; - } - } - } - } - - if(!$changed) { - $skip = ($mod['error'] == 'skip' || $mod['error'] == 'log') ? ' (SKIPPED)' : ' (ABORTING MOD)'; - - if($mod['error'] == 'log' || $mod['error'] == 'abort') { - VQMod::$log->write('VQModObject::applyMod - SEARCH NOT FOUND' . $skip . ': ' . $mod['search']->getContent(), $this); - } - - if($mod['error'] == 'abort') { - $this->_skip = true; - return; - } - - } - - break; - } - ksort($tmp); - $tmp = $this->_implodeData($tmp); - } - - VQMod::$fileModding = false; - - $data = $tmp; - } - - /** - * VQModObject::_parseMods() - * - * @param DOMNode $node node to be parsed - * @return null - * @description Parses modifications in preparation for the applyMod method to work - */ - private function _parseMods(DOMNode $node){ - $files = $node->getElementsByTagName('file'); - - $replaces = VQMod::$replaces; - - foreach($files as $file) { - $path = $file->getAttribute('path') ? $file->getAttribute('path') : ''; - $filesToMod = explode(',', $file->getAttribute('name')); - - foreach($filesToMod as $filename) { - - $fileToMod = $path . $filename; - if(!empty($replaces)) { - foreach($replaces as $r) { - if(count($r) == 2) { - $fileToMod = preg_replace($r[0], $r[1], $fileToMod); - } - } - } - - $error = ($file->hasAttribute('error')) ? $file->getAttribute('error') : 'log'; - $fullPath = VQMod::path($fileToMod); - - if(!$fullPath || !file_exists($fullPath)){ - if(strpos($fileToMod, '*') !== false) { - $fullPath = VQMod::getCwd() . $fileToMod; - } else { - if ($error == 'log' || $error == 'abort') { - $skip = ($error == 'log') ? ' (SKIPPED)' : ' (ABORTING MOD)'; - VQMod::$log->write('VQModObject::parseMods - Could not resolve path for [' . $fileToMod . ']' . $skip, $this); - } - - if ($error == 'log' || $error == 'skip') { - continue; - } elseif ($error == 'abort') { - return false; - } - } - } - - $operations = $file->getElementsByTagName('operation'); - - foreach($operations as $opIndex => $operation) { - VQMod::$fileModding = $fileToMod . '(' . $opIndex . ')'; - $skipOperation = false; - - $error = ($operation->hasAttribute('error')) ? $operation->getAttribute('error') : 'abort'; - $ignoreif = $operation->getElementsByTagName('ignoreif')->item(0); - - if($ignoreif) { - $ignoreif = new VQSearchNode($ignoreif); - } else { - $ignoreif = false; - } - - $search = $operation->getElementsByTagName('search')->item(0); - $add = $operation->getElementsByTagName('add')->item(0); - - if(!$search) { - VQMod::$log->write('Operation tag missing', $this); - $skipOperation = true; - } - - if(!$add) { - VQMod::$log->write('Operation tag missing', $this); - $skipOperation = true; - } - - if(!$skipOperation) { - $this->mods[$fullPath][] = array( - 'search' => new VQSearchNode($search), - 'add' => new VQAddNode($add), - 'ignoreif' => $ignoreif, - 'error' => $error, - 'fileToMod' => $fileToMod, - 'opIndex' => $opIndex, - ); - } - } - VQMod::$fileModding = false; - } - } - } - - /** - * VQModObject::_explodeData() - * - * @param string $data file contents - * @return string - * @description Splits a file into an array of individual lines - */ - private function _explodeData($data) { - return explode("\n", $data); - } - - /** - * VQModObject::_implodeData() - * - * @param array $data Array of lines - * @return string - * @description Joins an array of lines back into a text file - */ - private function _implodeData($data) { - return implode("\n", $data); - } + public $modFile = ''; + public $id = ''; + public $version = ''; + public $vqmver = ''; + public $author = ''; + public $mods = array(); + + private $_skip = false; + + /** + * VQModObject::__construct() + * + * @param DOMNode $node node + * @param string $modFile File modification is from + * @return null + * @description Loads modification meta information + */ + public function __construct(DOMNode $node, $modFile) { + if($node->hasChildNodes()) { + foreach($node->childNodes as $child) { + $name = (string) $child->nodeName; + if(isset($this->$name)) { + $this->$name = (string) $child->nodeValue; + } + } + } + + $this->modFile = $modFile; + $this->_parseMods($node); + } + + /** + * VQModObject::skip() + * + * @return bool + * @description Returns the skip status of a modification + */ + public function skip() { + return $this->_skip; + } + + /** + * VQModObject::applyMod() + * + * @param array $mods Array of search add nodes + * @param string $data File contents to be altered + * @return null + * @description Applies all modifications to the text data + */ + public function applyMod($mods, &$data) { + if($this->_skip) return; + $tmp = $data; + + foreach($mods as $mod) { + VQMod::$fileModding = $mod['fileToMod'] . '(' . $mod['opIndex'] . ')'; + if(!empty($mod['ignoreif'])) { + if($mod['ignoreif']->regex == 'true') { + if (preg_match($mod['ignoreif']->getContent(), $tmp)) { + continue; + } + } else { + if (strpos($tmp, $mod['ignoreif']->getContent()) !== false) { + continue; + } + } + } + + $indexCount = 0; + + $tmp = $this->_explodeData($tmp); + $lineMax = count($tmp) - 1; + + // tag attributes - Override attributes if set + foreach(array_keys((array)$mod['search']) as $key) { + if ($key == "\x0VQNode\x0_content") { continue; } + if ($key == "trim") { continue; } + if (isset($mod['add']->$key) && $mod['add']->$key) { + $mod['search']->$key = $mod['add']->$key; + } + } + + switch($mod['search']->position) { + case 'top': + $tmp[$mod['search']->offset] = $mod['add']->getContent() . $tmp[$mod['search']->offset]; + break; + + case 'bottom': + $offset = $lineMax - $mod['search']->offset; + if($offset < 0){ + $tmp[-1] = $mod['add']->getContent(); + } else { + $tmp[$offset] .= $mod['add']->getContent(); + } + break; + + default: + + $changed = false; + foreach($tmp as $lineNum => $line) { + if(strlen($mod['search']->getContent()) == 0) { + if($mod['error'] == 'log' || $mod['error'] == 'abort') { + VQMod::$log->write('VQModObject::applyMod - EMPTY SEARCH CONTENT ERROR', $this); + } + break; + } + + if($mod['search']->regex == 'true') { + $pos = @preg_match($mod['search']->getContent(), $line); + if($pos === false) { + if($mod['error'] == 'log' || $mod['error'] == 'abort' ) { + VQMod::$log->write('VQModObject::applyMod - INVALID REGEX ERROR - ' . $mod['search']->getContent(), $this); + } + break 2; + } elseif($pos == 0) { + $pos = false; + } + } else { + $pos = strpos($line, $mod['search']->getContent()); + } + + if($pos !== false) { + $indexCount++; + $changed = true; + + if(!$mod['search']->indexes() || ($mod['search']->indexes() && in_array($indexCount, $mod['search']->indexes()))) { + + switch($mod['search']->position) { + case 'before': + $offset = ($lineNum - $mod['search']->offset < 0) ? -1 : $lineNum - $mod['search']->offset; + $tmp[$offset] = empty($tmp[$offset]) ? $mod['add']->getContent() : $mod['add']->getContent() . "\n" . $tmp[$offset]; + break; + + case 'after': + $offset = ($lineNum + $mod['search']->offset > $lineMax) ? $lineMax : $lineNum + $mod['search']->offset; + $tmp[$offset] = $tmp[$offset] . "\n" . $mod['add']->getContent(); + break; + + case 'ibefore': + $tmp[$lineNum] = str_replace($mod['search']->getContent(), $mod['add']->getContent() . $mod['search']->getContent(), $line); + break; + + case 'iafter': + $tmp[$lineNum] = str_replace($mod['search']->getContent(), $mod['search']->getContent() . $mod['add']->getContent(), $line); + break; + + default: + if(!empty($mod['search']->offset)) { + if($mod['search']->offset > 0) { + for($i = 1; $i <= $mod['search']->offset; $i++) { + if(isset($tmp[$lineNum + $i])) { + $tmp[$lineNum + $i] = ''; + } + } + } elseif($mod['search']->offset < 0) { + for($i = -1; $i >= $mod['search']->offset; $i--) { + if(isset($tmp[$lineNum + $i])) { + $tmp[$lineNum + $i] = ''; + } + } + } + } + + if($mod['search']->regex == 'true') { + $tmp[$lineNum] = preg_replace($mod['search']->getContent(), $mod['add']->getContent(), $line); + } else { + $tmp[$lineNum] = str_replace($mod['search']->getContent(), $mod['add']->getContent(), $line); + } + break; + } + } + } + } + + if(!$changed) { + $skip = ($mod['error'] == 'skip' || $mod['error'] == 'log') ? ' (SKIPPED)' : ' (ABORTING MOD)'; + + if($mod['error'] == 'log' || $mod['error'] == 'abort') { + VQMod::$log->write('VQModObject::applyMod - SEARCH NOT FOUND' . $skip . ': ' . $mod['search']->getContent(), $this); + } + + if($mod['error'] == 'abort') { + $this->_skip = true; + return; + } + + } + + break; + } + ksort($tmp); + $tmp = $this->_implodeData($tmp); + } + + VQMod::$fileModding = false; + + $data = $tmp; + } + + /** + * VQModObject::_parseMods() + * + * @param DOMNode $node node to be parsed + * @return null + * @description Parses modifications in preparation for the applyMod method to work + */ + private function _parseMods(DOMNode $node){ + $files = $node->getElementsByTagName('file'); + + $replaces = VQMod::$replaces; + + foreach($files as $file) { + $path = $file->getAttribute('path') ? $file->getAttribute('path') : ''; + $filesToMod = explode(',', $file->getAttribute('name')); + + foreach($filesToMod as $filename) { + + $fileToMod = $path . $filename; + if(!empty($replaces)) { + foreach($replaces as $r) { + if(count($r) == 2) { + $fileToMod = preg_replace($r[0], $r[1], $fileToMod); + } + } + } + + $error = ($file->hasAttribute('error')) ? $file->getAttribute('error') : 'log'; + $fullPath = VQMod::path($fileToMod); + + if(!$fullPath || !file_exists($fullPath)){ + if(strpos($fileToMod, '*') !== false) { + $fullPath = VQMod::getCwd() . $fileToMod; + } else { + if ($error == 'log' || $error == 'abort') { + $skip = ($error == 'log') ? ' (SKIPPED)' : ' (ABORTING MOD)'; + VQMod::$log->write('VQModObject::parseMods - Could not resolve path for [' . $fileToMod . ']' . $skip, $this); + } + + if ($error == 'log' || $error == 'skip') { + continue; + } elseif ($error == 'abort') { + return false; + } + } + } + + $operations = $file->getElementsByTagName('operation'); + + foreach($operations as $opIndex => $operation) { + VQMod::$fileModding = $fileToMod . '(' . $opIndex . ')'; + $skipOperation = false; + + $error = ($operation->hasAttribute('error')) ? $operation->getAttribute('error') : 'abort'; + $ignoreif = $operation->getElementsByTagName('ignoreif')->item(0); + + if($ignoreif) { + $ignoreif = new VQSearchNode($ignoreif); + } else { + $ignoreif = false; + } + + $search = $operation->getElementsByTagName('search')->item(0); + $add = $operation->getElementsByTagName('add')->item(0); + + if(!$search) { + VQMod::$log->write('Operation tag missing', $this); + $skipOperation = true; + } + + if(!$add) { + VQMod::$log->write('Operation tag missing', $this); + $skipOperation = true; + } + + if(!$skipOperation) { + $this->mods[$fullPath][] = array( + 'search' => new VQSearchNode($search), + 'add' => new VQAddNode($add), + 'ignoreif' => $ignoreif, + 'error' => $error, + 'fileToMod' => $fileToMod, + 'opIndex' => $opIndex, + ); + } + } + VQMod::$fileModding = false; + } + } + } + + /** + * VQModObject::_explodeData() + * + * @param string $data file contents + * @return string + * @description Splits a file into an array of individual lines + */ + private function _explodeData($data) { + return explode("\n", $data); + } + + /** + * VQModObject::_implodeData() + * + * @param array $data Array of lines + * @return string + * @description Joins an array of lines back into a text file + */ + private function _implodeData($data) { + return implode("\n", $data); + } } /** @@ -931,40 +949,40 @@ private function _implodeData($data) { * @description Basic node object blueprint */ class VQNode { - public $regex = 'false'; - public $trim = 'false'; - private $_content = ''; - - /** - * VQNode::__construct() - * - * @param DOMNode $node Search/add node - * @return null - * @description Parses the node attributes and sets the node property - */ - public function __construct(DOMNode $node) { - $this->_content = $node->nodeValue; - - if($node->hasAttributes()) { - foreach($node->attributes as $attr) { - $name = $attr->nodeName; - if(isset($this->$name)) { - $this->$name = $attr->nodeValue; - } - } - } - } - - /** - * VQNode::getContent() - * - * @return string - * @description Returns the content, trimmed if applicable - */ - public function getContent() { - $content = ($this->trim == 'true') ? trim($this->_content) : $this->_content; - return $content; - } + public $regex = 'false'; + public $trim = 'false'; + private $_content = ''; + + /** + * VQNode::__construct() + * + * @param DOMNode $node Search/add node + * @return null + * @description Parses the node attributes and sets the node property + */ + public function __construct(DOMNode $node) { + $this->_content = $node->nodeValue; + + if($node->hasAttributes()) { + foreach($node->attributes as $attr) { + $name = $attr->nodeName; + if(isset($this->$name)) { + $this->$name = $attr->nodeValue; + } + } + } + } + + /** + * VQNode::getContent() + * + * @return string + * @description Returns the content, trimmed if applicable + */ + public function getContent() { + $content = ($this->trim == 'true') ? trim($this->_content) : $this->_content; + return $content; + } } /** @@ -972,31 +990,31 @@ public function getContent() { * @description Object for the xml tags */ class VQSearchNode extends VQNode { - public $position = 'replace'; - public $offset = 0; - public $index = 'false'; - public $regex = 'false'; - public $trim = 'true'; - - /** - * VQSearchNode::indexes() - * - * @return bool, array - * @description Returns the index values to use the search on, or false if none - */ - public function indexes() { - if($this->index == 'false') { - return false; - } - $tmp = explode(',', $this->index); - foreach($tmp as $k => $v) { - if(!is_int($v)) { - unset($k); - } - } - $tmp = array_unique($tmp); - return empty($tmp) ? false : $tmp; - } + public $position = 'replace'; + public $offset = 0; + public $index = 'false'; + public $regex = 'false'; + public $trim = 'true'; + + /** + * VQSearchNode::indexes() + * + * @return bool, array + * @description Returns the index values to use the search on, or false if none + */ + public function indexes() { + if($this->index == 'false') { + return false; + } + $tmp = explode(',', $this->index); + foreach($tmp as $k => $v) { + if(!is_int($v)) { + unset($k); + } + } + $tmp = array_unique($tmp); + return empty($tmp) ? false : $tmp; + } } /** @@ -1004,9 +1022,9 @@ public function indexes() { * @description Object for the xml tags */ class VQAddNode extends VQNode { - public $position = false; - public $offset = false; - public $index = false; - public $regex = false; - public $trim = 'false'; + public $position = false; + public $offset = false; + public $index = false; + public $regex = false; + public $trim = 'false'; }