diff --git a/interface/lib/classes/finediff.inc.php b/interface/lib/classes/finediff.inc.php new file mode 100644 index 0000000000000000000000000000000000000000..43b542f3629ec5ce14741267a0cdf991865bd193 --- /dev/null +++ b/interface/lib/classes/finediff.inc.php @@ -0,0 +1,752 @@ +<?php +/** +* FINE granularity DIFF +* +* Computes a set of instructions to convert the content of +* one string into another. +* +* Copyright (c) 2011 Raymond Hill (http://raymondhill.net/blog/?p=441) +* +* Licensed under The MIT License +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in +* all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +* THE SOFTWARE. +* +* @copyright Copyright 2011 (c) Raymond Hill (http://raymondhill.net/blog/?p=441) +* @link http://www.raymondhill.net/finediff/ +* @version 0.6 +* @license MIT License (http://www.opensource.org/licenses/mit-license.php) +*/ + +/** +* Usage (simplest): +* +* include 'finediff.php'; +* +* // for the stock stack, granularity values are: +* // FineDiff::$paragraphGranularity = paragraph/line level +* // FineDiff::$sentenceGranularity = sentence level +* // FineDiff::$wordGranularity = word level +* // FineDiff::$characterGranularity = character level [default] +* +* $opcodes = FineDiff::getDiffOpcodes($from_text, $to_text [, $granularityStack = null] ); +* // store opcodes for later use... +* +* ... +* +* // restore $to_text from $from_text + $opcodes +* include 'finediff.php'; +* $to_text = FineDiff::renderToTextFromOpcodes($from_text, $opcodes); +* +* ... +*/ + +/** +* Persisted opcodes (string) are a sequence of atomic opcode. +* A single opcode can be one of the following: +* c | c{n} | d | d{n} | i:{c} | i{length}:{s} +* 'c' = copy one character from source +* 'c{n}' = copy n characters from source +* 'd' = skip one character from source +* 'd{n}' = skip n characters from source +* 'i:{c} = insert character 'c' +* 'i{n}:{s}' = insert string s, which is of length n +* +* Do not exist as of now, under consideration: +* 'm{n}:{o} = move n characters from source o characters ahead. +* It would be essentially a shortcut for a delete->copy->insert +* command (swap) for when the inserted segment is exactly the same +* as the deleted one, and with only a copy operation in between. +* TODO: How often this case occurs? Is it worth it? Can only +* be done as a postprocessing method (->optimize()?) +*/ +abstract class FineDiffOp { + abstract public function getFromLen(); + abstract public function getToLen(); + abstract public function getOpcode(); + } + +class FineDiffDeleteOp extends FineDiffOp { + public function __construct($len) { + $this->fromLen = $len; + } + public function getFromLen() { + return $this->fromLen; + } + public function getToLen() { + return 0; + } + public function getOpcode() { + if ( $this->fromLen === 1 ) { + return 'd'; + } + return "d{$this->fromLen}"; + } + } + +class FineDiffInsertOp extends FineDiffOp { + public function __construct($text) { + $this->text = $text; + } + public function getFromLen() { + return 0; + } + public function getToLen() { + return strlen($this->text); + } + public function getText() { + return $this->text; + } + public function getOpcode() { + $to_len = strlen($this->text); + if ( $to_len === 1 ) { + return "i:{$this->text}"; + } + return "i{$to_len}:{$this->text}"; + } + } + +class FineDiffReplaceOp extends FineDiffOp { + public function __construct($fromLen, $text) { + $this->fromLen = $fromLen; + $this->text = $text; + } + public function getFromLen() { + return $this->fromLen; + } + public function getToLen() { + return strlen($this->text); + } + public function getText() { + return $this->text; + } + public function getOpcode() { + if ( $this->fromLen === 1 ) { + $del_opcode = 'd'; + } + else { + $del_opcode = "d{$this->fromLen}"; + } + $to_len = strlen($this->text); + if ( $to_len === 1 ) { + return "{$del_opcode}i:{$this->text}"; + } + return "{$del_opcode}i{$to_len}:{$this->text}"; + } + } + +class FineDiffCopyOp extends FineDiffOp { + public function __construct($len) { + $this->len = $len; + } + public function getFromLen() { + return $this->len; + } + public function getToLen() { + return $this->len; + } + public function getOpcode() { + if ( $this->len === 1 ) { + return 'c'; + } + return "c{$this->len}"; + } + public function increase($size) { + return $this->len += $size; + } + } + +/** +* FineDiff ops +* +* Collection of ops +*/ +class FineDiffOps { + public function appendOpcode($opcode, $from, $from_offset, $from_len) { + if ( $opcode === 'c' ) { + $edits[] = new FineDiffCopyOp($from_len); + } + else if ( $opcode === 'd' ) { + $edits[] = new FineDiffDeleteOp($from_len); + } + else /* if ( $opcode === 'i' ) */ { + $edits[] = new FineDiffInsertOp(substr($from, $from_offset, $from_len)); + } + } + public $edits = array(); + } + +/** +* FineDiff class +* +* TODO: Document +* +*/ +class FineDiff { + + /**------------------------------------------------------------------------ + * + * Public section + * + */ + + /** + * Constructor + * ... + * The $granularityStack allows FineDiff to be configurable so that + * a particular stack tailored to the specific content of a document can + * be passed. + */ + public function __construct($from_text = '', $to_text = '', $granularityStack = null) { + // setup stack for generic text documents by default + $this->granularityStack = $granularityStack ? $granularityStack : FineDiff::$characterGranularity; + $this->edits = array(); + $this->from_text = $from_text; + $this->doDiff($from_text, $to_text); + } + + public function getOps() { + return $this->edits; + } + + public function getOpcodes() { + $opcodes = array(); + foreach ( $this->edits as $edit ) { + $opcodes[] = $edit->getOpcode(); + } + return implode('', $opcodes); + } + + public function renderDiffToHTML() { + $in_offset = 0; + ob_start(); + foreach ( $this->edits as $edit ) { + $n = $edit->getFromLen(); + if ( $edit instanceof FineDiffCopyOp ) { + FineDiff::renderDiffToHTMLFromOpcode('c', $this->from_text, $in_offset, $n); + } + else if ( $edit instanceof FineDiffDeleteOp ) { + FineDiff::renderDiffToHTMLFromOpcode('d', $this->from_text, $in_offset, $n); + } + else if ( $edit instanceof FineDiffInsertOp ) { + FineDiff::renderDiffToHTMLFromOpcode('i', $edit->getText(), 0, $edit->getToLen()); + } + else /* if ( $edit instanceof FineDiffReplaceOp ) */ { + FineDiff::renderDiffToHTMLFromOpcode('d', $this->from_text, $in_offset, $n); + FineDiff::renderDiffToHTMLFromOpcode('i', $edit->getText(), 0, $edit->getToLen()); + } + $in_offset += $n; + } + return ob_get_clean(); + } + + /**------------------------------------------------------------------------ + * Return an opcodes string describing the diff between a "From" and a + * "To" string + */ + public static function getDiffOpcodes($from, $to, $granularities = null) { + $diff = new FineDiff($from, $to, $granularities); + return $diff->getOpcodes(); + } + + /**------------------------------------------------------------------------ + * Return an iterable collection of diff ops from an opcodes string + */ + public static function getDiffOpsFromOpcodes($opcodes) { + $diffops = new FineDiffOps(); + FineDiff::renderFromOpcodes(null, $opcodes, array($diffops,'appendOpcode')); + return $diffops->edits; + } + + /**------------------------------------------------------------------------ + * Re-create the "To" string from the "From" string and an "Opcodes" string + */ + public static function renderToTextFromOpcodes($from, $opcodes) { + ob_start(); + FineDiff::renderFromOpcodes($from, $opcodes, array('FineDiff','renderToTextFromOpcode')); + return ob_get_clean(); + } + + /**------------------------------------------------------------------------ + * Render the diff to an HTML string -- UTF8 unsafe + */ + public static function renderDiffToHTMLFromOpcodes($from, $opcodes) { + ob_start(); + FineDiff::renderFromOpcodes($from, $opcodes, array('FineDiff','renderDiffToHTMLFromOpcode')); + return ob_get_clean(); + } + + /**------------------------------------------------------------------------ + * Render the diff to an HTML string -- UTF8 safe + */ + public static function renderUTF8DiffToHTMLFromOpcodes($from, $opcodes) { + ob_start(); + FineDiff::renderUTF8FromOpcode($from, $opcodes, array('FineDiff','renderDiffToHTMLFromOpcode')); + return ob_get_clean(); + } + + /**------------------------------------------------------------------------ + * Generic opcodes parser, user must supply callback for handling + * single opcode + */ + public static function renderFromOpcodes($from, $opcodes, $callback) { + if ( !is_callable($callback) ) { + return; + } + $opcodes_len = strlen($opcodes); + $from_offset = $opcodes_offset = 0; + while ( $opcodes_offset < $opcodes_len ) { + $opcode = substr($opcodes, $opcodes_offset, 1); + $opcodes_offset++; + $n = intval(substr($opcodes, $opcodes_offset)); + if ( $n ) { + $opcodes_offset += strlen(strval($n)); + } + else { + $n = 1; + } + if ( $opcode === 'c' ) { // copy n characters from source + call_user_func($callback, 'c', $from, $from_offset, $n, ''); + $from_offset += $n; + } + else if ( $opcode === 'd' ) { // delete n characters from source + call_user_func($callback, 'd', $from, $from_offset, $n, ''); + $from_offset += $n; + } + else /* if ( $opcode === 'i' ) */ { // insert n characters from opcodes + call_user_func($callback, 'i', $opcodes, $opcodes_offset + 1, $n); + $opcodes_offset += 1 + $n; + } + } + } + + /**------------------------------------------------------------------------ + * Generic opcodes parser, user must supply callback for handling + * single opcode + */ + private static function renderUTF8FromOpcode($from, $opcodes, $callback) { + if ( !is_callable($callback) ) { + return; + } + $from_len = strlen($from); + $opcodes_len = strlen($opcodes); + $from_offset = $opcodes_offset = 0; + $last_to_chars = ''; + while ( $opcodes_offset < $opcodes_len ) { + $opcode = substr($opcodes, $opcodes_offset, 1); + $opcodes_offset++; + $n = intval(substr($opcodes, $opcodes_offset)); + if ( $n ) { + $opcodes_offset += strlen(strval($n)); + } + else { + $n = 1; + } + if ( $opcode === 'c' || $opcode === 'd' ) { + $beg = $from_offset; + $end = $from_offset + $n; + while ( $beg > 0 && (ord($from[$beg]) & 0xC0) === 0x80 ) { $beg--; } + while ( $end < $from_len && (ord($from[$end]) & 0xC0) === 0x80 ) { $end++; } + if ( $opcode === 'c' ) { // copy n characters from source + call_user_func($callback, 'c', $from, $beg, $end - $beg, ''); + $last_to_chars = substr($from, $beg, $end - $beg); + } + else /* if ( $opcode === 'd' ) */ { // delete n characters from source + call_user_func($callback, 'd', $from, $beg, $end - $beg, ''); + } + $from_offset += $n; + } + else /* if ( $opcode === 'i' ) */ { // insert n characters from opcodes + $opcodes_offset += 1; + if ( strlen($last_to_chars) > 0 && (ord($opcodes[$opcodes_offset]) & 0xC0) === 0x80 ) { + $beg = strlen($last_to_chars) - 1; + while ( $beg > 0 && (ord($last_to_chars[$beg]) & 0xC0) === 0x80 ) { $beg--; } + $prefix = substr($last_to_chars, $beg); + } else { + $prefix = ''; + } + $end = $from_offset; + while ( $end < $from_len && (ord($from[$end]) & 0xC0) === 0x80 ) { $end++; } + $toInsert = $prefix . substr($opcodes, $opcodes_offset, $n) . substr($from, $end, $end - $from_offset); + call_user_func($callback, 'i', $toInsert, 0, strlen($toInsert)); + $opcodes_offset += $n; + $last_to_chars = $toInsert; + } + } + } + + /** + * Stock granularity stacks and delimiters + */ + + const paragraphDelimiters = "\n\r"; + public static $paragraphGranularity = array( + FineDiff::paragraphDelimiters + ); + const sentenceDelimiters = ".\n\r"; + public static $sentenceGranularity = array( + FineDiff::paragraphDelimiters, + FineDiff::sentenceDelimiters + ); + const wordDelimiters = " \t.\n\r"; + public static $wordGranularity = array( + FineDiff::paragraphDelimiters, + FineDiff::sentenceDelimiters, + FineDiff::wordDelimiters + ); + const characterDelimiters = ""; + public static $characterGranularity = array( + FineDiff::paragraphDelimiters, + FineDiff::sentenceDelimiters, + FineDiff::wordDelimiters, + FineDiff::characterDelimiters + ); + + public static $textStack = array( + ".", + " \t.\n\r", + "" + ); + + /**------------------------------------------------------------------------ + * + * Private section + * + */ + + /** + * Entry point to compute the diff. + */ + private function doDiff($from_text, $to_text) { + $this->last_edit = false; + $this->stackpointer = 0; + $this->from_text = $from_text; + $this->from_offset = 0; + // can't diff without at least one granularity specifier + if ( empty($this->granularityStack) ) { + return; + } + $this->_processGranularity($from_text, $to_text); + } + + /** + * This is the recursive function which is responsible for + * handling/increasing granularity. + * + * Incrementally increasing the granularity is key to compute the + * overall diff in a very efficient way. + */ + private function _processGranularity($from_segment, $to_segment) { + $delimiters = $this->granularityStack[$this->stackpointer++]; + $has_next_stage = $this->stackpointer < count($this->granularityStack); + foreach ( FineDiff::doFragmentDiff($from_segment, $to_segment, $delimiters) as $fragment_edit ) { + // increase granularity + if ( $fragment_edit instanceof FineDiffReplaceOp && $has_next_stage ) { + $this->_processGranularity( + substr($this->from_text, $this->from_offset, $fragment_edit->getFromLen()), + $fragment_edit->getText() + ); + } + // fuse copy ops whenever possible + else if ( $fragment_edit instanceof FineDiffCopyOp && $this->last_edit instanceof FineDiffCopyOp ) { + $this->edits[count($this->edits)-1]->increase($fragment_edit->getFromLen()); + $this->from_offset += $fragment_edit->getFromLen(); + } + else { + /* $fragment_edit instanceof FineDiffCopyOp */ + /* $fragment_edit instanceof FineDiffDeleteOp */ + /* $fragment_edit instanceof FineDiffInsertOp */ + $this->edits[] = $this->last_edit = $fragment_edit; + $this->from_offset += $fragment_edit->getFromLen(); + } + } + $this->stackpointer--; + } + + /** + * This is the core algorithm which actually perform the diff itself, + * fragmenting the strings as per specified delimiters. + * + * This function is naturally recursive, however for performance purpose + * a local job queue is used instead of outright recursivity. + */ + private static function doFragmentDiff($from_text, $to_text, $delimiters) { + // Empty delimiter means character-level diffing. + // In such case, use code path optimized for character-level + // diffing. + if ( empty($delimiters) ) { + return FineDiff::doCharDiff($from_text, $to_text); + } + + $result = array(); + + // fragment-level diffing + $from_text_len = strlen($from_text); + $to_text_len = strlen($to_text); + $from_fragments = FineDiff::extractFragments($from_text, $delimiters); + $to_fragments = FineDiff::extractFragments($to_text, $delimiters); + + $jobs = array(array(0, $from_text_len, 0, $to_text_len)); + + $cached_array_keys = array(); + + while ( $job = array_pop($jobs) ) { + + // get the segments which must be diff'ed + list($from_segment_start, $from_segment_end, $to_segment_start, $to_segment_end) = $job; + + // catch easy cases first + $from_segment_length = $from_segment_end - $from_segment_start; + $to_segment_length = $to_segment_end - $to_segment_start; + if ( !$from_segment_length || !$to_segment_length ) { + if ( $from_segment_length ) { + $result[$from_segment_start * 4] = new FineDiffDeleteOp($from_segment_length); + } + else if ( $to_segment_length ) { + $result[$from_segment_start * 4 + 1] = new FineDiffInsertOp(substr($to_text, $to_segment_start, $to_segment_length)); + } + continue; + } + + // find longest copy operation for the current segments + $best_copy_length = 0; + + $from_base_fragment_index = $from_segment_start; + + $cached_array_keys_for_current_segment = array(); + + while ( $from_base_fragment_index < $from_segment_end ) { + $from_base_fragment = $from_fragments[$from_base_fragment_index]; + $from_base_fragment_length = strlen($from_base_fragment); + // performance boost: cache array keys + if ( !isset($cached_array_keys_for_current_segment[$from_base_fragment]) ) { + if ( !isset($cached_array_keys[$from_base_fragment]) ) { + $to_all_fragment_indices = $cached_array_keys[$from_base_fragment] = array_keys($to_fragments, $from_base_fragment, true); + } + else { + $to_all_fragment_indices = $cached_array_keys[$from_base_fragment]; + } + // get only indices which falls within current segment + if ( $to_segment_start > 0 || $to_segment_end < $to_text_len ) { + $to_fragment_indices = array(); + foreach ( $to_all_fragment_indices as $to_fragment_index ) { + if ( $to_fragment_index < $to_segment_start ) { continue; } + if ( $to_fragment_index >= $to_segment_end ) { break; } + $to_fragment_indices[] = $to_fragment_index; + } + $cached_array_keys_for_current_segment[$from_base_fragment] = $to_fragment_indices; + } + else { + $to_fragment_indices = $to_all_fragment_indices; + } + } + else { + $to_fragment_indices = $cached_array_keys_for_current_segment[$from_base_fragment]; + } + // iterate through collected indices + foreach ( $to_fragment_indices as $to_base_fragment_index ) { + $fragment_index_offset = $from_base_fragment_length; + // iterate until no more match + for (;;) { + $fragment_from_index = $from_base_fragment_index + $fragment_index_offset; + if ( $fragment_from_index >= $from_segment_end ) { + break; + } + $fragment_to_index = $to_base_fragment_index + $fragment_index_offset; + if ( $fragment_to_index >= $to_segment_end ) { + break; + } + if ( $from_fragments[$fragment_from_index] !== $to_fragments[$fragment_to_index] ) { + break; + } + $fragment_length = strlen($from_fragments[$fragment_from_index]); + $fragment_index_offset += $fragment_length; + } + if ( $fragment_index_offset > $best_copy_length ) { + $best_copy_length = $fragment_index_offset; + $best_from_start = $from_base_fragment_index; + $best_to_start = $to_base_fragment_index; + } + } + $from_base_fragment_index += strlen($from_base_fragment); + // If match is larger than half segment size, no point trying to find better + // TODO: Really? + if ( $best_copy_length >= $from_segment_length / 2) { + break; + } + // no point to keep looking if what is left is less than + // current best match + if ( $from_base_fragment_index + $best_copy_length >= $from_segment_end ) { + break; + } + } + + if ( $best_copy_length ) { + $jobs[] = array($from_segment_start, $best_from_start, $to_segment_start, $best_to_start); + $result[$best_from_start * 4 + 2] = new FineDiffCopyOp($best_copy_length); + $jobs[] = array($best_from_start + $best_copy_length, $from_segment_end, $best_to_start + $best_copy_length, $to_segment_end); + } + else { + $result[$from_segment_start * 4 ] = new FineDiffReplaceOp($from_segment_length, substr($to_text, $to_segment_start, $to_segment_length)); + } + } + + ksort($result, SORT_NUMERIC); + return array_values($result); + } + + /** + * Perform a character-level diff. + * + * The algorithm is quite similar to doFragmentDiff(), except that + * the code path is optimized for character-level diff -- strpos() is + * used to find out the longest common subequence of characters. + * + * We try to find a match using the longest possible subsequence, which + * is at most the length of the shortest of the two strings, then incrementally + * reduce the size until a match is found. + * + * I still need to study more the performance of this function. It + * appears that for long strings, the generic doFragmentDiff() is more + * performant. For word-sized strings, doCharDiff() is somewhat more + * performant. + */ + private static function doCharDiff($from_text, $to_text) { + $result = array(); + $jobs = array(array(0, strlen($from_text), 0, strlen($to_text))); + while ( $job = array_pop($jobs) ) { + // get the segments which must be diff'ed + list($from_segment_start, $from_segment_end, $to_segment_start, $to_segment_end) = $job; + $from_segment_len = $from_segment_end - $from_segment_start; + $to_segment_len = $to_segment_end - $to_segment_start; + + // catch easy cases first + if ( !$from_segment_len || !$to_segment_len ) { + if ( $from_segment_len ) { + $result[$from_segment_start * 4 + 0] = new FineDiffDeleteOp($from_segment_len); + } + else if ( $to_segment_len ) { + $result[$from_segment_start * 4 + 1] = new FineDiffInsertOp(substr($to_text, $to_segment_start, $to_segment_len)); + } + continue; + } + if ( $from_segment_len >= $to_segment_len ) { + $copy_len = $to_segment_len; + while ( $copy_len ) { + $to_copy_start = $to_segment_start; + $to_copy_start_max = $to_segment_end - $copy_len; + while ( $to_copy_start <= $to_copy_start_max ) { + $from_copy_start = strpos(substr($from_text, $from_segment_start, $from_segment_len), substr($to_text, $to_copy_start, $copy_len)); + if ( $from_copy_start !== false ) { + $from_copy_start += $from_segment_start; + break 2; + } + $to_copy_start++; + } + $copy_len--; + } + } + else { + $copy_len = $from_segment_len; + while ( $copy_len ) { + $from_copy_start = $from_segment_start; + $from_copy_start_max = $from_segment_end - $copy_len; + while ( $from_copy_start <= $from_copy_start_max ) { + $to_copy_start = strpos(substr($to_text, $to_segment_start, $to_segment_len), substr($from_text, $from_copy_start, $copy_len)); + if ( $to_copy_start !== false ) { + $to_copy_start += $to_segment_start; + break 2; + } + $from_copy_start++; + } + $copy_len--; + } + } + // match found + if ( $copy_len ) { + $jobs[] = array($from_segment_start, $from_copy_start, $to_segment_start, $to_copy_start); + $result[$from_copy_start * 4 + 2] = new FineDiffCopyOp($copy_len); + $jobs[] = array($from_copy_start + $copy_len, $from_segment_end, $to_copy_start + $copy_len, $to_segment_end); + } + // no match, so delete all, insert all + else { + $result[$from_segment_start * 4] = new FineDiffReplaceOp($from_segment_len, substr($to_text, $to_segment_start, $to_segment_len)); + } + } + ksort($result, SORT_NUMERIC); + return array_values($result); + } + + /** + * Efficiently fragment the text into an array according to + * specified delimiters. + * No delimiters means fragment into single character. + * The array indices are the offset of the fragments into + * the input string. + * A sentinel empty fragment is always added at the end. + * Careful: No check is performed as to the validity of the + * delimiters. + */ + private static function extractFragments($text, $delimiters) { + // special case: split into characters + if ( empty($delimiters) ) { + $chars = str_split($text, 1); + $chars[strlen($text)] = ''; + return $chars; + } + $fragments = array(); + $start = $end = 0; + for (;;) { + $end += strcspn($text, $delimiters, $end); + $end += strspn($text, $delimiters, $end); + if ( $end === $start ) { + break; + } + $fragments[$start] = substr($text, $start, $end - $start); + $start = $end; + } + $fragments[$start] = ''; + return $fragments; + } + + /** + * Stock opcode renderers + */ + private static function renderToTextFromOpcode($opcode, $from, $from_offset, $from_len) { + if ( $opcode === 'c' || $opcode === 'i' ) { + echo substr($from, $from_offset, $from_len); + } + } + + private static function renderDiffToHTMLFromOpcode($opcode, $from, $from_offset, $from_len) { + if ( $opcode === 'c' ) { + echo htmlspecialchars(substr($from, $from_offset, $from_len)); + } + else if ( $opcode === 'd' ) { + $deletion = substr($from, $from_offset, $from_len); + if ( strcspn($deletion, " \n\r") === 0 ) { + $deletion = str_replace(array("\n","\r"), array('\n','\r'), $deletion); + } + echo '<del>', htmlspecialchars($deletion), '</del>'; + } + else /* if ( $opcode === 'i' ) */ { + echo '<ins>', htmlspecialchars(substr($from, $from_offset, $from_len)), '</ins>'; + } + } + } + diff --git a/interface/web/monitor/dataloghistory_list.php b/interface/web/monitor/dataloghistory_list.php new file mode 100644 index 0000000000000000000000000000000000000000..cab8da8a3671454d32c6598bde1a74e5a1e512b5 --- /dev/null +++ b/interface/web/monitor/dataloghistory_list.php @@ -0,0 +1,54 @@ +<?php + +/* +Copyright (c) 2008, Till Brehm, projektfarm Gmbh +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of ISPConfig nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY +OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +require_once '../../lib/config.inc.php'; +require_once '../../lib/app.inc.php'; + +/****************************************** +* Begin Form configuration +******************************************/ + +$list_def_file = "list/dataloghistory.list.php"; + +/****************************************** +* End Form configuration +******************************************/ + +//* Check permissions for module +$app->auth->check_module_permissions('monitor'); + +$app->uses('listform_actions'); + +$app->listform_actions->SQLOrderBy = "ORDER BY sys_datalog.tstamp DESC, sys_datalog.datalog_id DESC"; + +$app->listform_actions->onLoad(); + + +?> diff --git a/interface/web/monitor/dataloghistory_undo.php b/interface/web/monitor/dataloghistory_undo.php new file mode 100644 index 0000000000000000000000000000000000000000..5ff08faa0aa2a49fef1518c2262dc21861aad5d5 --- /dev/null +++ b/interface/web/monitor/dataloghistory_undo.php @@ -0,0 +1,70 @@ +<?php + +/* +Copyright (c) 2007-2008, Till Brehm, projektfarm Gmbh and Oliver Vogel www.muv.com +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of ISPConfig nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY +OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +require_once '../../lib/config.inc.php'; +require_once '../../lib/app.inc.php'; + +//* Check permissions for module +$app->auth->check_module_permissions('monitor'); + +// Loading the template +$app->uses('tpl'); +$app->tpl->newTemplate("form.tpl.htm"); +$app->tpl->setInclude('content_tpl', 'templates/dataloghistory_undo.htm'); + +require('lib/lang/'.$_SESSION['s']['language'].'_dataloghistory_undo.lng'); +$app->tpl->setvar($wb); + +$id = intval($_GET['id']); + +$record = $app->db->queryOneRecord('SELECT * FROM sys_datalog WHERE datalog_id = ?', $id); + +$dbidx = explode(':', $record['dbidx']); + +$old_record = $app->db->queryOneRecord('SELECT * FROM ?? WHERE ??=?', $record['dbtable'], $dbidx[0], $dbidx[1]); + +if (is_array($old_record)) { + if(!$data = unserialize(stripslashes($record['data']))) { + $data = unserialize($record['data']); + } + + $new_record = $data['old']; + + $app->db->datalogUpdate($record['dbtable'], $new_record, $dbidx[0], $dbidx[1]); + + $app->tpl->setVar('success', true); +} else { + $app->tpl->setVar('success', false); +} + +$app->tpl_defaults(); +$app->tpl->pparse(); + +?> diff --git a/interface/web/monitor/dataloghistory_view.php b/interface/web/monitor/dataloghistory_view.php new file mode 100644 index 0000000000000000000000000000000000000000..e3b5e7b63c24b89bfc7bf75b6607c4d549f545cb --- /dev/null +++ b/interface/web/monitor/dataloghistory_view.php @@ -0,0 +1,130 @@ +<?php + +/* +Copyright (c) 2007-2008, Till Brehm, projektfarm Gmbh and Oliver Vogel www.muv.com +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of ISPConfig nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY +OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +require_once '../../lib/config.inc.php'; +require_once '../../lib/app.inc.php'; + +//* Check permissions for module +$app->auth->check_module_permissions('monitor'); + +$app->load('finediff'); + +// Loading the template +$app->uses('tpl'); +$app->tpl->newTemplate("form.tpl.htm"); +$app->tpl->setInclude('content_tpl', 'templates/dataloghistory_view.htm'); + +$app->load_language_file('web/monitor/lib/lang/'.$_SESSION['s']['language'].'_dataloghistory_view.lng'); +require('lib/lang/'.$_SESSION['s']['language'].'_dataloghistory_view.lng'); +$app->tpl->setvar($wb); + +$id = intval($_GET['id']); + +$record = $app->db->queryOneRecord('SELECT * FROM sys_datalog WHERE datalog_id = ?', $id); + +$out['id'] = $id; + +$out['timestamp'] = date($app->lng('conf_format_datetime'), $record['tstamp']); +$out['table'] = $record['dbtable']; + +$out['action_char'] = $record['action']; +$out['action_name'] = $app->lng($record['action']); + +$out['session_id'] = $record['session_id']; + +if(!$data = unserialize(stripslashes($record['data']))) { + $data = unserialize($record['data']); +} + +switch ($record['action']) { + case 'i': + $inserts = array(); + foreach ($data['new'] as $key=>$value) { + $inserts[] = array( + 'key' => $key, + 'value' => nl2br($value), + ); + } + $app->tpl->setLoop('inserts', $inserts); + break; + case 'u': + $updates = array(); + foreach ($data['new'] as $key=>$value) { + if ($value != $data['old'][$key]) { + $old = $data['old'][$key]; + $new = $value; + $changes = show_diff_if_needed($old, $new); + $updates[] = array( + 'key' => $key, + 'is_diff' => $changes['is_diff'], + 'old' => nl2br($changes['old']), + 'new' => nl2br($changes['new']), + 'diff' => nl2br($changes['diff']), + ); + } + } + if (count($updates) > 0) { + $app->tpl->setLoop('updates', $updates); + } else { + $out['no_changes'] = true; + } + break; + case 'd': + $deletes = array(); + foreach ($data['old'] as $key=>$value) { + $deletes[] = array( + 'key' => $key, + 'value' => nl2br($value), + ); + } + $app->tpl->setLoop('deletes', $deletes); + break; +} + +$app->tpl->setVar($out); + +$app->tpl_defaults(); +$app->tpl->pparse(); + +function show_diff_if_needed($old, $new) { + global $app; + + $diff_min_lines = 6; + + if (substr_count($old, "\n") >= $diff_min_lines || substr_count($new, "\n") >= $diff_min_lines) { + $opcodes = FineDiff::getDiffOpcodes($old, $new); + $html = FineDiff::renderUTF8DiffToHTMLFromOpcodes($old, $opcodes); + return array('is_diff'=>true, 'old'=>'', 'new'=>'', 'diff'=>$html); + } else { + return array('is_diff'=>false, 'old'=>$old, 'new'=>$new, 'diff'=>''); + } +} + +?> diff --git a/interface/web/monitor/lib/lang/de_dataloghistory_list.lng b/interface/web/monitor/lib/lang/de_dataloghistory_list.lng new file mode 100644 index 0000000000000000000000000000000000000000..2c2b6c9fc2d41c11f5d37d11d691140229718b7a --- /dev/null +++ b/interface/web/monitor/lib/lang/de_dataloghistory_list.lng @@ -0,0 +1,8 @@ +<?php +$wb['list_head_txt'] = 'Datalog-History'; +$wb['tstamp_txt'] = 'Datum'; +$wb['server_id_txt'] = 'Server'; +$wb['dbtable_txt'] = 'Datenbanktabelle'; +$wb['action_txt'] = 'Aktion'; +$wb['status_txt'] = 'Status'; +?> diff --git a/interface/web/monitor/lib/lang/de_dataloghistory_undo.lng b/interface/web/monitor/lib/lang/de_dataloghistory_undo.lng new file mode 100644 index 0000000000000000000000000000000000000000..306f7c5eac0d7de9b3bd8b676a368af67a018fbc --- /dev/null +++ b/interface/web/monitor/lib/lang/de_dataloghistory_undo.lng @@ -0,0 +1,6 @@ +<?php +$wb['list_head_txt'] = 'Datalog-History-Eintrag'; +$wb['success_txt'] = 'Erfolgreich zurückgesetzt'; +$wb['error_txt'] = 'Fehler beim Zurücksetzen: Eintrag existiert nicht mehr'; +$wb['btn_cancel_txt'] = 'Zurück'; +?> diff --git a/interface/web/monitor/lib/lang/de_dataloghistory_view.lng b/interface/web/monitor/lib/lang/de_dataloghistory_view.lng new file mode 100644 index 0000000000000000000000000000000000000000..81123a69c0676d5163b68be44f347557bbd1086e --- /dev/null +++ b/interface/web/monitor/lib/lang/de_dataloghistory_view.lng @@ -0,0 +1,26 @@ +<?php +$wb['i'] = 'Insert'; +$wb['u'] = 'Update'; +$wb['d'] = 'Delete'; +$wb['list_head_txt'] = 'Datalog-History-Eintrag'; +$wb['id_txt'] = 'ID'; +$wb['timestamp_txt'] = 'Zeitpunkt'; +$wb['table_txt'] = 'Tabelle'; +$wb['action_txt'] = 'Aktion'; +$wb['session_id_txt'] = 'Session-ID'; +$wb['fields_txt'] = 'Felder'; +$wb['fields_inserted_txt'] = 'Eingefügte Felder'; +$wb['fields_updated_txt'] = 'Geänderte Felder'; +$wb['fields_deleted_txt'] = 'Gelöschte Felder'; +$wb['no_changes_txt'] = 'Keine Änderungen (Resync)'; +$wb['is_diff_txt'] = 'Die Änderungen werden hervorgehoben angezeigt'; +$wb['is_diff_inserts_txt'] = 'Einfügungen'; +$wb['is_diff_deletes_txt'] = 'Löschungen'; +$wb['field_txt'] = 'Feld'; +$wb['value_txt'] = 'Wert'; +$wb['old_txt'] = 'Alt'; +$wb['new_txt'] = 'Neu'; +$wb['btn_cancel_txt'] = 'Zurück'; +$wb['undo_txt'] = 'Rückgängig machen'; +$wb['undo_confirmation_txt'] = 'Soll diese Änderung wirklich rückgängig gemacht werden?'; +?> diff --git a/interface/web/monitor/lib/lang/en_dataloghistory_list.lng b/interface/web/monitor/lib/lang/en_dataloghistory_list.lng new file mode 100644 index 0000000000000000000000000000000000000000..9f9afd6347ba4443cc46796810fb1c4a24106ccf --- /dev/null +++ b/interface/web/monitor/lib/lang/en_dataloghistory_list.lng @@ -0,0 +1,8 @@ +<?php +$wb["list_head_txt"] = 'Datalog History'; +$wb["tstamp_txt"] = 'Date'; +$wb["server_id_txt"] = 'Server'; +$wb["dbtable_txt"] = 'DB Table'; +$wb["action_txt"] = 'Action'; +$wb["status_txt"] = 'Status'; +?> diff --git a/interface/web/monitor/lib/lang/en_dataloghistory_undo.lng b/interface/web/monitor/lib/lang/en_dataloghistory_undo.lng new file mode 100644 index 0000000000000000000000000000000000000000..258d8866966c781d0302373230bd7e5b2f0391b9 --- /dev/null +++ b/interface/web/monitor/lib/lang/en_dataloghistory_undo.lng @@ -0,0 +1,6 @@ +<?php +$wb['list_head_txt'] = 'Data Log History Entry'; +$wb['success_txt'] = 'Undo successful'; +$wb['error_txt'] = 'Error during undo: Record does not exist anymore'; +$wb['btn_cancel_txt'] = 'Back'; +?> diff --git a/interface/web/monitor/lib/lang/en_dataloghistory_view.lng b/interface/web/monitor/lib/lang/en_dataloghistory_view.lng new file mode 100644 index 0000000000000000000000000000000000000000..df9ddd286f46e816e06132e7465929ab8dd87229 --- /dev/null +++ b/interface/web/monitor/lib/lang/en_dataloghistory_view.lng @@ -0,0 +1,26 @@ +<?php +$wb['i'] = 'Insert'; +$wb['u'] = 'Update'; +$wb['d'] = 'Delete'; +$wb['list_head_txt'] = 'Data Log History Entry'; +$wb['id_txt'] = 'ID'; +$wb['timestamp_txt'] = 'Timestamp'; +$wb['table_txt'] = 'Table'; +$wb['action_txt'] = 'Action'; +$wb['session_id_txt'] = 'Session ID'; +$wb['fields_txt'] = 'Fields'; +$wb['fields_inserted_txt'] = 'Inserted Fields'; +$wb['fields_updated_txt'] = 'Updated Fields'; +$wb['fields_deleted_txt'] = 'Deleted Fields'; +$wb['no_changes_txt'] = 'No changes (re-sync)'; +$wb['is_diff_txt'] = 'The differences are highlighted'; +$wb['is_diff_inserts_txt'] = 'Insertions'; +$wb['is_diff_deletes_txt'] = 'Deletions'; +$wb['field_txt'] = 'Field'; +$wb['value_txt'] = 'Value'; +$wb['old_txt'] = 'Old'; +$wb['new_txt'] = 'New'; +$wb['btn_cancel_txt'] = 'Back'; +$wb['undo_txt'] = 'Undo action'; +$wb['undo_confirmation_txt'] = 'Do you really want to undo this action?'; +?> diff --git a/interface/web/monitor/list/dataloghistory.list.php b/interface/web/monitor/list/dataloghistory.list.php new file mode 100644 index 0000000000000000000000000000000000000000..1757125e8d2b33690f4bf7522513ca5c0eb3c653 --- /dev/null +++ b/interface/web/monitor/list/dataloghistory.list.php @@ -0,0 +1,91 @@ +<?php +/* + Datatypes: + - INTEGER + - DOUBLE + - CURRENCY + - VARCHAR + - TEXT + - DATE +*/ + +//* Name of list +$liste['name'] = 'dataloghistory'; + +//* Database table +$liste['table'] = 'sys_datalog'; + +//* Primary index column +$liste['table_idx'] = 'datalog_id'; + +//* Search Field Prefix +$liste['search_prefix'] = 'search_'; + +//* Records per page +$liste['records_per_page'] = "15"; + +//* Script file for listing +$liste['file'] = 'dataloghistory_list.php'; + +//* Script file to edit +$liste['edit_file'] = 'dataloghistory_list.php'; + +//* Script file to delete +$liste['delete_file'] = 'dataloghistory_list.php'; + +//* Paging template +$liste['paging_tpl'] = 'templates/paging.tpl.htm'; + +//* Enable auth +$liste['auth'] = 'no'; + + +/***************************************************** +* Suchfelder +*****************************************************/ + +$liste["item"][] = array( 'field' => "tstamp", + 'datatype' => "DATETIME", + 'formtype' => "TEXT", + 'op' => "like", + 'prefix' => "", + 'suffix' => "", + 'width' => "", + 'value' => ""); + + +$liste['item'][] = array( 'field' => 'server_id', + 'datatype' => 'INTEGER', + 'formtype' => 'SELECT', + 'op' => '=', + 'prefix' => '', + 'suffix' => '', + 'datasource' => array ( 'type' => 'SQL', + 'querystring' => 'SELECT server_id,server_name FROM server WHERE {AUTHSQL} ORDER BY server_name', + 'keyfield'=> 'server_id', + 'valuefield'=> 'server_name' + ), + 'width' => '', + 'value' => ''); + +$liste["item"][] = array( 'field' => "action", + 'datatype' => "VARCHAR", + 'formtype' => "SELECT", + 'op' => "=", + 'prefix' => "", + 'suffix' => "", + 'width' => "", + 'value' => array('i' => "Insert", 'u' => "Update", 'd' => "Delete")); + + +$liste["item"][] = array( 'field' => "dbtable", + 'datatype' => "VARCHAR", + 'formtype' => "TEXT", + 'op' => "like", + 'prefix' => "%", + 'suffix' => "%", + 'width' => "", + 'value' => ""); + + +?> diff --git a/interface/web/monitor/templates/dataloghistory_list.htm b/interface/web/monitor/templates/dataloghistory_list.htm new file mode 100644 index 0000000000000000000000000000000000000000..0cd3c4f4d27f52fa40977b3e00b80bf0c65cfbb4 --- /dev/null +++ b/interface/web/monitor/templates/dataloghistory_list.htm @@ -0,0 +1,54 @@ +<tmpl_if name="list_head_txt"> +<div class='page-header'> + <h1><tmpl_var name="list_head_txt"></h1> +</div> +</tmpl_if> +<tmpl_if name="list_desc_txt"><p><tmpl_var name="list_desc_txt"></p></tmpl_if> + + + <p class="fieldset-legend"><tmpl_var name="list_head_txt"></p> + <div class="table-wrapper marginTop15"> +<table class="table"> + <thead class="dark form-group-sm"> + <tr> + <th data-column="tstamp"><tmpl_var name="tstamp_txt"></th> + <th data-column="server_id"><tmpl_var name="server_id_txt"></th> + <th data-column="action"><tmpl_var name="action_txt"></th> + <th data-column="dbtable"><tmpl_var name="dbtable_txt"></th> + <th class="small-col text-right">{tmpl_var name='search_limit'}</th> + </tr> + <tr> + <td> </td> + <td><select class="form-control" name="search_server_id">{tmpl_var name='search_server_id'}</select></td> + <td><select class="form-control" name="search_action">{tmpl_var name='search_action'}</select></td> + <td><input class="form-control" type="text" name="search_dbtable" value="{tmpl_var name='search_dbtable'}" /></td> + <td class="text-right"> + <button type="button" class="btn btn-default formbutton-default formbutton-narrow" name="Filter" id="Filter" value="{tmpl_var name="filter_txt"}" data-submit-form="pageForm" data-form-action="monitor/dataloghistory_list.php"><span class="icon icon-filter"></span></button> + </td> + </tr> + </thead> + <tbody> + <tmpl_loop name="records"> + <tr> + <td><a href="#" data-load-content="monitor/dataloghistory_view.php?id={tmpl_var name='id'}">{tmpl_var name="tstamp"}</a></td> + <td><a href="#" data-load-content="monitor/dataloghistory_view.php?id={tmpl_var name='id'}">{tmpl_var name="server_id"}</a></td> + <td><a href="#" data-load-content="monitor/dataloghistory_view.php?id={tmpl_var name='id'}">{tmpl_var name="action"}</a></td> + <td><a href="#" data-load-content="monitor/dataloghistory_view.php?id={tmpl_var name='id'}">{tmpl_var name="dbtable"}</a></td> + <td class="text-right"></td> + </tr> + </tmpl_loop> + <tmpl_unless name="records"> + <tr class="tbl_row_noresults tbl_row_<tmpl_if name='__EVEN__'}even<tmpl_else>uneven</tmpl_if>"> + <td colspan="5">{tmpl_var name='globalsearch_noresults_text_txt'}</td> + </tr> + </tmpl_unless> + </tbody> + <tfoot> + <tr> + <td colspan="5"><tmpl_var name="paging"></td> + </tr> + </tfoot> + </table> +</div> + + </div> diff --git a/interface/web/monitor/templates/dataloghistory_undo.htm b/interface/web/monitor/templates/dataloghistory_undo.htm new file mode 100644 index 0000000000000000000000000000000000000000..997231a885ad3fb3e412658fabdd5ce7a62712de --- /dev/null +++ b/interface/web/monitor/templates/dataloghistory_undo.htm @@ -0,0 +1,17 @@ +<tmpl_if name="list_head_txt"> +<div class='page-header'> + <h1><tmpl_var name="list_head_txt"></h1> +</div> +</tmpl_if> + +<tmpl_if name='success'> + <tmpl_var name='success_txt'> +<tmpl_else> + <tmpl_var name='error_txt'> +</tmpl_if> + +<div class="clear"> + <div class="right"> + <button class="btn btn-default formbutton-default" type="button" value="{tmpl_var name='btn_cancel_txt'}" data-load-content="monitor/dataloghistory_list.php">{tmpl_var name='btn_cancel_txt'}</button> + </div> +</div> diff --git a/interface/web/monitor/templates/dataloghistory_view.htm b/interface/web/monitor/templates/dataloghistory_view.htm new file mode 100644 index 0000000000000000000000000000000000000000..0ce595e2652c90709d649bc0881210ca3856d5e0 --- /dev/null +++ b/interface/web/monitor/templates/dataloghistory_view.htm @@ -0,0 +1,124 @@ +<tmpl_if name="list_head_txt"> +<div class='page-header'> + <h1><tmpl_var name="list_head_txt"></h1> +</div> +</tmpl_if> +<div class="table-wrapper marginTop15"> + <table class="table"> + <thead class="dark form-group-sm"> + <tr> + <th width="25%"><tmpl_var name="id_txt"></th> + <th><tmpl_var name="id"></th> + </tr> + </thead> + <tbody> + <tr> + <td><tmpl_var name="timestamp_txt"></td> + <td><tmpl_var name="timestamp"></td> + </tr> + <tr> + <td><tmpl_var name="table_txt"></td> + <td><tmpl_var name="table"></td> + </tr> + <tr> + <td><tmpl_var name="action_txt"></td> + <td><tmpl_var name="action_name"></td> + </tr> + <tr> + <td><tmpl_var name="session_id_txt"></td> + <td><tmpl_var name="session_id"></td> + </tr> + </tbody> + </table> +</div> + +<tmpl_if name='action_char' value='i'> + <h2><tmpl_var name="fields_inserted_txt"></h2> +</tmpl_if> +<tmpl_if name='action_char' value='u'> + <h2><tmpl_var name="fields_updated_txt"></h2> +</tmpl_if> +<tmpl_if name='action_char' value='d'> + <h2><tmpl_var name="fields_deleted_txt"></h2> +</tmpl_if> + +<div class="table-wrapper marginTop15"> + <table class="table"> + <tmpl_if name='action_char' value='i'> + <thead class="dark form-group-sm"> + <tr> + <th width="25%"><tmpl_var name="field_txt"></th> + <th><tmpl_var name="value_txt"></th> + </tr> + </thead> + <tmpl_loop name='inserts'> + <tr> + <td><tmpl_var name="key"></td> + <td><tmpl_var name="value"></td> + </tr> + </tmpl_loop> + </tmpl_if> + <tmpl_if name='action_char' value='u'> + <tmpl_if name='no_changes'> + <tbody> + <tr> + <td class="text-center"><h3><tmpl_var name="no_changes_txt"></h3></td> + </tr> + </tbody> + <tmpl_else> + <thead class="dark form-group-sm"> + <tr> + <th width="25%"><tmpl_var name="field_txt"></th> + <th><tmpl_var name="old_txt"></th> + <th><tmpl_var name="new_txt"></th> + </tr> + </thead> + <tbody> + <tmpl_loop name='updates'> + <tmpl_if name='is_diff'> + <tr> + <td><tmpl_var name="key"></td> + <td colspan="2"> + <div class="alert alert-warning"><tmpl_var name="is_diff_txt"> (<span class="finediff"><ins><tmpl_var name="is_diff_inserts_txt"></ins></span> / <span class="finediff"><del><tmpl_var name="is_diff_deletes_txt"></del></span>)</div> + <div class="finediff"><tmpl_var name="diff"></div> + </td> + </tr> + <tmpl_else> + <tr> + <td><tmpl_var name="key"></td> + <td><tmpl_var name="old"></td> + <td><tmpl_var name="new"></td> + </tr> + </tmpl_if> + </tmpl_loop> + </tbody> + </tmpl_if> + </tmpl_if> + <tmpl_if name='action_char' value='d'> + <thead class="dark form-group-sm"> + <tr> + <th width="25%"><tmpl_var name="field_txt"></th> + <th><tmpl_var name="value_txt"></th> + </tr> + </thead> + <tbody> + <tmpl_loop name='deletes'> + <tr> + <td><tmpl_var name="key"></td> + <td><tmpl_var name="value"></td> + </tr> + </tmpl_loop> + </tbody> + </tmpl_if> + + </table> +</div> + +<div class="clear"> + <div class="right"> + <tmpl_if name='action_char' value='u'> + <a class="btn btn-default formbutton-danger formbutton-narrow" href="javascript: ISPConfig.confirm_action('monitor/dataloghistory_undo.php?id={tmpl_var name='id'}','{tmpl_var name='undo_confirmation_txt'}');"> {tmpl_var name='undo_txt'}</a> + </tmpl_if> + <button class="btn btn-default formbutton-default" type="button" value="{tmpl_var name='btn_cancel_txt'}" data-load-content="monitor/dataloghistory_list.php">{tmpl_var name='btn_cancel_txt'}</button> + </div> +</div>