shop.balmet.com

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

vqmod.php (27522B)


      1 <?php
      2 /**
      3  * VQMod
      4  * @description Main Object used
      5  */
      6 abstract class VQMod {
      7 	public static $_vqversion = '2.6.4';						// Current version number
      8 
      9 	private static $_modFileList = array();						// Array of xml files
     10 	private static $_mods = array();							// Array of modifications to apply
     11 	private static $_filesModded = array();						// Array of already modified files
     12 	private static $_doNotMod = array();						// Array of files not to apply modifications to
     13 	private static $_cwd = '';									// Current working directory path
     14 	private static $_folderChecks = false;						// Flag for already checked log/cache folders exist
     15 	private static $_cachePathFull = '';						// Full cache folder path
     16 	private static $_lastModifiedTime = 0;						// Integer representing the last time anything was modified
     17 	private static $_devMode = false;							// Flag for developer mode - disables caching while true
     18 
     19 	public static $logFolder = 'vqmod/logs/';					// Path log folders are stored in
     20 	public static $vqCachePath = 'vqmod/vqcache/';				// Relative path to cache file directory
     21 	public static $modCache = 'vqmod/mods.cache';				// Relative path to serialized mods array cache file
     22 	public static $checkedCache = 'vqmod/checked.cache';		// Relative path to already checked files array cache file
     23 	public static $protectedFilelist = 'vqmod/vqprotect.txt';	// Relative path to protected files array cache file
     24 	public static $pathReplaces = 'vqmod/pathReplaces.php';		// Relative path to dynamic path replacement file
     25 	public static $logging = true;								// Flag to enabled/disable logging
     26 	public static $log;											// Log object reference
     27 	public static $fileModding = false;							// Reference to the current file being modified by vQmod for logging
     28 	public static $directorySeparator = '';						// System directory separator (/ or \ depending on OS)
     29 	public static $replaces = array();							// Array of regex replaces to perform on file paths
     30 	public static $windows = false;								// Flag determining if windows or *nix based
     31 
     32 	/**
     33 	 * VQMod::bootup()
     34 	 *
     35 	 * @param bool $path File path to use
     36 	 * @param bool $logging Enable/disabled logging
     37 	 * @return null
     38 	 * @description Startup of VQMod
     39 	 */
     40 	public static function bootup($path = false, $logging = true) {
     41 		if(!class_exists('DOMDocument')) {
     42 			die('VQMod::bootup - ERROR - YOU NEED THE PHP "DOMDocument" EXTENSION INSTALLED TO USE VQMod');
     43 		}
     44 
     45 		if(strtoupper(substr(PHP_OS, 0, 3)) == 'WIN') {
     46 			self::$windows = true;
     47 		}
     48 
     49 		self::$directorySeparator = defined('DIRECTORY_SEPARATOR') ? DIRECTORY_SEPARATOR : '/';
     50 
     51 		if(!$path){
     52 			$path = dirname(dirname(__FILE__));
     53 		}
     54 		self::_setCwd($path);
     55 
     56 		self::$logging = (bool) $logging;
     57 		self::$log = new VQModLog();
     58 
     59 		$replacesPath = self::path(self::$pathReplaces);
     60 		$replaces = array();
     61 		if($replacesPath) {
     62 			include_once($replacesPath);
     63 			self::$_lastModifiedTime = filemtime($replacesPath);
     64 		}
     65 
     66 		self::$replaces = !is_array($replaces) ? array() : $replaces;
     67 		self::_getMods();
     68 		self::_loadProtected();
     69 		self::_loadChecked();
     70 	}
     71 
     72 	/**
     73 	 * VQMod::modCheck()
     74 	 *
     75 	 * @param string $sourceFile path for file to be modified
     76 	 * @param string $modificationFile path for mods to be applied to file
     77 	 * @return string
     78 	 * @description Checks if a file has modifications and applies them, returning cache files or the file name
     79 	 */
     80 	public static function modCheck($sourceFile, $modificationFile = false) {
     81 
     82 		if(!self::$_folderChecks) {
     83 
     84 			if(self::$logging) {
     85 				// Create log folder if it doesn't exist
     86 				$log_folder = self::path(self::$logFolder, true);
     87 				self::dirCheck($log_folder);
     88 			}
     89 
     90 			// Create cache folder if it doesn't exist
     91 			$cache_folder = self::path(self::$vqCachePath, true);
     92 			self::dirCheck($cache_folder);
     93 
     94 			// Store cache folder path to save on repeat checks for path validity
     95 			self::$_cachePathFull = self::path(self::$vqCachePath);
     96 
     97 			self::$_folderChecks = true;
     98 		}
     99 
    100 		if(!preg_match('%^([a-z]:)?[\\\\/]%i', $sourceFile)) {
    101 			$sourcePath = self::path($sourceFile);
    102 		} else {
    103 			$sourcePath = self::_realpath($sourceFile);
    104 		}
    105 
    106 		if($modificationFile !== false) {
    107 			if(!preg_match('%^([a-z]:)?[\\\\/]%i', $modificationFile)) {
    108 				$modificationsPath = self::path($modificationFile);
    109 			} else {
    110 				$modificationsPath = self::_realpath($modificationFile);
    111 			}
    112 		} else {
    113 			$modificationsPath = $sourcePath;
    114 		}
    115 
    116 		if(!$sourcePath || is_dir($sourcePath) || in_array($sourcePath, self::$_doNotMod)) {
    117 			return $sourceFile;
    118 		}
    119 
    120 		$stripped_filename = preg_replace('~^' . preg_quote(self::getCwd(), '~') . '~i', '', $sourcePath);
    121 		$cacheFile = self::_cacheName($stripped_filename);
    122 		$file_last_modified = filemtime($sourcePath);
    123 
    124 		if(file_exists($cacheFile) && filemtime($cacheFile) >= self::$_lastModifiedTime && filemtime($cacheFile) >= $file_last_modified) {
    125 			return $cacheFile;
    126 		}
    127 
    128 		if(isset(self::$_filesModded[$sourcePath])) {
    129 			return self::$_filesModded[$sourcePath]['cached'] ? $cacheFile : $sourceFile;
    130 		}
    131 
    132 		$changed = false;
    133 		$fileHash = sha1_file($sourcePath);
    134 		$fileData = file_get_contents($sourcePath);
    135 
    136 		foreach(self::$_mods as $modObject) {
    137 			foreach($modObject->mods as $path => $mods) {
    138 				if(self::_checkMatch($path, $modificationsPath)) {
    139 					$modObject->applyMod($mods, $fileData);
    140 				}
    141 			}
    142 		}
    143 
    144 		if (sha1($fileData) != $fileHash) {
    145 			$writePath = $cacheFile;
    146 			if(!file_exists($writePath) || is_writable($writePath)) {
    147 				file_put_contents($writePath, $fileData, LOCK_EX);
    148 				$changed = true;
    149 			}
    150 		} else {
    151 			file_put_contents(self::path(self::$checkedCache, true), $stripped_filename . PHP_EOL, FILE_APPEND | LOCK_EX);
    152 			self::$_doNotMod[] = $sourcePath;
    153 		}
    154 
    155 		self::$_filesModded[$sourcePath] = array('cached' => $changed);
    156 		return $changed ? $writePath : $sourcePath;
    157 	}
    158 
    159 	/**
    160 	 * VQMod::path()
    161 	 *
    162 	 * @param string $path File path
    163 	 * @param bool $skip_real If true path is full not relative
    164 	 * @return bool, string
    165 	 * @description Returns the full true path of a file if it exists, otherwise false
    166 	 */
    167 	public static function path($path, $skip_real = false) {
    168 		$tmp = self::$_cwd . $path;
    169 		$realpath = $skip_real ? $tmp : self::_realpath($tmp);
    170 		if(!$realpath) {
    171 			return false;
    172 		}
    173 		return $realpath;
    174 	}
    175 
    176 	/**
    177 	 * VQMod::getCwd()
    178 	 *
    179 	 * @return string
    180 	 * @description Returns current working directory
    181 	 */
    182 	public static function getCwd() {
    183 		return self::$_cwd;
    184 	}
    185 
    186 	/**
    187 	 * VQMod::dirCheck()
    188 	 *
    189 	 * @param string $path
    190 	 * @return null
    191 	 * @description Creates $path folder if it doesn't exist
    192 	 */
    193 	public static function dirCheck($path) {
    194 		if(!is_dir($path)) {
    195 			if(!mkdir($path)) {
    196 				die('VQMod::dirCheck - CANNOT CREATE "' . $path . '" DIRECTORY');
    197 			}
    198 		}
    199 	}
    200 
    201 	/**
    202 	 * VQMod::handleXMLError()
    203 	 *
    204 	 * @description Error handler for bad XML files
    205 	 */
    206 	public static function handleXMLError($errno, $errstr, $errfile, $errline) {
    207 		if ($errno == E_WARNING && (substr_count($errstr, 'DOMDocument::load()') > 0)) {
    208 			throw new DOMException(str_replace('DOMDocument::load()', '', $errstr));
    209 		} else {
    210 			return false;
    211 		}
    212 	}
    213 
    214 	/**
    215 	 * VQMod::_getMods()
    216 	 *
    217 	 * @return null
    218 	 * @description Gets list of XML files in vqmod xml folder for processing
    219 	 */
    220 	private static function _getMods() {
    221 
    222 		self::$_modFileList = glob(self::path('vqmod/xml/', true) . '*.xml');
    223 
    224 		foreach(self::$_modFileList as $file) {
    225 			if(file_exists($file)) {
    226 				$lastMod = filemtime($file);
    227 				if($lastMod > self::$_lastModifiedTime){
    228 					self::$_lastModifiedTime = $lastMod;
    229 				}
    230 			}
    231 		}
    232 
    233 		$xml_folder_time = filemtime(self::path('vqmod/xml'));
    234 		if($xml_folder_time > self::$_lastModifiedTime){
    235 			self::$_lastModifiedTime = $xml_folder_time;
    236 		}
    237 
    238 		$modCache = self::path(self::$modCache);
    239 		if(self::$_devMode || !file_exists($modCache)) {
    240 			self::$_lastModifiedTime = time();
    241 		} elseif(file_exists($modCache) && filemtime($modCache) >= self::$_lastModifiedTime) {
    242 			$mods = file_get_contents($modCache);
    243 			if(!empty($mods))
    244 			self::$_mods = unserialize($mods);
    245 			if(self::$_mods !== false) {
    246 				return;
    247 			}
    248 		}
    249 
    250 		// Clear checked cache if rebuilding
    251 		file_put_contents(self::path(self::$checkedCache, true), '', LOCK_EX);
    252 
    253 		if(self::$_modFileList) {
    254 			self::_parseMods();
    255 		} else {
    256 			self::$log->write('VQMod::_getMods - NO XML FILES READABLE IN XML FOLDER');
    257 		}
    258 	}
    259 
    260 	/**
    261 	 * VQMod::_parseMods()
    262 	 *
    263 	 * @return null
    264 	 * @description Loops through xml files and attempts to load them as VQModObject's
    265 	 */
    266 	private static function _parseMods() {
    267 
    268 		set_error_handler(array('VQMod', 'handleXMLError'));
    269 
    270 		$dom = new DOMDocument('1.0', 'UTF-8');
    271 		foreach(self::$_modFileList as $modFileKey => $modFile) {
    272 			if(file_exists($modFile)) {
    273 				try {
    274 					$dom->load($modFile);
    275 					$mod = $dom->getElementsByTagName('modification')->item(0);
    276 					$vqmver = $mod->getElementsByTagName('vqmver')->item(0);
    277 
    278 					if($vqmver) {
    279 						$version_check = $vqmver->getAttribute('required');
    280 						if(strtolower($version_check) == 'true') {
    281 							if(version_compare(self::$_vqversion, $vqmver->nodeValue, '<')) {
    282 								self::$log->write('VQMod::_parseMods - FILE "' . $modFile . '" REQUIRES VQMOD "' . $vqmver->nodeValue . '" OR ABOVE AND HAS BEEN SKIPPED');
    283 								continue;
    284 							}
    285 						}
    286 					}
    287 
    288 					self::$_mods[] = new VQModObject($mod, $modFile);
    289 				} catch (Exception $e) {
    290 					self::$log->write('VQMod::_parseMods - INVALID XML FILE: ' . $e->getMessage());
    291 				}
    292 			} else {
    293 				self::$log->write('VQMod::_parseMods - FILE NOT FOUND: ' . $modFile);
    294 			}
    295 		}
    296 
    297 		restore_error_handler();
    298 
    299 		$modCache = self::path(self::$modCache, true);
    300 		$result = file_put_contents($modCache, serialize(self::$_mods), LOCK_EX);
    301 		if(!$result) {
    302 			die('VQMod::_parseMods - "/vqmod/mods.cache" FILE NOT WRITEABLE');
    303 		}
    304 	}
    305 
    306 	/**
    307 	 * VQMod::_loadProtected()
    308 	 *
    309 	 * @return null
    310 	 * @description Loads protected list and adds them to _doNotMod array
    311 	 */
    312 	private static function _loadProtected() {
    313 		$file = self::path(self::$protectedFilelist);
    314 		if($file && is_file($file)) {
    315 			$paths = file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
    316 			if(!empty($paths)) {
    317 				foreach($paths as $path) {
    318 					$fullPath = self::path($path);
    319 					if($fullPath && !in_array($fullPath, self::$_doNotMod)) {
    320 						self::$_doNotMod[] = $fullPath;
    321 					}
    322 				}
    323 			}
    324 		}
    325 	}
    326 
    327 	/**
    328 	 * VQMod::_loadChecked()
    329 	 *
    330 	 * @return null
    331 	 * @description Loads already checked files and adds them to _doNotMod array
    332 	 */
    333 	private static function _loadChecked() {
    334 		$file = self::path(self::$checkedCache);
    335 		if($file && is_file($file)) {
    336 			$paths = file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
    337 			if(!empty($paths)) {
    338 				foreach($paths as $path) {
    339 					$fullPath = self::path($path, true);
    340 					if($fullPath) {
    341 						self::$_doNotMod[] = $fullPath;
    342 					}
    343 				}
    344 			}
    345 		}
    346 	}
    347 
    348 	/**
    349 	 * VQMod::_cacheName()
    350 	 *
    351 	 * @param string $file Filename to be converted to cache filename
    352 	 * @return string
    353 	 * @description Returns cache file name for a path
    354 	 */
    355 	private static function _cacheName($file) {
    356 		return self::$_cachePathFull . 'vq2-' . preg_replace('~[/\\\\]+~', '_', $file);
    357 	}
    358 
    359 	/**
    360 	 * VQMod::_setCwd()
    361 	 *
    362 	 * @param string $path Path to be used as current working directory
    363 	 * @return null
    364 	 * @description Sets the current working directory variable
    365 	 */
    366 	private static function _setCwd($path) {
    367 		self::$_cwd = self::_realpath($path);
    368 	}
    369 
    370 	/**
    371 	 * VQMod::_realpath()
    372 	 *
    373 	 * @param string $file
    374 	 * @return string
    375 	 * @description Returns real path of any path, adding directory slashes if necessary
    376 	 */
    377 	private static function _realpath($file) {
    378 		$path = realpath($file);
    379 		if(!$path) {
    380 			return false;
    381 		}
    382 
    383 		if(is_dir($path)) {
    384 			$path = rtrim($path, self::$directorySeparator) . self::$directorySeparator;
    385 		}
    386 
    387 		return $path;
    388 	}
    389 
    390 	/**
    391 	 * VQMod::_checkMatch()
    392 	 *
    393 	 * @param string $modFilePath Modification path from a <file> node
    394 	 * @param string $checkFilePath File path
    395 	 * @return bool
    396 	 * @description Checks a modification path against a file path
    397 	 */
    398 	private static function _checkMatch($modFilePath, $checkFilePath) {
    399 		$modFilePath = str_replace('\\', '/', $modFilePath);
    400 		$checkFilePath = str_replace('\\', '/', $checkFilePath);
    401 
    402 		if(self::$windows) {
    403 			$modFilePath = strtolower($modFilePath);
    404 			$checkFilePath = strtolower($checkFilePath);
    405 		}
    406 
    407 		if($modFilePath == $checkFilePath) {
    408 			$return = true;
    409 		} elseif(strpos($modFilePath, '*') !== false) {
    410 			$return = true;
    411 			$modParts = explode('/', $modFilePath);
    412 			$checkParts = explode('/', $checkFilePath);
    413 
    414 			if(count($modParts) !== count($checkParts)) {
    415 				 $return = false;
    416 			} else {
    417 
    418 				$toCheck = array_diff_assoc($modParts, $checkParts);
    419 
    420 				foreach($toCheck as $k => $part) {
    421 					if($part === '*') {
    422 						continue;
    423 					} elseif(strpos($part, '*') !== false) {
    424 						$part = preg_replace_callback('~([^*]+)~', array('self', '_quotePath'), $part);
    425 						$part = str_replace('*', '[^/]*', $part);
    426 						$part = (bool) preg_match('~^' . $part . '$~', $checkParts[$k]);
    427 
    428 						if($part) {
    429 							continue;
    430 						}
    431 					} elseif($part === $checkParts[$k]) {
    432 						continue;
    433 					}
    434 
    435 					$return = false;
    436 					break;
    437 				}
    438 
    439 			}
    440 		} else {
    441 			$return = false;
    442 		}
    443 
    444 		return $return;
    445 	}
    446 
    447 	/**
    448 	 * VQMod::_quotePath()
    449 	 *
    450 	 * @param string $matches callback matches
    451 	 * @return string
    452 	 * @description apply's preg_quote to string from callback
    453 	 */
    454 	private static function _quotePath($matches) {
    455 		return preg_quote($matches[1], '~');
    456 	}
    457 }
    458 
    459 /**
    460  * VQModLog
    461  * @description Object to log information to a file
    462  */
    463 class VQModLog {
    464 	private $_sep;
    465 	private $_defhash = 'da39a3ee5e6b4b0d3255bfef95601890afd80709';
    466 	private $_logs = array();
    467 
    468 	/**
    469 	 * VQModLog::__construct()
    470 	 *
    471 	 * @return null
    472 	 * @description Object instantiation method
    473 	 */
    474 	public function __construct() {
    475 		$this->_sep = str_repeat('-', 70);
    476 	}
    477 
    478 	/**
    479 	 * VQModLog::__destruct()
    480 	 *
    481 	 * @return null
    482 	 * @description Logs any messages to the log file just before object is destroyed
    483 	 */
    484 	public function __destruct() {
    485 		if(empty($this->_logs) || VQMod::$logging == false) {
    486 			return;
    487 		}
    488 
    489 		$logPath = VQMod::path(VQMod::$logFolder . date('w_D') . '.log', true);
    490 
    491 		$txt = array();
    492 
    493 		$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);
    494 		$txt[] = 'REQUEST URI : ' . $_SERVER['REQUEST_URI'];
    495 
    496 		foreach($this->_logs as $count => $log) {
    497 			if($log['obj']) {
    498 				$vars = get_object_vars($log['obj']);
    499 				$txt[] = 'MOD DETAILS:';
    500 				foreach($vars as $k => $v) {
    501 					if(is_string($v)) {
    502 						$txt[] = '   ' . str_pad($k, 10, ' ', STR_PAD_RIGHT) . ': ' . $v;
    503 					}
    504 				}
    505 
    506 			}
    507 
    508 			foreach($log['log'] as $msg) {
    509 				$txt[] = $msg;
    510 			}
    511 
    512 			if ($count > count($this->_logs)-1) {
    513 				$txt[] = '';
    514 			}
    515 		}
    516 
    517 		$txt[] = $this->_sep;
    518 		$txt[] = str_repeat(PHP_EOL, 2);
    519 		$append = true;
    520 
    521 		if(!file_exists($logPath)) {
    522 			$append = false;
    523 		} else {
    524 			$content = file_get_contents($logPath);
    525 			if(!empty($content) && strpos($content, ' Date: ' . date('Y-m-d ')) === false) {
    526 				$append = false;
    527 			}
    528 		}
    529 
    530 		$result = file_put_contents($logPath, implode(PHP_EOL, $txt), ($append ? FILE_APPEND | LOCK_EX : LOCK_EX));
    531 		if(!$result) {
    532 			die('VQModLog::__destruct - LOG FILE "' . $logPath . '" COULD NOT BE WRITTEN');
    533 		}
    534 	}
    535 
    536 	/**
    537 	 * VQModLog::write()
    538 	 *
    539 	 * @param string $data Text to be added to log file
    540 	 * @param VQModObject $obj Modification the error belongs to
    541 	 * @return null
    542 	 * @description Adds error to log object ready to be output
    543 	 */
    544 	public function write($data, VQModObject $obj = NULL) {
    545 		if($obj) {
    546 			$hash = sha1($obj->id);
    547 		} else {
    548 			$hash = $this->_defhash;
    549 		}
    550 
    551 		if(empty($this->_logs[$hash])) {
    552 			$this->_logs[$hash] = array(
    553 				'obj' => $obj,
    554 				'log' => array()
    555 			);
    556 		}
    557 
    558 		if(VQMod::$fileModding) {
    559 			$this->_logs[$hash]['log'][] = PHP_EOL . 'File Name    : ' . VQMod::$fileModding;
    560 		}
    561 
    562 		$this->_logs[$hash]['log'][] = $data;
    563 
    564 	}
    565 }
    566 
    567 /**
    568  * VQModObject
    569  * @description Object for the <modification> that orchestrates each applied modification
    570  */
    571 class VQModObject {
    572 	public $modFile = '';
    573 	public $id = '';
    574 	public $version = '';
    575 	public $vqmver = '';
    576 	public $author = '';
    577 	public $mods = array();
    578 
    579 	private $_skip = false;
    580 
    581 	/**
    582 	 * VQModObject::__construct()
    583 	 *
    584 	 * @param DOMNode $node <modification> node
    585 	 * @param string $modFile File modification is from
    586 	 * @return null
    587 	 * @description Loads modification meta information
    588 	 */
    589 	public function __construct(DOMNode $node, $modFile) {
    590 		if($node->hasChildNodes()) {
    591 			foreach($node->childNodes as $child) {
    592 				$name = (string) $child->nodeName;
    593 				if(isset($this->$name)) {
    594 					$this->$name = (string) $child->nodeValue;
    595 				}
    596 			}
    597 		}
    598 
    599 		$this->modFile = $modFile;
    600 		$this->_parseMods($node);
    601 	}
    602 
    603 	/**
    604 	 * VQModObject::skip()
    605 	 *
    606 	 * @return bool
    607 	 * @description Returns the skip status of a modification
    608 	 */
    609 	public function skip() {
    610 		return $this->_skip;
    611 	}
    612 
    613 	/**
    614 	 * VQModObject::applyMod()
    615 	 *
    616 	 * @param array $mods Array of search add nodes
    617 	 * @param string $data File contents to be altered
    618 	 * @return null
    619 	 * @description Applies all modifications to the text data
    620 	 */
    621 	public function applyMod($mods, &$data) {
    622 		if($this->_skip) return;
    623 		$tmp = $data;
    624 
    625 		foreach($mods as $mod) {
    626 			VQMod::$fileModding = $mod['fileToMod'] . '(' . $mod['opIndex'] . ')';
    627 			if(!empty($mod['ignoreif'])) {
    628 				if($mod['ignoreif']->regex == 'true') {
    629 					if (preg_match($mod['ignoreif']->getContent(), $tmp)) {
    630 						continue;
    631 					}
    632 				} else {
    633 					if (strpos($tmp, $mod['ignoreif']->getContent()) !== false) {
    634 						continue;
    635 					}
    636 				}
    637 			}
    638 
    639 			$indexCount = 0;
    640 
    641 			$tmp = $this->_explodeData($tmp);
    642 			$lineMax = count($tmp) - 1;
    643 
    644 			// <add> tag attributes - Override <search> attributes if set
    645 			foreach(array_keys((array)$mod['search']) as $key) {
    646 				if ($key == "\x0VQNode\x0_content") { continue; }
    647 				if ($key == "trim") { continue; }
    648 				if (isset($mod['add']->$key) && $mod['add']->$key) {
    649 					$mod['search']->$key = $mod['add']->$key;
    650 				}
    651 			}
    652 
    653 			switch($mod['search']->position) {
    654 				case 'top':
    655 				$tmp[$mod['search']->offset] =  $mod['add']->getContent() . $tmp[$mod['search']->offset];
    656 				break;
    657 
    658 				case 'bottom':
    659 				$offset = $lineMax - $mod['search']->offset;
    660 				if($offset < 0){
    661 					$tmp[-1] = $mod['add']->getContent();
    662 				} else {
    663 					$tmp[$offset] .= $mod['add']->getContent();
    664 				}
    665 				break;
    666 
    667 				default:
    668 
    669 				$changed = false;
    670 				foreach($tmp as $lineNum => $line) {
    671 					if(strlen($mod['search']->getContent()) == 0) {
    672 						if($mod['error'] == 'log' || $mod['error'] == 'abort') {
    673 							VQMod::$log->write('VQModObject::applyMod - EMPTY SEARCH CONTENT ERROR', $this);
    674 						}
    675 						break;
    676 					}
    677 
    678 					if($mod['search']->regex == 'true') {
    679 						$pos = @preg_match($mod['search']->getContent(), $line);
    680 						if($pos === false) {
    681 							if($mod['error'] == 'log' || $mod['error'] == 'abort' ) {
    682 								VQMod::$log->write('VQModObject::applyMod - INVALID REGEX ERROR - ' . $mod['search']->getContent(), $this);
    683 							}
    684 							break 2;
    685 						} elseif($pos == 0) {
    686 							$pos = false;
    687 						}
    688 					} else {
    689 						$pos = strpos($line, $mod['search']->getContent());
    690 					}
    691 
    692 					if($pos !== false) {
    693 						$indexCount++;
    694 						$changed = true;
    695 
    696 						if(!$mod['search']->indexes() || ($mod['search']->indexes() && in_array($indexCount, $mod['search']->indexes()))) {
    697 
    698 							switch($mod['search']->position) {
    699 								case 'before':
    700 								$offset = ($lineNum - $mod['search']->offset < 0) ? -1 : $lineNum - $mod['search']->offset;
    701 								$tmp[$offset] = empty($tmp[$offset]) ? $mod['add']->getContent() : $mod['add']->getContent() . "\n" . $tmp[$offset];
    702 								break;
    703 
    704 								case 'after':
    705 								$offset = ($lineNum + $mod['search']->offset > $lineMax) ? $lineMax : $lineNum + $mod['search']->offset;
    706 								$tmp[$offset] = $tmp[$offset] . "\n" . $mod['add']->getContent();
    707 								break;
    708 
    709 								case 'ibefore':
    710 								$tmp[$lineNum] = str_replace($mod['search']->getContent(), $mod['add']->getContent() . $mod['search']->getContent(), $line);
    711 								break;
    712 
    713 								case 'iafter':
    714 								$tmp[$lineNum] = str_replace($mod['search']->getContent(), $mod['search']->getContent() . $mod['add']->getContent(), $line);
    715 								break;
    716 
    717 								default:
    718 								if(!empty($mod['search']->offset)) {
    719 									if($mod['search']->offset > 0) {
    720 										for($i = 1; $i <= $mod['search']->offset; $i++) {
    721 											if(isset($tmp[$lineNum + $i])) {
    722 												$tmp[$lineNum + $i] = '';
    723 											}
    724 										}
    725 									} elseif($mod['search']->offset < 0) {
    726 										for($i = -1; $i >= $mod['search']->offset; $i--) {
    727 											if(isset($tmp[$lineNum + $i])) {
    728 												$tmp[$lineNum + $i] = '';
    729 											}
    730 										}
    731 									}
    732 								}
    733 
    734 								if($mod['search']->regex == 'true') {
    735 									$tmp[$lineNum] = preg_replace($mod['search']->getContent(), $mod['add']->getContent(), $line);
    736 								} else {
    737 									$tmp[$lineNum] = str_replace($mod['search']->getContent(), $mod['add']->getContent(), $line);
    738 								}
    739 								break;
    740 							}
    741 						}
    742 					}
    743 				}
    744 
    745 				if(!$changed) {
    746 					$skip = ($mod['error'] == 'skip' || $mod['error'] == 'log') ? ' (SKIPPED)' : ' (ABORTING MOD)';
    747 
    748 					if($mod['error'] == 'log' || $mod['error'] == 'abort') {
    749 						VQMod::$log->write('VQModObject::applyMod - SEARCH NOT FOUND' . $skip . ': ' . $mod['search']->getContent(), $this);
    750 					}
    751 
    752 					if($mod['error'] == 'abort') {
    753 						$this->_skip = true;
    754 						return;
    755 					}
    756 
    757 				}
    758 
    759 				break;
    760 			}
    761 			ksort($tmp);
    762 			$tmp = $this->_implodeData($tmp);
    763 		}
    764 
    765 		VQMod::$fileModding = false;
    766 
    767 		$data = $tmp;
    768 	}
    769 
    770 	/**
    771 	 * VQModObject::_parseMods()
    772 	 *
    773 	 * @param DOMNode $node <modification> node to be parsed
    774 	 * @return null
    775 	 * @description Parses modifications in preparation for the applyMod method to work
    776 	 */
    777 	private function _parseMods(DOMNode $node){
    778 		$files = $node->getElementsByTagName('file');
    779 
    780 		$replaces = VQMod::$replaces;
    781 
    782 		foreach($files as $file) {
    783 			$path = $file->getAttribute('path') ? $file->getAttribute('path') : '';
    784 			$filesToMod = explode(',', $file->getAttribute('name'));
    785 
    786 			foreach($filesToMod as $filename) {
    787 
    788 				$fileToMod = $path . $filename;
    789 				if(!empty($replaces)) {
    790 					foreach($replaces as $r) {
    791 						if(count($r) == 2) {
    792 							$fileToMod = preg_replace($r[0], $r[1], $fileToMod);
    793 						}
    794 					}
    795 				}
    796 
    797 				$error = ($file->hasAttribute('error')) ? $file->getAttribute('error') : 'log';
    798 				$fullPath = VQMod::path($fileToMod);
    799 
    800 				if(!$fullPath || !file_exists($fullPath)){
    801 					if(strpos($fileToMod, '*') !== false) {
    802 						$fullPath = VQMod::getCwd() . $fileToMod;
    803 					} else {
    804 						if ($error == 'log' || $error == 'abort') {
    805 							$skip = ($error == 'log') ? ' (SKIPPED)' : ' (ABORTING MOD)';
    806 							VQMod::$log->write('VQModObject::parseMods - Could not resolve path for [' . $fileToMod . ']' . $skip, $this);
    807 						}
    808 
    809 						if ($error == 'log' || $error == 'skip') {
    810 							continue;
    811 						} elseif ($error == 'abort') {
    812 							return false;
    813 						}
    814 					}
    815 				}
    816 
    817 				$operations = $file->getElementsByTagName('operation');
    818 
    819 				foreach($operations as $opIndex => $operation) {
    820 					VQMod::$fileModding = $fileToMod . '(' . $opIndex . ')';
    821 					$skipOperation = false;
    822 
    823 					$error = ($operation->hasAttribute('error')) ? $operation->getAttribute('error') : 'abort';
    824 					$ignoreif = $operation->getElementsByTagName('ignoreif')->item(0);
    825 
    826 					if($ignoreif) {
    827 						$ignoreif = new VQSearchNode($ignoreif);
    828 					} else {
    829 						$ignoreif = false;
    830 					}
    831 
    832 					$search = $operation->getElementsByTagName('search')->item(0);
    833 					$add = $operation->getElementsByTagName('add')->item(0);
    834 
    835 					if(!$search) {
    836 						VQMod::$log->write('Operation <search> tag missing', $this);
    837 						$skipOperation = true;
    838 					}
    839 
    840 					if(!$add) {
    841 						VQMod::$log->write('Operation <add> tag missing', $this);
    842 						$skipOperation = true;
    843 					}
    844 
    845 					if(!$skipOperation) {
    846 						$this->mods[$fullPath][] = array(
    847 							'search' 		=> new VQSearchNode($search),
    848 							'add' 			=> new VQAddNode($add),
    849 							'ignoreif'		=> $ignoreif,
    850 							'error'		 	=> $error,
    851 							'fileToMod'		=> $fileToMod,
    852 							'opIndex'		=> $opIndex,
    853 						);
    854 					}
    855 				}
    856 				VQMod::$fileModding = false;
    857 			}
    858 		}
    859 	}
    860 
    861 	/**
    862 	 * VQModObject::_explodeData()
    863 	 *
    864 	 * @param string $data File contents
    865 	 * @return string
    866 	 * @description Splits a file into an array of individual lines
    867 	 */
    868 	private function _explodeData($data) {
    869 		return explode("\n", $data);
    870 	}
    871 
    872 	/**
    873 	 * VQModObject::_implodeData()
    874 	 *
    875 	 * @param array $data Array of lines
    876 	 * @return string
    877 	 * @description Joins an array of lines back into a text file
    878 	 */
    879 	private function _implodeData($data) {
    880 		return implode("\n", $data);
    881 	}
    882 }
    883 
    884 /**
    885  * VQNode
    886  * @description Basic node object blueprint
    887  */
    888 class VQNode {
    889 	public $regex = 'false';
    890 	public $trim = 'false';
    891 	private $_content = '';
    892 
    893 	/**
    894 	 * VQNode::__construct()
    895 	 *
    896 	 * @param DOMNode $node Search/add node
    897 	 * @return null
    898 	 * @description Parses the node attributes and sets the node property
    899 	 */
    900 	public function  __construct(DOMNode $node) {
    901 		$this->_content = $node->nodeValue;
    902 
    903 		if($node->hasAttributes()) {
    904 			foreach($node->attributes as $attr) {
    905 				$name = $attr->nodeName;
    906 				if(isset($this->$name)) {
    907 					$this->$name = $attr->nodeValue;
    908 				}
    909 			}
    910 		}
    911 	}
    912 
    913 	/**
    914 	 * VQNode::getContent()
    915 	 *
    916 	 * @return string
    917 	 * @description Returns the content, trimmed if applicable
    918 	 */
    919 	public function getContent() {
    920 		$content = ($this->trim == 'true') ? trim($this->_content) : $this->_content;
    921 		return $content;
    922 	}
    923 }
    924 
    925 /**
    926  * VQSearchNode
    927  * @description Object for the <search> xml tags
    928  */
    929 class VQSearchNode extends VQNode {
    930 	public $position = 'replace';
    931 	public $offset = 0;
    932 	public $index = 'false';
    933 	public $regex = 'false';
    934 	public $trim = 'true';
    935 
    936 	/**
    937 	 * VQSearchNode::indexes()
    938 	 *
    939 	 * @return bool, array
    940 	 * @description Returns the index values to use the search on, or false if none
    941 	 */
    942 	public function indexes() {
    943 		if($this->index == 'false') {
    944 			return false;
    945 		}
    946 		$tmp = explode(',', $this->index);
    947 		foreach($tmp as $k => $v) {
    948 			if(!is_int($v)) {
    949 				unset($k);
    950 			}
    951 		}
    952 		$tmp = array_unique($tmp);
    953 		return empty($tmp) ? false : $tmp;
    954 	}
    955 }
    956 
    957 /**
    958  * VQAddNode
    959  * @description Object for the <add> xml tags
    960  */
    961 class VQAddNode extends VQNode {
    962 	public $position = false;
    963 	public $offset = false;
    964 	public $index = false;
    965 	public $regex = false;
    966 	public $trim = 'false';
    967 }