getOne($query, [$page, $version]); } $contributionlib->remove_history($historyId); } $query = "delete from `tiki_history` where `pageName`=? and `version`=?"; $result = $this->query($query, [$page,$version]); $res = $this->version_exists($page, $version); if (! $res) { $logslib = TikiLib::lib('logs'); $logslib->add_action("Removed version", $page, 'wiki page', "version=$version"); //get_strings tra("Removed version $version") return true; } else { return false; } } public function use_version($page, $version, $comment = '') { $this->invalidate_cache($page); $query = "select * from `tiki_history` where `pageName`=? and `version`=?"; $result = $this->query($query, [$page,$version]); if (! $result->numRows()) { return false; } $res = $result->fetchRow(); global $prefs; // add both an optional, manual comment, and an automatic comment to existing one (after truncating if needed) if (trim($comment) <> '') { $comment = ". " . trim($comment); } $ver_comment = " [" . tr('Rollback by %0 to version %1', $GLOBALS['user'], $version) . $comment . "]"; $too_long = 200 - strlen($res["comment"] . $ver_comment); if ($too_long < 0) { $too_long -= 4; $res["comment"] = substr($res["comment"], 0, $too_long) . '...'; } $res["comment"] = $res["comment"] . $ver_comment; $query = "update `tiki_pages` set `data`=?,`lastModif`=?,`user`=?,`comment`=?,`version`=`version`+1,`ip`=?, `description`=?, `is_html`=?"; $bindvars = [$res['data'], $res['lastModif'], $res['user'], $res['comment'], $res['ip'], $res['description'], $res['is_html']]; // handle rolling back once page has been edited in a different editor (wiki or wysiwyg) based on is_html in history if ($prefs['feature_wysiwyg'] == 'y' && $prefs['wysiwyg_optional'] == 'y' && $prefs['wysiwyg_memo'] == 'y') { if ($res['is_html'] == 1) { // big hack: when you move to wysiwyg you do not come back usually -> wysiwyg should be a column in tiki_history $info = $this->get_hist_page_info($page); $bindvars[] = $info['wysiwyg']; } else { $bindvars[] = 'n'; } $query .= ', `wysiwyg`=?'; } $query .= ' where `pageName`=?'; $bindvars[] = $page; $result = $this->query($query, $bindvars); $query = "delete from `tiki_links` where `fromPage` = ?"; $result = $this->query($query, [$page]); $this->clear_links($page); $pages = TikiLib::lib('parser')->get_pages($res["data"], true); foreach ($pages as $a_page => $types) { $this->replace_link($page, $a_page, $types); } $this->replicate_page_to_history($page); global $prefs; if ($prefs['feature_actionlog'] == 'y') { $logslib = TikiLib::lib('logs'); $logslib->add_action("Rollback", $page, 'wiki page', "version=$version"); } //get_strings tra("Changed actual version to $version"); return true; } // Used to see a specific version of the page public function get_view_date($date_str) { global $tikilib; if (! $date_str) { // Date is undefined throw new Exception(); } $view_date = $date_str; $tsp = explode('-', $date_str); if (count($tsp) == 3) { // Date in YYYY-MM-DD format $view_date = $tikilib->make_time(23, 59, 59, $tsp[1] + 1, $tsp[2], $tsp[0] + 1900); } return $view_date; } public function get_user_versions($user) { $query = "select `pageName`,`version`, `lastModif`, `user`, `ip`, `comment` from `tiki_history` where `user`=? order by `lastModif` desc"; $result = $this->query($query, [$user]); $ret = []; while ($res = $result->fetchRow()) { $aux = []; $aux["pageName"] = $res["pageName"]; $aux["version"] = $res["version"]; $aux["lastModif"] = $res["lastModif"]; $aux["ip"] = $res["ip"]; $aux["comment"] = $res["comment"]; $ret[] = $aux; } return $ret; } // Returns information about a specific version of a page public function get_version($page, $version) { //fix for encoded slowly without doing it all at once in the installer upgrade script $wikilib = TikiLib::lib('wiki'); $converter = new convertToTiki9(); $converter->convertPageHistoryFromPageAndVersion($page, $version); $query = "select * from `tiki_history` where `pageName`=? and `version`=?"; $result = $this->query($query, [$page,$version]); $res = $result->fetchRow(); return $res; } // Get page info for a specified version public function get_hist_page_info($pageName, $version = null) { $info = parent::get_page_info($pageName); if (empty($version)) { // No version = last version return $info; } if (! $info) { // Page does not exist return false; } $old_info = $this->get_version($pageName, $version); if ($old_info == null) { // History does not exist if ($version == $this->get_page_latest_version($pageName) + 1) { // Last version return $info; } throw new Exception(); } // Override parameters with versioned data $info['data'] = $old_info['data']; $info['version'] = $old_info['version']; $info['last_version'] = $info['version']; $info["user"] = $old_info["user"]; $info["ip"] = $old_info["ip"]; $info["description"] = $old_info["description"]; $info["comment"] = $old_info["comment"]; $info["is_html"] = $old_info["is_html"]; $info['lastModif'] = $old_info["lastModif"]; $info['page_size'] = strlen($old_info['data']); return $info; } // Returns all the versions for this page // without the data itself public function get_page_history($page, $fetchdata = true, $offset = 0, $limit = -1) { global $prefs; $query = "select * from `tiki_history` where `pageName`=? order by `version` desc"; $result = $this->query($query, [$page], $limit, $offset); $ret = []; while ($res = $result->fetchRow()) { $aux = []; $aux["version"] = $res["version"]; $aux["lastModif"] = $res["lastModif"]; $aux["user"] = $res["user"]; $aux["ip"] = $res["ip"]; if ($fetchdata == true) { $aux["data"] = $res["data"]; } $aux["pageName"] = $res["pageName"]; $aux["description"] = $res["description"]; $aux["comment"] = $res["comment"]; $aux["is_html"] = $res["is_html"]; //$aux["percent"] = levenshtein($res["data"],$actual); if ($prefs['feature_contribution'] == 'y') { $contributionlib = TikiLib::lib('contribution'); $aux['contributions'] = $contributionlib->get_assigned_contributions($res['historyId'], 'history'); $logslib = TikiLib::lib('logs'); $aux['contributors'] = $logslib->get_wiki_contributors($aux); } $ret[] = $aux; } return $ret; } // Returns one version of the page from the history // without the data itself (version = 0 now returns data from current version) public function get_page_from_history($page, $version, $fetchdata = false) { $wikilib = TikiLib::lib('wiki'); $converter = new convertToTiki9(); $converter->convertPageHistoryFromPageAndVersion($page, $version); if ($fetchdata == true) { if ($version > 0) { $query = "select `pageName`, `description`, `version`, `lastModif`, `user`, `ip`, `data`, `comment`, `is_html` from `tiki_history` where `pageName`=? and `version`=?"; } else { $query = "select `pageName`, `description`, `version`, `lastModif`, `user`, `ip`, `data`, `comment`, `is_html` from `tiki_pages` where `pageName`=?"; } } else { if ($version > 0) { $query = "select `pageName`, `description`, `version`, `lastModif`, `user`, `ip`, `comment`, `is_html` from `tiki_history` where `pageName`=? and `version`=?"; } else { $query = "select `pageName`, `description`, `version`, `lastModif`, `user`, `ip`, `comment`, `is_html` from `tiki_pages` where `pageName`=?"; } } if ($version > 0) { $result = $this->query($query, [$page,$version]); } else { $result = $this->query($query, [$page]); } $ret = []; while ($res = $result->fetchRow()) { $aux = []; $aux["version"] = $res["version"]; $aux["lastModif"] = $res["lastModif"]; $aux["user"] = $res["user"]; $aux["ip"] = $res["ip"]; if ($fetchdata == true) { $aux["data"] = $res["data"]; } $aux["pageName"] = $res["pageName"]; $aux["description"] = $res["description"]; $aux["comment"] = $res["comment"]; $aux["is_html"] = $res["is_html"]; //$aux["percent"] = levenshtein($res["data"],$actual); $ret[] = $aux; } return empty($ret) ? $ret : $ret[0]; } /** * note that this function returns the latest but one version in the * history db table, which is one less than the current version * * @param string $page page name * @param string $sort_mode default version_desc * @return bool int */ public function get_page_latest_version($page, $sort_mode = 'version_desc') { $query = "select `version` from `tiki_history` where `pageName`=? order by " . $this->convertSortMode($sort_mode); $result = $this->query($query, [$page], 2); $ret = false; if ($res = $result->fetchRow()) { if ($res = $result->fetchRow()) { $ret = $res['version']; } } return $ret; } // Use largest version +1 in history table rather than tiki_page because versions used to be bugged // tiki_history is also bugged as not all changes get stored in the history, like minor changes // and changes that do not modify the body of the page. Both numbers are wrong, but the largest of // them both is right. // Also, it's possible that latest version in history has been rejected (with Flagged Revisions), // so the current version is not the biggest number. public function get_page_next_version($page, $willDoHistory = true) { $query = "select `version` from `tiki_history` where `pageName`=? order by `version` desc"; $result = $this->query($query, [$page], 1); $version = 0; if ($res = $result->fetchRow()) { $version = $res['version']; } $query = "select `version` from `tiki_pages` where `pageName`=?"; $result = $this->query($query, [$page], 1); if ($res = $result->fetchRow()) { $version = max($res['version'], $version); } return $version + ($willDoHistory ? 1 : 0); } public function version_exists($pageName, $version) { $query = "select `pageName` from `tiki_history` where `pageName` = ? and `version`=?"; $result = $this->query($query, [$pageName,$version]); return $result->numRows(); } // This function get the last changes from pages from the last $days days // if days is 0 this gets all the registers public function get_last_changes($days, $offset = 0, $limit = -1, $sort_mode = 'lastModif_desc', $findwhat = '', $repeat = false) { global $user; $bindvars = []; $categories = $this->get_jail(); if (! isset($categjoin)) { $categjoin = ''; } if ($categories) { $categjoin .= "inner join `tiki_objects` as tob on (tob.`itemId`= ta.`object` and tob.`type`= ?) inner join `tiki_category_objects` as tc on (tc.`catObjectId`=tob.`objectId` and tc.`categId` IN(" . implode(', ', array_fill(0, count($categories), '?')) . ")) "; $bindvars = array_merge(['wiki page'], $categories); } $where = "where true "; if ($findwhat) { $findstr = '%' . $findwhat . '%'; $where .= " and ta.`object` like ? or ta.`user` like ? or ta.`comment` like ?"; $bindvars = array_merge($bindvars, [$findstr,$findstr,$findstr]); } if ($days) { $toTime = $this->make_time(23, 59, 59, $this->date_format("%m"), $this->date_format("%d"), $this->date_format("%Y")); $fromTime = $toTime - (24 * 60 * 60 * $days); $where .= " and ta.`lastModif`>=? and ta.`lastModif`<=? "; $bindvars[] = $fromTime; $bindvars[] = $toTime; } $group_by = ""; if ($repeat) { $group_by = "group by thf.`page_id`"; } // WARNING: This assumes the current version of each page will be found in tiki_history $query = "select distinct ta.`action`, ta.`lastModif`, ta.`user`, ta.`ip`, ta.`object`, thf.`comment`, thf.`version`, thf.`page_id` from `tiki_actionlog` ta inner join (select th.`version`, th.`comment`, th.`pageName`, th.`lastModif`, tp.`page_id` from `tiki_history` as th LEFT OUTER JOIN `tiki_pages` tp ON tp.`pageName` = th.`pageName` AND tp.`version` = th.`version`) as thf on ta.`object`=thf.`pageName` and ta.`lastModif`=thf.`lastModif` and ta.`objectType`='wiki page' and ta.`action` <> 'Removed version' " . $categjoin . $where . $group_by . " order by ta." . $this->convertSortMode($sort_mode); // TODO: Optimize. This fetches all records just to be able to give a count. $result = Perms::filter([ 'type' => 'wiki page' ], 'object', $this->fetchAll($query, $bindvars), [ 'object' => 'object' ], 'view'); $cant = count($result); $ret = []; if ($limit == -1) { $result = array_slice($result, $offset); } else { $result = array_slice($result, $offset, $limit); } foreach ($result as $res) { $res['current'] = isset($res['page_id']); $res['pageName'] = $res['object']; $ret[] = $res; } return ['data' => $ret, 'cant' => $cant]; } public function get_nb_history($page) { $query_cant = "select count(*) from `tiki_history` where `pageName` = ?"; $cant = $this->getOne($query_cant, [$page]); return $cant; } // This function gets the version number of the version before or after the time specified // (note that current version is not included in search) public function get_version_by_time($page, $unixtimestamp, $before_or_after = 'before', $include_minor = true) { $query = "select `version`, `version_minor`, `lastModif` from `tiki_history` where `pageName`=? order by `version` desc"; $result = $this->query($query, [$page]); $ret = []; $version = 0; while ($res = $result->fetchRow()) { $aux = []; $aux["version"] = $res["version"]; $aux["version_minor"] = $res["version_minor"]; $aux["lastModif"] = $res["lastModif"]; $ret[] = $aux; } foreach ($ret as $ver) { if ($ver["lastModif"] <= $unixtimestamp && ($include_minor || $ver["version_minor"] == 0)) { if ($before_or_after == 'before') { $version = (int) $ver["version"]; break; } elseif ($before_or_after == 'after') { break; } } if ($before_or_after == 'after' && ($include_minor || $ver["version_minor"] == 0)) { $version = (int) $ver["version"]; } } return max(0, $version); } } /** * * This class represents a structured view (per word) on a document. Feeding it with additional references, it can be used to generate a * complete view of the document including changes made over time (like the "Track changes" in some word processing programs). A statistics * of the different authors contributions can be generated as well * * @author cdrwhite * @since 6.0 */ class Document { /** * @var array a list of words and whitespaces represented by an array(word,author,deleted,diffid,[deleted_by]) */ private $_document; /** * @var array array of statistical data grouped by author each represented by an array(words,deleted_words,whitespaces,deleted_whitespaces,characters,deleted_characters,printables,deleted_printables) * @see getStatistics */ private $_statistics; /** * @var array sum of all statistics for all authors, generated by getStatistics, retrieved by getTotal() * @see getTotal; */ private $_total; /** * @var string filter used in getStatistics to distinguish between characters and printable characters * @see getStatistics */ private $_filter; /** * @var int processing settings */ private $_process = 1; /** * @var bool should the page contents be parsed (HTML instead of WIKI text) */ private $_parsed; /** * @var bool should the html tags be stripped from the parsed contents */ private $_nohtml; /** * @var string start marker. If set, text before this marker (including the marker itself) will be removed */ public $startmarker = ''; /** * @var string end marker. If set, text after this marker (including the marker itself) will be removed */ public $endmarker = ''; /** * @var string regex for splitting page text into an array of words; */ private $_search = "#(\[[^\[].*?\]|\(\(.*?\)\)|(~np~\{.*?\}~/np~)|<[^>]+>|[,\"':\s]+|[^\s,\"':<]+|]+>)#"; /** * @var array Page info */ private $_info; /** * @var array complete page history */ private $_data; /** * * Initializing Internal variables for getStatistics and getTotals and adding the first page to the document * @param string $page Name of the page to include * @param int $lastversion >0 uses the version specified (or last page, if this is greater than the version of the last page) =0 uses the latest(current) version, <0 means a timestamp (lastModif has to be before that) * @param int $process 0 = don't parse (take original wiki text and count wiki tags/plugins), 1 = parse (take html as base), 2 = parse and strip html tags * @param string $start start marker (all text will be skipped, including this marker which must be at the beginning of a line) * @param string $end end marker (all text will be skipped from this marker on, including this marker which must be at the beginning of a line) */ public function __construct($page, $lastversion = 0, $process = 1, $showpopups = true, $startmarker = '', $endmarker = '') { $histlib = TikiLib::lib('hist'); $this->_document = []; $this->_history = false; $this->_filter = '/([[:blank:]]|[[:cntrl:]]|[[:punct:]]|[[:space:]])/'; $this->_parsed = true; $this->_nohtml = false; $this->_showpopups = $showpopups; switch ($process) { case 0: $this->_parsed = false; $this->_process = 0; break; case 2: $this->_nohtml = true; $this->_process = 2; break; } $this->startmarker = $startmarker; $this->endmarker = $endmarker; $this->_info = $histlib->get_hist_page_info($page, true); if ($lastversion == 0) { $lastversion = $this->_info['version']; } $this->_data = []; $this->_data = [[ 'version' => $this->_info['version'], 'lastModif' => $this->_info['lastModif'], 'user' => $this->_info['user'], 'ip' => $this->_info['ip'], 'pageName' => $page, 'description' => $this->_info['description'], 'comment' => $this->_info['comment'], 'data' => $this->_info['data'], ]]; $this->_data = array_merge($this->_data, $histlib->get_page_history($page, true, 0, -1)); $next = count($this->_data) - 1; $author = $this->_data[$next]['user']; $next = $this->getLastAuthorText($author, $next, $lastversion); if ($next == -1) { // all pages from the same author, no need to diff $index = $this->getIndex($lastversion); } else { $index = $next; } $source = $this->removeText($this->_data[$index]['data']); $source = preg_replace(['/\{AUTHOR\(.+?\)\}/','/{AUTHOR\}/','/\{INCLUDE\(.+?\)\}\{INCLUDE\}/'], ' ~np~$0~/np~', $source); if ($this->_parsed) { $source = TikiLib::lib('parser')->parse_data($source, ['suppress_icons' => true]); } if ($this->_nohtml) { $source = strip_tags($source); } preg_match_all($this->_search, $source, $out, PREG_PATTERN_ORDER); $words = $out[0]; $this->_document = $this->addWords($this->_document, $words, $author); if ($next == -1) { return; } do { $author = $this->_data[$next - 1]['user']; $next = $this->getLastAuthorText($author, $next - 1, $lastversion); if ($next == -1) { $index = $this->getIndex($lastversion); } else { $index = $next; } $newpage = $this->removeText($this->_data[$index]['data']); $this->mergeDiff($newpage, $author); } while ($next > 0); $this->parseAuthorAndInclude(); } /** * * Removes all text before the first occurrence of start marker and after the last occurrence of the end marker * This copies the original behaviour of the wikiplugin_include even though it could be done with a regex in fewer lines * @param string $text contains the whole text * @return string returns the text inside the markers */ private function removeText($text) { $start = ($this->startmarker != ''); $stop = ($this->endmarker != ''); if ($start || $stop) { $explText = explode("\n", $text); if ($start && $stop) { $state = 0; foreach ($explText as $i => $line) { if ($state == 0) { // Searching for start marker, dropping lines until found unset($explText[$i]); // Drop the line if (0 == strcmp($this->startmarker, trim($line))) { $state = 1; // Start retaining lines and searching for stop marker } } else { // Searching for stop marker, retaining lines until found if (0 == strcmp($this->endmarker, trim($line))) { unset($explText[$i]); // Stop marker, drop the line $state = 0; // Go back to looking for start marker } } } } elseif ($start) { // Only start marker is set. Search for it, dropping all lines until it is found. foreach ($explText as $i => $line) { unset($explText[$i]); // Drop the line if (0 == strcmp($this->startmarker, trim($line))) { break; } } } else { // Only stop marker is set. Search for it, dropping all lines after it is found. $state = 1; foreach ($explText as $i => $line) { if ($state == 0) { // Dropping lines unset($explText[$i]); } else { // Searching for stop marker, retaining lines until found if (0 == strcmp($this->endmarker, trim($line))) { unset($explText[$i]); // Stop marker, drop the line $state = 0; // Start dropping lines } } } } $text = implode("\n", $explText); } return $text; } /** * * get the id of the last text of the given author * @param string $author name of the current author * @param int $start start index * @param int $lastversion last version to check, assuming all versions, if none is provided * @return int id of the first text of a different author or -1 if there is none * @see get_page_history_all */ private function getLastAuthorText($author, $start = -1, $lastversion = -1) { if ($start == -1) { return $start; } if ($start < 0) { $start = count($this->_data) - 1; } if ($lastversion == -1) { $lastversion = $this->_data[0]['version']; } $i = $start; while ($i >= 0 and $this->_data[$i]['user'] == $author and $this->_data[$i]['version'] <= $lastversion) { $i--; } $i++; if ($this->_data[$i]['version'] >= $lastversion) { $i = -1; } return $i; } /** * * gets the index position of the requested version in the data array * @param int $version */ private function getIndex($version) { for ($i = count($this->_data) - 1; $i >= 0; $i--) { if ($this->_data[$i]['version'] == $version) { return $i; } } return -1; } /** * * returns the history (identical to $histlib->get_page_history, but saves another fetch from database as we already have the info */ public function getHistory() { return array_slice($this->_data, 1); } /** * * returns the page info history (identical to $tikilib->get_page_info, but saves another fetch from database as we already have the info */ public function getInfo() { return $this->_info; } /** * * Generates an array of words from the internal document structure, which can be used by the diff class. * The internal document structure will be modified to allow mergeDiff to integrate a new page with the current page without losing any information * @see mergeDiff * @return array list of words in the document (no author etc.) */ public function getDiffArray() { $diffarray = []; foreach ($this->_document as &$word) { if (! $word['deleted']) { $word['diffid'] = count($diffarray); $diffarray[] = $word['word']; } else { $word['diffid'] = -1; } } return $diffarray; } /** * * Generates a statistics per author, the totals can be retrieved via getTotal * @see getTotal * @param string $filter regex to filter out non printable characters (difference between characters and printables) * @return array array indexed by author containing arrays with statistics (words, deleted_words, whitespaces, deleted_whitespaces, characters, deleted_characters, printables, deleted_printables) */ public function getStatistics($filter = '/([[:blank:]]|[[:cntrl:]]|[[:punct:]]|[[:space:]])/') { $style = 0; if ($this->_filter != $filter) { //a new filter invalidates the statistics $this->_statistics = false; $this->_filter = $filter; } if ($this->_statistics != false) { return $this->_statistics; //there is already a history for the current state } $this->_statistics = []; $this->_total = [ 'words' => 0, 'deleted_words' => 0, 'whitespaces' => 0, 'deleted_whitespaces' => 0, 'characters' => 0, 'deleted_characters' => 0, 'printables' => 0, 'deleted_printables' => 0, ]; foreach ($this->_document as $word) { $author = $word['author']; if (! isset($this->_statistics[$author])) { $this->_statistics[$author] = [ 'words' => 0, 'words_percent' => 0, 'deleted_words' => 0, 'deleted_words_percent' => 0, 'whitespaces' => 0, 'whitespaces_percent' => 0, 'deleted_whitespaces' => 0, 'deleted_whitespaces_percent' => 0, 'characters' => 0, 'characters_percent' => 0, 'deleted_characters' => 0, 'deleted_characters_percent' => 0, 'printables' => 0, 'printables_percent' => 0, 'deleted_printables' => 0, 'deleted_printables_percent' => 0, 'style' => "author$style", ]; $style++; if ($style > 15) { $style = 0; } } //isset author if ($word['deleted']) { $prefix = 'deleted_'; } else { $prefix = ''; } $w = $word['word']; if ($this->_nohtml) { $w = strip_tags($w); } if (trim($w) == '') { $this->_statistics[$author][$prefix . 'whitespaces']++; $this->_total[$prefix . 'whitespaces']++; } else { $this->_statistics[$author][$prefix . 'words']++; $this->_total[$prefix . 'words']++; } $l = mb_strlen($w); $this->_statistics[$author][$prefix . 'characters'] += $l; $this->_total[$prefix . 'characters'] += $l; $l = mb_strlen(preg_replace($this->_filter, '', $w)); $this->_statistics[$author][$prefix . 'printables'] += $l; $this->_total[$prefix . 'printables'] += $l; } //foreach //calculate percentages foreach ($this->_statistics as &$author) { $author['words_percent'] = $author['words'] / $this->_total['words']; $author['deleted_words_percent'] = ($this->_total['deleted_words'] != 0 ? $author['deleted_words'] / $this->_total['deleted_words'] : 0); $author['whitespaces_percent'] = $author['whitespaces'] / $this->_total['whitespaces']; $author['deleted_whitespaces_percent'] = ($this->_total['deleted_whitespaces'] != 0 ? $author['deleted_whitespaces'] / $this->_total['deleted_whitespaces'] : 0); $author['characters_percent'] = $author['characters'] / $this->_total['characters']; $author['deleted_characters_percent'] = ($this->_total['deleted_characters'] != 0 ? $author['deleted_characters'] / $this->_total['deleted_characters'] : 0); $author['printables_percent'] = $author['printables'] / $this->_total['printables']; $author['deleted_printables_percent'] = ($this->_total['deleted_printables'] != 0 ? $author['deleted_printables'] / $this->_total['deleted_printables'] : 0); } return $this->_statistics; } /** * * gets the totals from a previous getStatistics call * @see getStatistics * @return array with statistics (words, deleted_words, whitespaces, deleted_whitespaces, characters, deleted_characters, printables, deleted_printables) */ public function getTotal() { return $this->_total; } /** * * Retrieves the document data in different formats, * @param string $type can be one of 'words' (array of words/whitespaces), 'text' (unformatted string), 'wiki' (string with wikiplugin AUTHOR tags to show the authors) or the default empty string '' (returns the internal document structure) * @param array $options array containing the filter specific options: * * * * *
TypeNameApplicable forPurpose
boolshowpopupswikirenders popups, defaults to true
boolescapetext/wikiEscapes brackets and htmlspecialchars
* @return array|string depending on the parameter $type, a string or array containing the documents words */ public function get($type = '', $options = []) { switch ($type) { case 'words': $words = []; foreach ($this->_document as $word) { $words[] = $word['word']; } return $words; break; case 'text': $text = ''; foreach ($this->_document as $word) { $text .= $word['word']; } return $text; if ($options['escape']) { if (! $this->_parsed) { $text = '~np~' . preg_replace(['/\~np\~/', '//\~\/np\~/'], ['~np~','~/np~;'], $text) . '~/np~'; } $text = preg_replace(['//'], ['<','>'], $text); } break; case 'wiki': $text = ''; $author = ''; $deleted = 0; $deleted_by = ''; if (isset($options['showpopups'])) { $showpopups = $options['showpopups']; } else { $showpopups = true; } foreach ($this->_document as $word) { $skip = false; $d = isset($word['deleted_by']) ? $word['deleted_by'] : ''; $w = $word['word']; if ($author != $word['author'] or $deleted != $word['deleted'] or $deleted_by != $d) { if ($text != '') { if ($options['escape']) { $text .= '~/np~'; } $text .= '{AUTHOR}'; } $author = $word['author']; $deleted = $word['deleted']; $deleted_by = $d; $text .= "{AUTHOR(author=\"$author\"" . ($deleted ? ",deleted_by=\"$deleted_by\"" : '') . ',visible="1"' . ($showpopups ? ', popup="1"' : '') . ')}'; if ($options['escape']) { $text .= "~np~"; } } if (! $options['escape']) { if ($this->_parsed and ! $this->_nohtml) { // skipping popups for links if (substr($w, 0, 3) == '') { $text .= $w . "{AUTHOR(author=\"$author\"" . ($deleted ? ",deleted_by=\"$deleted_by\"" : '') . ',visible="1", ' . ($showpopups ? ', popup="1"' : '') . ')}'; $skip = true; } } } else { //escape existing tags if (! $this->_parsed) { $w = preg_replace(['/\~np\~/', '/\~\/np\~/'], ['~np~','~/np~'], $w); } $w = preg_replace(['//'], ['&lt;','&gt;'], $w); //double encode! } if (strlen($w) == 0 and ! $this->_parsed) { $text .= "\n"; } else { if (! $skip) { $text .= $w; } } } // foreach if ($options['escape']) { $text .= "~/np~"; } $text .= "{AUTHOR}"; return $text; break; default: return $this->_document; } } /** * * Adds the supplied list of words to the provided document structure * @param array $doc a list of words (arrays containing word, author, deleted, diffid, optionally deleted_by and statistical data) where the new words will be added to * @param array $list array of words/whitespaces to add to the document * @param string $author name of the author to credit * @return provided document structure $doc with the words from $list appended */ private function addWords($doc, $list, $author, $deleted = false, $deleted_by = '') { $newdoc = $doc; foreach ($list as $word) { $newword = [ 'word' => $word, 'author' => $author, 'deleted' => $deleted, 'diffid' => -1, ]; if ($deleted) { $newword['deleted_by'] = $deleted_by; } $newdoc[] = $newword; } return $newdoc; } /** * * moves a nuber of words from the b eginning of this document to the provided document structure * @param array $doc a list of words (arrays containing word, author, deleted, diffid, optionally deleted_by and statistical data) where the new words will be appended to * @param int $pos number of characters to move from the current documents beginning to the new list, deleted words which have a negative diff id wille be moved but not counted * @param array $list list of words to move * @param bool $setDeleted mark the moved words as deleted, if not already deleted * @param string $deletedBy name of the author who deleted the words */ private function moveWords(&$doc, &$pos, $list, $deleted = false, $deleted_by = '') { $pos += count($list); // get the words from the old document $i = 0; while ($i < count($this->_document) and $this->_document[$i]['diffid'] < $pos) { $word = $this->_document[$i]; if ($deleted) { if (! $word['deleted']) { $word['deleted'] = true; $word['deleted_by'] = $deleted_by; } } $doc[] = $word; $i++; } //take care of deleted words while ($i < count($this->_document) and $this->_document[$i]['diffid'] < 0) { $word = $this->_document[$i]; $doc[] = $word; $i++; } $this->_document = array_slice($this->_document, $i); } /** * * Returns an indexed array containing the plugins parameters indexed by key name * @param string $pluginstr Complete Plugin tag including brackets () containing the parameters * @return array|bool Array containing the parameters or false if none are given */ public function retrieveParams($pluginstr) { $params = []; $start = strpos($pluginstr, '('); if ($start === false) { return false; } $end = strrpos($pluginstr, ')'); if ($end === false) { return false; } $pstr = substr($pluginstr, $start + 1, $end - $start - 1); $plist = explode(',', $pstr); foreach ($plist as $paramstr) { $p = explode('=', trim($paramstr)); $params[strtolower(trim($p[0]))] = preg_replace('/^"|^\"|"$|\"$/', '', trim($p[1])); } return $params; } /** * * merges a newer version of a page into the current document * @param string $newpage a string with a later version of the page * @param string $newauthor name of the author of the new version */ public function mergeDiff($newpage, $newauthor) { $this->_history = false; $author = $newauthor; $deleted = false; $deleted_by = ''; $newdoc = []; $page = preg_replace(['/\{AUTHOR\(.+?\)\}/','/{AUTHOR\}/','/\{INCLUDE\(.+?\)\}\{INCLUDE\}/'], ' ~np~$0~/np~', $newpage); if ($this->_parsed) { $page = TikiLib::lib('parser')->parse_data($page, ['suppress_icons' => true]); $page = preg_replace(['/\{AUTHOR\(.+?\)\}/','/{AUTHOR\}/','/\{INCLUDE\(.+?\)\}\{INCLUDE\}/'], ' ~np~$0~/np~', $page); } if ($this->_nohtml) { $page = strip_tags($page); } preg_match_all($this->_search, $page, $out, PREG_PATTERN_ORDER); $new = $out[0]; $z = new Text_Diff($this->getDiffArray(), $new); $pos = 0; foreach ($z->getDiff() as $element) { if (is_a($element, 'Text_Diff_Op_copy')) { $this->moveWords($newdoc, $pos, $element->orig, $deleted, $deleted_by); } else { if (is_a($element, 'Text_Diff_Op_add')) { $newdoc = $this->addWords($newdoc, $element->final, $author, $deleted, $deleted_by); } else { if (is_a($element, 'Text_Diff_Op_delete')) { $this->moveWords($newdoc, $pos, $element->orig, $deleted, $author); } else { //change $newdoc = $this->addWords($newdoc, $element->final, $author, $deleted, $deleted_by); $this->moveWords($newdoc, $pos, $element->orig, true, $author); } //delete } // add } // copy } // foreach diff $this->_document = $newdoc; } /** * * Kills double whitespaces in parseAuthor before/after {author} tags * @param array $newdoc array containing the new document * @param int $index position in the old document */ private function killDoubleWhitespaces(&$newdoc, &$index) { if (count($newdoc) > 2) { $w1 = $newdoc[count($newdoc) - 1]['word']; $w2 = $newdoc[count($newdoc) - 2]['word']; if ($this->_nohtml) { $w1 = strip_tags($w1); $w2 = strip_tags($w2); } if (trim($w1) == '' and trim($w2) == '') { array_pop($newdoc); // kill one of the whitespaces } } if ($index < count($this->_document) - 2) { $w1 = $this->_document[$index + 1]['word']; $w2 = $this->_document[$index + 2]['word']; if ($this->_nohtml) { $w1 = strip_tags($w1); $w2 = strip_tags($w2); } if (trim($w1) == '' and trim($w2) == '') { $index++; // jump over one of the whitespaces } } } /** * * parses the left over author/include tags and sets the author accordingly */ public function parseAuthorAndInclude() { $newdoc = []; $author = ''; $deleted_by = ''; for ($index = 0, $cdoc = count($this->_document); $index < $cdoc; $index++) { $word = $this->_document[$index]; if (preg_match('/\{AUTHOR\(.+?\)\}/', $word['word'])) { $params = $this->retrieveParams($word['word']); $author = $params['author']; if (isset($params['deleted_by'])) { $deleted_by = $params['deleted_by']; } // manage double whitespace before and after $this->killDoubleWhitespaces($newdoc, $index); } elseif (preg_match('/\{AUTHOR\}/', $word['word'])) { $author = ''; $deleted_by = ''; $this->killDoubleWhitespaces($newdoc, $index); } elseif (preg_match('/\{INCLUDE\(.+?\)\}\{INCLUDE\}/', $word['word'])) { $params = $this->retrieveParams($word['word']); $start = ''; $stop = ''; if (isset($params['start'])) { $start = $params['start']; } if (isset($params['stop'])) { $stop = $params['stop']; } $subdoc = new Document($params['page'], 0, $this->_process, $this->_showpopups, $start, $stop); $newdoc = array_merge($newdoc, $subdoc->get()); } else { //normal word if ($author != '') { $word['author'] = $author; } if ($deleted_by != '') { $word['deleted'] = true; $word['deleted_by'] = $deleted_by; } $newdoc[] = $word; } } //foreach $this->_document = $newdoc; } } function histlib_helper_setup_diff($page, $oldver, $newver, $diff_style = '',$current_ver=0) { global $prefs; $smarty = TikiLib::lib('smarty'); $histlib = TikiLib::lib('hist'); $tikilib = TikiLib::lib('tiki'); $prefs['wiki_edit_section'] = 'n'; $info = $tikilib->get_page_info($page); if ($oldver == 0 || $oldver == $info["version"]) { $old = & $info; $smarty->assign_by_ref('old', $info); } else { // fetch the required page from history, including its content while ($oldver > 0 && ! ($exists = $histlib->version_exists($page, $oldver) )) { --$oldver; } if ($exists) { $old = $histlib->get_page_from_history($page, $oldver, true); $smarty->assign_by_ref('old', $old); } } if ($newver == 0 || $newver >= $info["version"]) { $new =& $info; $smarty->assign_by_ref('new', $info); } else { // fetch the required page from history, including its content while ($newver > 0 && ! ($exists = $histlib->version_exists($page, $newver) )) { --$newver; } if ($exists) { $new = $histlib->get_page_from_history($page, $newver, true); $smarty->assign_by_ref('new', $new); } } // if ($current_ver == 0 || $current_ver >= $info["version"]) { $curver =& $info; $response=extractDataWikiDiff($info); $smarty->assign_by_ref('object_curver', $response); $smarty->assign_by_ref('curver', $info); } else { if ($exists) { $curver = $histlib->get_page_from_history($page, $current_ver, true); $response=extractDataWikiDiff($curver); $smarty->assign_by_ref('object_curver', $response); $smarty->assign_by_ref('curver', $curver); } } $oldver_mod = $oldver; if ($oldver == 0) { $oldver_mod = 1; } $query = "SELECT `comment`, `version` from `tiki_history` WHERE `pageName`=? and `version` BETWEEN ? AND ? ORDER BY `version` DESC"; $result = $histlib->query($query, [$page,$oldver_mod,$newver]); $diff_summaries = []; if ($oldver == 0) { $diff_summaries[] = $old['comment']; } while ($res = $result->fetchRow()) { $aux = []; $aux["comment"] = $res["comment"]; $aux["version"] = $res["version"]; $diff_summaries[] = $aux; } $smarty->assign('diff_summaries', $diff_summaries); if (empty($diff_style) || $diff_style == "old") { $diff_style = $prefs['default_wiki_diff_style']; } $smarty->assign('diff_style', $diff_style); $parserlib = TikiLib::lib('parser'); if ($diff_style == "sideview") { $old["data"] = $parserlib->parse_data($old["data"], ['preview_mode' => true]); $new["data"] = $parserlib->parse_data($new["data"], ['preview_mode' => true]); } else { require_once('lib/diff/difflib.php'); if ($info['is_html'] == 1 and $diff_style != "htmldiff") { $search[] = "~~"; $replace[] = "\n"; $search[] = "~<(hr|br) />~"; $replace[] = "\n"; $old['data'] = strip_tags(preg_replace($search, $replace, $old['data']), '

'); $new['data'] = strip_tags(preg_replace($search, $replace, $new['data']), '

'); } if ($diff_style == "htmldiff" && $old['is_html'] != 1) { $parse_options = ['is_html' => ($old['is_html'] == 1), 'noheadinc' => true, 'suppress_icons' => true, 'noparseplugins' => true]; $old["data"] = $parserlib->parse_data($old["data"], $parse_options); $new["data"] = $parserlib->parse_data($new["data"], $parse_options); $old['data'] = histlib_strip_irrelevant($old['data']); $new['data'] = histlib_strip_irrelevant($new['data']); } # If the user doesn't have permission to view # source, strip out all tiki-source-based comments global $tiki_p_wiki_view_source; if ($tiki_p_wiki_view_source != 'y') { $old["data"] = preg_replace(';~tc~(.*?)~/tc~;s', '', $old["data"]); $new["data"] = preg_replace(';~tc~(.*?)~/tc~;s', '', $new["data"]); } $html = diff2($old["data"], $new["data"], $diff_style); $smarty->assign_by_ref('diffdata', $html); } } function histlib_strip_irrelevant($data) { $data = preg_replace("/<(h1|h2|h3|h4|h5|h6|h7)\s+([^\\\\>]+)>/i", '<$1>', $data); return $data; } function extractDataWikiDiff(array $curver){ if(isset($curver["data"])){ $pattern = '/curver="y"/i'; return preg_match($pattern, $curver["data"]); } }