1

使用 vqmod 的默认错误记录如下:

---------- Date: 2012-10-09 19:46:06 ~ IP : 127.0.0.1 ----------
REQUEST URI : /oc/
MOD DETAILS:
   modFile   : C:\wamp\www\oc\vqmod\xml\templace.xml
   id        : Template
   version   : 1.5.2 - 1.5.2.1
   vqmver    : 1.0.8
   author    : templace.com
SEARCH NOT FOUND (ABORTING MOD): require_once(foo . 'library/template.php');

----------------------------------------------------------------------

导致错误的示例 vqmod

<modification>
    <id>Templace</id>
    <version>1.5.2 - 1.5.2.1</version>
    <author>templace.com</author>   
    <vqmver>1.0.8</vqmver>

    <file name="system/startup.php">
        <operation>
            <search position="before"><![CDATA[
            require_once(foo . 'library/template.php');
            ]]></search>
            <add><![CDATA[
            require_once(DIR_SYSTEM . 'library/templace.php');
            ]]></add>
        </operation>
    </file>
</modification>

要解决此问题,我必须打开 vqmod 文件templace.xml并搜索file[name]此错误所指的内容。

问题:我如何添加file[name]实际错误所指的父级?

例如,在错误消息中添加:“system/startup.php”以使其更易于调试。

vqmod.php

/**
 * VQMod
 * @description Main Object used
 */
final class VQMod {
    private $_vqversion = '2.1.7';
    private $_modFileList = array();
    private $_mods = array();
    private $_filesModded = array();
    private $_cwd = '';
    private $_doNotMod = array();
    private $_virtualMode = true;

    public $useCache = false;
    public $logFilePath = 'vqmod/vqmod.log';
    public $vqCachePath = 'vqmod/vqcache/';
    public $protectedFilelist = 'vqmod/vqprotect.txt';
    public $logging = true;
    public $cacheTime = 5; // local=5secs live=60secs
    public $log;

    /**
     * VQMod::__construct()
     *
     * @param bool $path File path to use
     * @param bool $logging Enable/disabled logging
     * @return null
     * @description Startup of VQMod
     */
    public function __construct($path = false, $logging = true) {
        if(!class_exists('DOMDocument')) {
            die('ERROR - YOU NEED DOMDocument INSTALLED TO USE VQMod');
        }

        if(!$path){
            $path = dirname(dirname(__FILE__));
        }
        $this->_setCwd($path);

        $this->logging = (bool) $logging;
        $this->log = new VQModLog($this);

        $this->_getMods();
        $this->_loadProtected();
    }

