You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1300 lines
51 KiB

<?php
// (c) Copyright by authors of the Tiki Wiki CMS Groupware Project
//
// All Rights Reserved. See copyright.txt for details and a complete list of authors.
// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
// $Id$
class HistLib extends TikiLib
{
/*
* Removes a specific version of a page
*
*/
public function remove_version($page, $version, $historyId = '')
{
global $prefs;
if ($prefs['feature_contribution'] == 'y') {
$contributionlib = TikiLib::lib('contribution');
if ($historyId == '') {
$query = 'select `historyId` from `tiki_history` where `pageName`=? and `version`=?';
$historyId = $this->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:
* <table>
* <tr><th>Type</th><th>Name</th><th>Applicable for</th><th>Purpose</th></tr>
* <tr><td>bool</td><td>showpopups</td><td>wiki</td><td>renders popups, defaults to true</td></tr>
* <tr><td>bool</td><td>escape</td><td>text/wiki</td><td>Escapes brackets and htmlspecialchars</td></tr>
* </table>
* @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\~/'], ['&#126;np&#126;','&#126;/np&#126;;'], $text) .
'~/np~';
}
$text = preg_replace(['/</','/>/'], ['&lt;','&gt;'], $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) == '<a ') {
$text .= '{AUTHOR}';
}
if (substr($w, -4) == '</a>') {
$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\~/'], ['&#126;np&#126;','&#126;/np&#126;'], $w);
}
$w = preg_replace(['/</','/>/'], ['&amp;lt;','&amp;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('/^"|^\&quot;|"$|\&quot;$/', '', 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[] = "~</(table|td|th|div|p)>~";
$replace[] = "\n";
$search[] = "~<(hr|br) />~";
$replace[] = "\n";
$old['data'] = strip_tags(preg_replace($search, $replace, $old['data']), '<h1><h2><h3><h4><b><i><u><span>');
$new['data'] = strip_tags(preg_replace($search, $replace, $new['data']), '<h1><h2><h3><h4><b><i><u><span>');
}
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"]);
}
}