    /**
     * VQMod::modCheck()
     *
     * @param string $sourceFile path for file
     * @return string
     * @description Checks if a file has modifications and applies them, returning cache files or the file name
     */
    public function modCheck($sourceFile) {

        if(!preg_match('%^([a-z]:)?[\\\\/]%i', $sourceFile)) {
            $sourcePath = $this->path($sourceFile);
        } else {
            $sourcePath = realpath($sourceFile);
        }

        if(!$sourcePath || is_dir($sourcePath) || in_array($sourcePath, $this->_doNotMod)) {
            return $sourceFile;
        }

        $stripped_filename = preg_replace('~^' . preg_quote($this->getCwd(), '~') . '~', '', $sourcePath);
        $cacheFile = $this->_cacheName($stripped_filename);

        if($this->useCache && file_exists($cacheFile)) {
            //return $cacheFile; // useCache being Deprecated in favor of cacheTime
        }

        if(isset($this->_filesModded[$sourcePath])) {
            return $this->_filesModded[$sourcePath]['cached'] ? $cacheFile : $sourceFile;
        }

        $changed = false;
        $fileHash = sha1_file($sourcePath);
        $fileData = file_get_contents($sourcePath);

        foreach($this->_mods as $modObject) {
            foreach($modObject->mods as $path => $mods) {
                if($this->_checkMatch($path, $sourcePath)) {
                    $modObject->applyMod($mods, $fileData);
                }
            }
        }

        // START QPHORIA CACHELOCK CODE
        //
        if (sha1($fileData) != $fileHash) {
            $writePath = $cacheFile;
            $cacheLock = false;
            if(file_exists($writePath) && ((filemtime($writePath) + (float)$this->cacheTime) >= time())) {
                $cacheLock = true;
                $changed = true;
            }
            if(!$cacheLock && (!file_exists($writePath) || is_writable($writePath))) {
                file_put_contents($writePath, $fileData);
                $changed = true;
            } else {
                //file_put_contents('./cachelock.txt', "$writePath \r\n", FILE_APPEND); // debugging only.
            }
            //file_put_contents('./cachetotal.txt', "$writePath \r\n", FILE_APPEND);
        } // END QPHORIA CACHELOCK CODE

        /* Original Code
        if(sha1($fileData) != $fileHash) {
            $writePath = $this->_virtualMode ?  $cacheFile : $sourcePath;
            if(!file_exists($writePath) || is_writable($writePath)) {
                file_put_contents($writePath, $fileData);
                $changed = true;
            }
        }*/

        $this->_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 function path($path, $skip_real = false) {
        $tmp = $this->_cwd . $path;
        $realpath = $skip_real ? $tmp : realpath($tmp);
        if(!$realpath) {
            return false;
        }
        if(is_dir($realpath)) {
            $realpath = rtrim($realpath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
        }
        return $realpath;
    }

    /**
     * VQMod::getCwd()
     *
     * @return string
     * @description Returns current working directory
     */
    public function getCwd() {
        return $this->_cwd;
    }

    /**
     * VQMod::_getMods()
     *
     * @return null
     * @description Gets list of XML files in vqmod xml folder for processing
     */
    private function _getMods() {

        $this->_modFileList = glob($this->path('vqmod/xml/') . '*.xml');

        if($this->_modFileList) {
            $this->_parseMods();
        } else {
            $this->log->write('NO MODS IN USE');
        }
    }

    /**
     * VQMod::_parseMods()
     *
     * @return null
     * @description Loops through xml files and attempts to load them as VQModObject's
     */
    private function _parseMods() {

        $dom = new DOMDocument('1.0', 'UTF-8');
        foreach($this->_modFileList as $modFileKey => $modFile) {
            if(file_exists($modFile)) {
                if(@$dom->load($modFile)) {
                    $mod = $dom->getElementsByTagName('modification')->item(0);
                    $this->_mods[] = new VQModObject($mod, $modFile, $this);
                } else {
                    $this->log->write('DOM UNABLE TO LOAD: ' . $modFile);
                }
            } else {
                $this->log->write('FILE NOT FOUND: ' . $modFile);
            }
        }
    }

    /**
     * VQMod::_loadProtected()
     *
     * @return null
     * @description Loads protected list and adds them to _doNotMod array
     */
    private function _loadProtected() {
        $file = $this->path($this->protectedFilelist);
        if($file && is_file($file)) {
            $protected = file_get_contents($file);
            if(!empty($protected)) {
                $protected = preg_replace('~\r?\n~', "\n", $protected);
                $paths = explode("\n", $protected);
                foreach($paths as $path) {
                    $fullPath = $this->path($path);
                    if($fullPath && !in_array($fullPath, $this->_doNotMod)) {
                        $this->_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 function _cacheName($file) {
        return $this->path($this->vqCachePath) . '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 function _setCwd($path) {
        $realpath = realpath($path);
        if(!$realpath) {
            die('COULDNT RESOLVE CWD REALPATH');
        }
        $this->_cwd = rtrim($realpath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
    }

    /**
     * VQMod::_checkMatch()
     *
     * @param string $modFilePath Modification path from a <file> node
     * @param string $checkFilePath File path
     * @return bool
     * @description Checks a modification path against a file path
     */
    private function _checkMatch($modFilePath, $checkFilePath) {
        $modFilePath = str_replace('\\', '/', $modFilePath);
        $checkFilePath = str_replace('\\', '/', $checkFilePath);

        $modFilePath = preg_replace('/([^*]+)/e', 'preg_quote("$1", "~")', $modFilePath);
        $modFilePath = str_replace('*', '[^/]*', $modFilePath);
        $return = (bool) preg_match('~^' . $modFilePath . '$~', $checkFilePath);
        return $return;

    }
}

/**
 * VQModLog
 * @description Object to log information to a file
 */
class VQModLog {
    private $_sep;
    private $_vqmod;
    private $_defhash = 'da39a3ee5e6b4b0d3255bfef95601890afd80709';
    private $_logs = array();

    /**
     * VQModLog::__construct()
     *
     * @param VQMod $vqmod VQMod main class as reference
     * @return null
     * @description Object instantiation method
     */
    public function __construct(VQMod $vqmod) {
        $this->_vqmod = $vqmod;
        $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) || $this->_vqmod->logging == false) {
            return;
        }

        $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);

        $logPath = $this->_vqmod->path($this->_vqmod->logFilePath, true);
        if(!file_exists($logPath)) {
            $res = file_put_contents($logPath, '');
            if($res === false) {
                die('COULD NOT WRITE TO LOG FILE');
            }
        }

        file_put_contents($logPath, implode(PHP_EOL, $txt), FILE_APPEND);
    }

    /**
     * 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()
            );
        }

        $this->_logs[$hash]['log'][] = $data;

    }
}

/**
 * VQModObject
 * @description Object for the <modification> that orchestrates each applied modification
 */
class VQModObject {
    public $modFile = '';
    public $id = '';
    public $version = '';
    public $vqmver = '';
    public $author = '';
    public $mods = array();

    private $_vqmod;
    private $_skip = false;

    /**
     * VQModObject::__construct()
     *
     * @param DOMNode $node <modification> node
     * @param string $modFile File modification is from
     * @param VQMod $vqmod VQMod object as reference
     * @return null
     * @description Loads modification meta information
     */
    public function __construct(DOMNode $node, $modFile, VQmod $vqmod) {
        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->_vqmod = $vqmod;
        $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) {
            $indexCount = 0;
            $tmp = $this->_explodeData($tmp);
            $lineMax = count($tmp) - 1;

            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;

                case 'all':
                $tmp = array($mod['add']->getContent());
                break;

                default:

                $changed = false;
                foreach($tmp as $lineNum => $line) {
                    if($mod['search']->regex == 'true') {
                        $pos = @preg_match($mod['search']->getContent(), $line);
                        if($pos === false) {
                            if($mod['error'] == 'log' || $mod['error'] == 'abort' ) {
                                $this->_vqmod->log->write('INVALID REGEX ERROR - ' . $mod['search']->getContent(), $this);
                            }
                            continue 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;

                                default:
                                if(!empty($mod['search']->offset)) {
                                    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') {
                        $this->_vqmod->log->write('SEARCH NOT FOUND' . $skip . ': ' . $mod['search']->getContent(), $this);
                    }

                    if($mod['error'] == 'abort') {
                        $this->_skip = true;
                        return;
                    }

                }

                break;
            }
            ksort($tmp);
            $tmp = $this->_implodeData($tmp);
        }

        $data = $tmp;
    }

    /**
     * VQModObject::_parseMods()
     *
     * @param DOMNode $node <modification> 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');

        foreach($files as $file) {
            $fileToMod = $file->getAttribute('name');
            $error = ($file->hasAttribute('error')) ? $file->getAttribute('error') : 'log';
            $fullPath = $this->_vqmod->path($fileToMod);

            if(!$fullPath){
                if(strpos($fileToMod, '*') !== false) {
                    $fullPath = $this->_vqmod->getCwd() . $fileToMod;
                } else {
                    if ($error == 'log' || $error == 'abort') {
                        $skip = ($error == 'log') ? ' (SKIPPED)' : ' (ABORTING MOD)';
                        $this->_vqmod->log->write('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 $operation) {

                $error = ($operation->hasAttribute('error')) ? $operation->getAttribute('error') : 'abort';

                $this->mods[$fullPath][] = array(
                    'search'        => new VQSearchNode($operation->getElementsByTagName('search')->item(0)),
                    'add'           => new VQAddNode($operation->getElementsByTagName('add')->item(0)),
                    'error'         => $error
                );
            }
        }
    }

    /**
     * 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 $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 <search> 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 <add> xml tags
 */
class VQAddNode extends VQNode {
}

还有一些其他想法可以使调试更加容易:

  1. 列出以前编辑过此相同文件的任何其他 vqmod 文件。这是另一个常见问题,我发现两个扩展程序正在编辑同一个文件,而后者导致错误,但了解任何其他 vqmods 编辑同一个文件会很有用。是的,我想我可以添加error="skip"所有内容,但不认为这是隐藏所有错误的最佳方法,应该让用户知道存在错误......

  2. “建议修复”,也许是一些聪明的方法可以测试它是什么类型的错误。与我上面所说的相矛盾,但即使是最基本的形式,如果它不是必需的,你也可以建议隐藏错误。这样任何人都可以阅读它并了解它是如何修复它的。例如

    OPEN: vqmod/xml/templace.xml (line:23)
    FIND: <operation>
    REPLACE <operation error="skip">
    
  3. 在 XML 文件中添加错误来自的行号。不必一直搜索并且可以快速转到 vqmod 中的行号会很可爱

4

1 回答 1

1

正在编辑的文件的问题肯定是一个过期的问题,我计划在下一版本的 vQmod 中添加一个问题。至于其他建议

  1. 有趣的想法,当然可以考虑。我看到的唯一问题是它可能会使一些日志文件变得巨大
  2. 这几乎是不可能合并的
  3. 如果没有一些非常昂贵的运行时,这是不可能的。错误并不在于 XML 本身,因此需要重新打开已解析的 xml,逐行搜索有问题的行,然后报告。这听起来很简单,但您必须记住,xml 可以为多个操作使用相同的搜索参数 - 所以在这种情况下,您最好自己搜索文件
于 2012-10-09T22:43:26.387 回答