diff --git a/composer.json-dist b/composer.json-dist index dfee8815..2cf5bdd6 100644 --- a/composer.json-dist +++ b/composer.json-dist @@ -1,29 +1,30 @@ { "name": "kolab/plugins", "description": "For CI of Kolab plugins", "license": "AGPL-3.0+", "require": { "php": ">=7.2.0", - "caxy/php-htmldiff": "~0.1.7", + "caxy/php-htmldiff": "~0.1.15", + "lolli42/finediff": "~1.0.3", "pear/pear-core-minimal": "~1.10.1", "pear/net_socket": "~1.2.1", "pear/auth_sasl": "~1.1.0", "pear/net_idna2": "~0.2.0", "pear/mail_mime": "~1.10.0", "pear/net_smtp": "~1.7.1", "pear/net_ldap2": "~2.2.0", "pear/net_sieve": "~1.4.0", "pear/http_request2": "~2.5.0", "roundcube/rtf-html-php": "~2.1", "sabre/vobject": "~4.5.1", "kolab/net_ldap3": "dev-master", "spomky-labs/otphp": "~10.0.3", "endroid/qr-code": "~1.6.5", "enygma/yubikey": "~3.2" }, "require-dev": { "friendsofphp/php-cs-fixer": "^3.0", "phpstan/phpstan": "^1.2", "phpunit/phpunit": "^7 || ^9" } } diff --git a/plugins/libkolab/composer.json b/plugins/libkolab/composer.json index fb79f874..3a8c124e 100644 --- a/plugins/libkolab/composer.json +++ b/plugins/libkolab/composer.json @@ -1,33 +1,34 @@ { "name": "kolab/libkolab", "type": "roundcube-plugin", "description": "Plugin to setup a basic environment for the interaction with a Kolab server.", "homepage": "https://git.kolab.org/diffusion/RPK/", "license": "AGPLv3", "version": "3.5.11", "authors": [ { "name": "Thomas Bruederli", "email": "bruederli@kolabsys.com", "role": "Lead" }, { "name": "Aleksander Machniak", "email": "machniak@kolabsys.com", "role": "Developer" } ], "repositories": [ { "type": "composer", "url": "https://plugins.roundcube.net" } ], "require": { "php": ">=7.2.0", "roundcube/plugin-installer": ">=0.1.3", "kolab/libcalendaring": ">=3.4.0", "pear/http_request2": "~2.5.0", - "caxy/php-htmldiff": "~0.1.7" + "caxy/php-htmldiff": "~0.1.15", + "lolli42/finediff": "~1.0.3" } } diff --git a/plugins/libkolab/libkolab.php b/plugins/libkolab/libkolab.php index ae2d82a0..96863be5 100644 --- a/plugins/libkolab/libkolab.php +++ b/plugins/libkolab/libkolab.php @@ -1,392 +1,387 @@ * * Copyright (C) 2012-2015, Kolab Systems AG * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ class libkolab extends rcube_plugin { public static $http_requests = []; public static $bonnie_api = false; /** * Required startup method of a Roundcube plugin */ public function init() { // load local config $this->load_config(); $this->require_plugin('libcalendaring'); // extend include path to load bundled lib classes $include_path = $this->home . '/lib' . PATH_SEPARATOR . ini_get('include_path'); set_include_path($include_path); $this->add_hook('storage_init', [$this, 'storage_init']); $this->add_hook('storage_connect', [$this, 'storage_connect']); $this->add_hook('user_delete', ['kolab_storage', 'delete_user_folders']); // For Chwala $this->add_hook('folder_mod', ['kolab_storage', 'folder_mod']); $rcmail = rcube::get_instance(); try { kolab_format::$timezone = new DateTimeZone($rcmail->config->get('timezone', 'GMT')); } catch (Exception $e) { rcube::raise_error($e, true); kolab_format::$timezone = new DateTimeZone('GMT'); } $this->add_texts('localization/', false); if (!empty($rcmail->output->type) && $rcmail->output->type == 'html') { // @phpstan-ignore-next-line $rcmail->output->add_handler('libkolab.folder_search_form', [$this, 'folder_search_form']); $this->include_stylesheet($this->local_skin_path() . '/libkolab.css'); } // embed scripts and templates for email message audit trail if (property_exists($rcmail, 'task') && $rcmail->task == 'mail' && self::get_bonnie_api()) { if (!empty($rcmail->output->type) && $rcmail->output->type == 'html') { $this->add_hook('render_page', [$this, 'bonnie_render_page']); $this->include_script('libkolab.js'); // add 'Show history' item to message menu $this->api->add_content( html::tag( 'li', ['role' => 'menuitem'], $this->api->output->button([ 'command' => 'kolab-mail-history', 'label' => 'libkolab.showhistory', 'type' => 'link', 'classact' => 'icon history active', 'class' => 'icon history disabled', 'innerclass' => 'icon history', ]) ), 'messagemenu' ); } $this->register_action('plugin.message-changelog', [$this, 'message_changelog']); } } /** * Hook into IMAP FETCH HEADER.FIELDS command and request Kolab-specific headers */ public function storage_init($p) { $kolab_headers = 'X-KOLAB-TYPE X-KOLAB-MIME-VERSION MESSAGE-ID'; if (!empty($p['fetch_headers'])) { $p['fetch_headers'] .= ' ' . $kolab_headers; } else { $p['fetch_headers'] = $kolab_headers; } return $p; } /** * Hook into IMAP connection to replace client identity */ public function storage_connect($p) { $client_name = 'Roundcube/Kolab'; if (empty($p['ident'])) { $p['ident'] = [ 'name' => $client_name, 'version' => RCUBE_VERSION, /* 'php' => PHP_VERSION, 'os' => PHP_OS, 'command' => $_SERVER['REQUEST_URI'], */ ]; } else { $p['ident']['name'] = $client_name; } return $p; } /** * Getter for a singleton instance of the Bonnie API * * @return mixed kolab_bonnie_api instance if configured, false otherwise */ public static function get_bonnie_api() { // get configuration for the Bonnie API if (!self::$bonnie_api && ($bonnie_config = rcube::get_instance()->config->get('kolab_bonnie_api', false))) { self::$bonnie_api = new kolab_bonnie_api($bonnie_config); } return self::$bonnie_api; } /** * Hook to append the message history dialog template to the mail view */ public function bonnie_render_page($p) { if (($p['template'] === 'mail' || $p['template'] === 'message') && !$p['kolab-audittrail']) { // append a template for the audit trail dialog $this->api->output->add_footer( html::div( ['id' => 'mailmessagehistory', 'class' => 'uidialog', 'aria-hidden' => 'true', 'style' => 'display:none'], self::object_changelog_table(['class' => 'records-table changelog-table']) ) ); $this->api->output->set_env('kolab_audit_trail', true); $p['kolab-audittrail'] = true; } return $p; } /** * Handler for message audit trail changelog requests */ public function message_changelog() { if (!self::$bonnie_api) { return false; } $rcmail = rcmail::get_instance(); $msguid = rcube_utils::get_input_value('_uid', rcube_utils::INPUT_POST, true); $mailbox = rcube_utils::get_input_value('_mbox', rcube_utils::INPUT_POST); $result = $msguid && $mailbox ? self::$bonnie_api->changelog('mail', null, $mailbox, $msguid) : null; if (is_array($result)) { if (is_array($result['changes'])) { $dtformat = $rcmail->config->get('date_format') . ' ' . $rcmail->config->get('time_format'); array_walk($result['changes'], function (&$change) use ($dtformat, $rcmail) { if ($change['date']) { $dt = rcube_utils::anytodatetime($change['date']); if ($dt instanceof DateTimeInterface) { $change['date'] = $rcmail->format_date($dt, $dtformat); } } }); } $this->api->output->command('plugin.message_render_changelog', $result['changes']); } else { $this->api->output->command('plugin.message_render_changelog', false); } $this->api->output->send(); } /** * Wrapper function to load and initalize the HTTP_Request2 Object * * @param string|Net_URL2 $url Request URL * @param string $method Request method ('OPTIONS','GET','HEAD','POST','PUT','DELETE','TRACE','CONNECT') * @param array $config Configuration for this Request instance, that will be merged * with default configuration * * @return HTTP_Request2 Request object */ public static function http_request($url = '', $method = 'GET', $config = []) { $rcube = rcube::get_instance(); $http_config = (array) $rcube->config->get('kolab_http_request'); // deprecated configuration options if (empty($http_config)) { foreach (['ssl_verify_peer', 'ssl_verify_host'] as $option) { $value = $rcube->config->get('kolab_' . $option, true); if (is_bool($value)) { $http_config[$option] = $value; } } } if (!empty($config)) { $http_config = array_merge($http_config, $config); } // force CURL adapter, this allows to handle correctly // compressed responses with SplObserver registered (kolab_files) (#4507) $http_config['adapter'] = 'HTTP_Request2_Adapter_Curl'; $key = md5(serialize($http_config)); if (!empty(self::$http_requests[$key])) { $request = self::$http_requests[$key]; } else { // load HTTP_Request2 (support both composer-installed and system-installed package) if (!class_exists('HTTP_Request2')) { require_once 'HTTP/Request2.php'; } try { $request = new HTTP_Request2(); $request->setConfig($http_config); } catch (Exception $e) { rcube::raise_error($e, true, true); } // proxy User-Agent string $request->setHeader('user-agent', $_SERVER['HTTP_USER_AGENT']); self::$http_requests[$key] = $request; } // cleanup try { $request->setBody(''); $request->setUrl($url); $request->setMethod($method); } catch (Exception $e) { rcube::raise_error($e, true, true); } return $request; } /** * Table oultine for object changelog display */ public static function object_changelog_table($attrib = []) { $rcube = rcmail::get_instance(); $attrib += ['domain' => 'libkolab']; $table = new html_table(['cols' => 5, 'border' => 0, 'cellspacing' => 0]); $table->add_header('diff', ''); $table->add_header('revision', $rcube->gettext('revision', $attrib['domain'])); $table->add_header('date', $rcube->gettext('date', $attrib['domain'])); $table->add_header('user', $rcube->gettext('user', $attrib['domain'])); $table->add_header('operation', $rcube->gettext('operation', $attrib['domain'])); $table->add_header('actions', ' '); $rcube->output->add_label( 'libkolab.showrevision', 'libkolab.actionreceive', 'libkolab.actionappend', 'libkolab.actionmove', 'libkolab.actiondelete', 'libkolab.actionread', 'libkolab.actionflagset', 'libkolab.actionflagclear', 'libkolab.objectchangelog', 'libkolab.objectchangelognotavailable', 'close' ); return $table->show($attrib); } /** * Wrapper function for generating a html diff using the FineDiff class by Raymond Hill */ public static function html_diff($from, $to, $is_html = null) { // auto-detect text/html format if ($is_html === null) { - $from_html = (preg_match('/<(html|body)(\s+[a-z]|>)/', $from, $m) && strpos($from, '') > 0); - $to_html = (preg_match('/<(html|body)(\s+[a-z]|>)/', $to, $m) && strpos($to, '') > 0); + $from_html = preg_match('/<(html|body)(\s+[a-z]|>)/', $from, $m) && strpos($from, '') > 0; + $to_html = preg_match('/<(html|body)(\s+[a-z]|>)/', $to, $m) && strpos($to, '') > 0; $is_html = $from_html || $to_html; // ensure both parts are of the same format if ($is_html && !$from_html) { $converter = new rcube_text2html($from, false, ['wrap' => true]); $from = $converter->get_html(); } if ($is_html && !$to_html) { $converter = new rcube_text2html($to, false, ['wrap' => true]); $to = $converter->get_html(); } } // compute diff from HTML if ($is_html) { - include_once __dir__ . '/vendor/Caxy/HtmlDiff/Match.php'; - include_once __dir__ . '/vendor/Caxy/HtmlDiff/Operation.php'; - include_once __dir__ . '/vendor/Caxy/HtmlDiff/HtmlDiff.php'; - // replace data: urls with a transparent image to avoid memory problems - $from = preg_replace('/src="data:image[^"]+/', 'src="data:image/gif;base64,R0lGODlhAQABAPAAAOjq6gAAACH/C1hNUCBEYXRhWE1QAT8AIfkEBQAAAAAsAAAAAAEAAQAAAgJEAQA7', $from); - $to = preg_replace('/src="data:image[^"]+/', 'src="data:image/gif;base64,R0lGODlhAQABAPAAAOjq6gAAACH/C1hNUCBEYXRhWE1QAT8AIfkEBQAAAAAsAAAAAAEAAQAAAgJEAQA7', $to); + $src = 'src="data:image/gif;base64,R0lGODlhAQABAPAAAOjq6gAAACH/C1hNUCBEYXRhWE1QAT8AIfkEBQAAAAAsAAAAAAEAAQAAAgJEAQA7'; + $from = preg_replace('/src="data:image[^"]+/', $src, $from); + $to = preg_replace('/src="data:image[^"]+/', $src, $to); $diff = new Caxy\HtmlDiff\HtmlDiff($from, $to); $diffhtml = $diff->build(); // remove empty inserts (from tables) return preg_replace('!\s*!Uims', '', $diffhtml); } else { - include_once __dir__ . '/vendor/finediff.php'; - - $diff = new FineDiff($from, $to, FineDiff::$wordGranularity); - return $diff->renderDiffToHTML(); + $diff = new cogpowered\FineDiff\Diff(new cogpowered\FineDiff\Granularity\Word()); + return $diff->render($from, $to); } } /** * Return a date() format string to render identifiers for recurrence instances * * @param array $event Hash array with event properties * * @return string Format string */ public static function recurrence_id_format($event) { return !empty($event['allday']) ? 'Ymd' : 'Ymd\THis'; } /** * Returns HTML code for folder search widget * * @param array $attrib Named parameters * * @return string HTML code for the gui object */ public function folder_search_form($attrib) { $rcmail = rcmail::get_instance(); $attrib += [ 'gui-object' => false, 'wrapper' => true, 'form-name' => 'foldersearchform', 'command' => 'non-extsing-command', 'reset-command' => 'non-existing-command', ]; if (($attrib['label-domain'] ?? null) && !strpos($attrib['buttontitle'], '.')) { $attrib['buttontitle'] = $attrib['label-domain'] . '.' . $attrib['buttontitle']; } if ($attrib['buttontitle']) { $attrib['placeholder'] = $rcmail->gettext($attrib['buttontitle']); } return $rcmail->output->search_form($attrib); } } diff --git a/plugins/libkolab/vendor/Caxy/HtmlDiff/HtmlDiff.php b/plugins/libkolab/vendor/Caxy/HtmlDiff/HtmlDiff.php deleted file mode 100644 index 11cc31bb..00000000 --- a/plugins/libkolab/vendor/Caxy/HtmlDiff/HtmlDiff.php +++ /dev/null @@ -1,629 +0,0 @@ -oldText = $this->purifyHtml(trim($oldText)); - $this->newText = $this->purifyHtml(trim($newText)); - $this->encoding = $encoding; - $this->content = ''; - $this->groupDiffs = $groupDiffs; - $this->setSpecialCaseTags($specialCaseTags); - $this->setSpecialCaseChars(static::$defaultSpecialCaseChars); - } - - /** - * @param boolean $boolean - * @return HtmlDiff - */ - public function setInsertSpaceInReplace($boolean) - { - $this->insertSpaceInReplace = $boolean; - - return $this; - } - - /** - * @return boolean - */ - public function getInsertSpaceInReplace() - { - return $this->insertSpaceInReplace; - } - - public function setSpecialCaseChars(array $chars) - { - $this->specialCaseChars = $chars; - } - - public function getSpecialCaseChars() - { - return $this->specialCaseChars; - } - - public function addSpecialCaseChar($char) - { - if (!in_array($char, $this->specialCaseChars)) { - $this->specialCaseChars[] = $char; - } - } - - public function removeSpecialCaseChar($char) - { - $key = array_search($char, $this->specialCaseChars); - if ($key !== false) { - unset($this->specialCaseChars[$key]); - } - } - - public function setSpecialCaseTags(array $tags = array()) - { - $this->specialCaseTags = $tags; - - foreach ($this->specialCaseTags as $tag) { - $this->addSpecialCaseTag($tag); - } - } - - public function addSpecialCaseTag($tag) - { - if (!in_array($tag, $this->specialCaseTags)) { - $this->specialCaseTags[] = $tag; - } - - $opening = $this->getOpeningTag($tag); - $closing = $this->getClosingTag($tag); - - if (!in_array($opening, $this->specialCaseOpeningTags)) { - $this->specialCaseOpeningTags[] = $opening; - } - if (!in_array($closing, $this->specialCaseClosingTags)) { - $this->specialCaseClosingTags[] = $closing; - } - } - - public function removeSpecialCaseTag($tag) - { - if (($key = array_search($tag, $this->specialCaseTags)) !== false) { - unset($this->specialCaseTags[$key]); - - $opening = $this->getOpeningTag($tag); - $closing = $this->getClosingTag($tag); - - if (($key = array_search($opening, $this->specialCaseOpeningTags)) !== false) { - unset($this->specialCaseOpeningTags[$key]); - } - if (($key = array_search($closing, $this->specialCaseClosingTags)) !== false) { - unset($this->specialCaseClosingTags[$key]); - } - } - } - - public function getSpecialCaseTags() - { - return $this->specialCaseTags; - } - - public function getOldHtml() - { - return $this->oldText; - } - - public function getNewHtml() - { - return $this->newText; - } - - public function getDifference() - { - return $this->content; - } - - public function setGroupDiffs($boolean) - { - $this->groupDiffs = $boolean; - } - - public function isGroupDiffs() - { - return $this->groupDiffs; - } - - protected function getOpeningTag($tag) - { - return "/<".$tag."[^>]*/i"; - } - - protected function getClosingTag($tag) - { - return ""; - } - - protected function getStringBetween($str, $start, $end) - { - $expStr = explode( $start, $str, 2 ); - if ( count( $expStr ) > 1 ) { - $expStr = explode( $end, $expStr[ 1 ] ); - if ( count( $expStr ) > 1 ) { - array_pop( $expStr ); - - return implode( $end, $expStr ); - } - } - - return ''; - } - - protected function purifyHtml($html, $tags = null) - { - if ( class_exists( 'Tidy' ) && false ) { - $config = array( 'output-xhtml' => true, 'indent' => false ); - $tidy = new tidy; - $tidy->parseString( $html, $config, 'utf8' ); - $html = (string) $tidy; - - return $this->getStringBetween( $html, '' ); - } - - return $html; - } - - public function build() - { - $this->splitInputsToWords(); - $this->indexNewWords(); - $operations = $this->operations(); - foreach ($operations as $item) { - $this->performOperation( $item ); - } - - return $this->content; - } - - protected function indexNewWords() - { - $this->wordIndices = array(); - foreach ($this->newWords as $i => $word) { - if ( $this->isTag( $word ) ) { - $word = $this->stripTagAttributes( $word ); - } - if ( isset( $this->wordIndices[ $word ] ) ) { - $this->wordIndices[ $word ][] = $i; - } else { - $this->wordIndices[ $word ] = array( $i ); - } - } - } - - protected function splitInputsToWords() - { - $this->oldWords = $this->convertHtmlToListOfWords( $this->explode( $this->oldText ) ); - $this->newWords = $this->convertHtmlToListOfWords( $this->explode( $this->newText ) ); - } - - protected function isPartOfWord($text) - { - return ctype_alnum(str_replace($this->specialCaseChars, '', $text)); - } - - protected function convertHtmlToListOfWords($characterString) - { - $mode = 'character'; - $current_word = ''; - $words = array(); - foreach ($characterString as $i => $character) { - switch ($mode) { - case 'character': - if ( $this->isStartOfTag( $character ) ) { - if ($current_word != '') { - $words[] = $current_word; - } - $current_word = "<"; - $mode = 'tag'; - } elseif ( preg_match( "[^\s]", $character ) > 0 ) { - if ($current_word != '') { - $words[] = $current_word; - } - $current_word = $character; - $mode = 'whitespace'; - } else { - if ( - (ctype_alnum($character) && (strlen($current_word) == 0 || $this->isPartOfWord($current_word))) || - (in_array($character, $this->specialCaseChars) && isset($characterString[$i+1]) && $this->isPartOfWord($characterString[$i+1])) - ) { - $current_word .= $character; - } else { - $words[] = $current_word; - $current_word = $character; - } - } - break; - case 'tag' : - if ( $this->isEndOfTag( $character ) ) { - $current_word .= ">"; - $words[] = $current_word; - $current_word = ""; - - if ( !preg_match('[^\s]', $character ) ) { - $mode = 'whitespace'; - } else { - $mode = 'character'; - } - } else { - $current_word .= $character; - } - break; - case 'whitespace': - if ( $this->isStartOfTag( $character ) ) { - if ($current_word != '') { - $words[] = $current_word; - } - $current_word = "<"; - $mode = 'tag'; - } elseif ( preg_match( "[^\s]", $character ) ) { - $current_word .= $character; - } else { - if ($current_word != '') { - $words[] = $current_word; - } - $current_word = $character; - $mode = 'character'; - } - break; - default: - break; - } - } - if ($current_word != '') { - $words[] = $current_word; - } - - return $words; - } - - protected function isStartOfTag($val) - { - return $val == "<"; - } - - protected function isEndOfTag($val) - { - return $val == ">"; - } - - protected function isWhiteSpace($value) - { - return !preg_match( '[^\s]', $value ); - } - - protected function explode($value) - { - // as suggested by @onassar - return preg_split( '//u', $value ); - } - - protected function performOperation($operation) - { - switch ($operation->action) { - case 'equal' : - $this->processEqualOperation( $operation ); - break; - case 'delete' : - $this->processDeleteOperation( $operation, "diffdel" ); - break; - case 'insert' : - $this->processInsertOperation( $operation, "diffins"); - break; - case 'replace': - $this->processReplaceOperation( $operation ); - break; - default: - break; - } - } - - protected function processReplaceOperation($operation) - { - $processDelete = strlen($this->oldText) > 0; - $processInsert = strlen($this->newText) > 0; - - if ($processDelete) { - $this->processDeleteOperation( $operation, "diffmod" ); - } - - if ($this->insertSpaceInReplace && $processDelete && $processInsert) { - $this->content .= ' '; - } - - if ($processInsert) { - $this->processInsertOperation( $operation, "diffmod" ); - } - } - - protected function processInsertOperation($operation, $cssClass) - { - $text = array(); - foreach ($this->newWords as $pos => $s) { - if ($pos >= $operation->startInNew && $pos < $operation->endInNew) { - $text[] = $s; - } - } - $this->insertTag( "ins", $cssClass, $text ); - } - - protected function processDeleteOperation($operation, $cssClass) - { - $text = array(); - foreach ($this->oldWords as $pos => $s) { - if ($pos >= $operation->startInOld && $pos < $operation->endInOld) { - $text[] = $s; - } - } - $this->insertTag( "del", $cssClass, $text ); - } - - protected function processEqualOperation($operation) - { - $result = array(); - foreach ($this->newWords as $pos => $s) { - if ($pos >= $operation->startInNew && $pos < $operation->endInNew) { - $result[] = $s; - } - } - $this->content .= implode( "", $result ); - } - - protected function insertTag($tag, $cssClass, &$words) - { - while (true) { - if ( count( $words ) == 0 ) { - break; - } - - $nonTags = $this->extractConsecutiveWords( $words, 'noTag' ); - - $specialCaseTagInjection = ''; - $specialCaseTagInjectionIsBefore = false; - - if ( count( $nonTags ) != 0 ) { - $text = $this->wrapText( implode( "", $nonTags ), $tag, $cssClass ); - $this->content .= $text; - } else { - $firstOrDefault = false; - foreach ($this->specialCaseOpeningTags as $x) { - if ( preg_match( $x, $words[ 0 ] ) ) { - $firstOrDefault = $x; - break; - } - } - if ($firstOrDefault) { - $specialCaseTagInjection = ''; - if ($tag == "del") { - unset( $words[ 0 ] ); - } - } elseif ( array_search( $words[ 0 ], $this->specialCaseClosingTags ) !== false ) { - $specialCaseTagInjection = ""; - $specialCaseTagInjectionIsBefore = true; - if ($tag == "del") { - unset( $words[ 0 ] ); - } - } - } - if ( count( $words ) == 0 && count( $specialCaseTagInjection ) == 0 ) { - break; - } - if ($specialCaseTagInjectionIsBefore) { - $this->content .= $specialCaseTagInjection . implode( "", $this->extractConsecutiveWords( $words, 'tag' ) ); - } else { - $workTag = $this->extractConsecutiveWords( $words, 'tag' ); - if ( isset( $workTag[ 0 ] ) && $this->isOpeningTag( $workTag[ 0 ] ) && !$this->isClosingTag( $workTag[ 0 ] ) ) { - if ( strpos( $workTag[ 0 ], 'class=' ) ) { - $workTag[ 0 ] = str_replace( 'class="', 'class="diffmod ', $workTag[ 0 ] ); - $workTag[ 0 ] = str_replace( "class='", 'class="diffmod ', $workTag[ 0 ] ); - } else { - $workTag[ 0 ] = str_replace( ">", ' class="diffmod">', $workTag[ 0 ] ); - } - } - $this->content .= implode( "", $workTag ) . $specialCaseTagInjection; - } - } - } - - protected function checkCondition($word, $condition) - { - return $condition == 'tag' ? $this->isTag( $word ) : !$this->isTag( $word ); - } - - protected function wrapText($text, $tagName, $cssClass) - { - return sprintf( '<%1$s class="%2$s">%3$s', $tagName, $cssClass, $text ); - } - - protected function extractConsecutiveWords(&$words, $condition) - { - $indexOfFirstTag = null; - foreach ($words as $i => $word) { - if ( !$this->checkCondition( $word, $condition ) ) { - $indexOfFirstTag = $i; - break; - } - } - if ($indexOfFirstTag !== null) { - $items = array(); - foreach ($words as $pos => $s) { - if ($pos >= 0 && $pos < $indexOfFirstTag) { - $items[] = $s; - } - } - if ($indexOfFirstTag > 0) { - array_splice( $words, 0, $indexOfFirstTag ); - } - - return $items; - } else { - $items = array(); - foreach ($words as $pos => $s) { - if ( $pos >= 0 && $pos <= count( $words ) ) { - $items[] = $s; - } - } - array_splice( $words, 0, count( $words ) ); - - return $items; - } - } - - protected function isTag($item) - { - return $this->isOpeningTag( $item ) || $this->isClosingTag( $item ); - } - - protected function isOpeningTag($item) - { - return preg_match( "#<[^>]+>\\s*#iU", $item ); - } - - protected function isClosingTag($item) - { - return preg_match( "#]+>\\s*#iU", $item ); - } - - protected function operations() - { - $positionInOld = 0; - $positionInNew = 0; - $operations = array(); - $matches = $this->matchingBlocks(); - $matches[] = new Match( count( $this->oldWords ), count( $this->newWords ), 0 ); - foreach ($matches as $i => $match) { - $matchStartsAtCurrentPositionInOld = ( $positionInOld == $match->startInOld ); - $matchStartsAtCurrentPositionInNew = ( $positionInNew == $match->startInNew ); - $action = 'none'; - - if ($matchStartsAtCurrentPositionInOld == false && $matchStartsAtCurrentPositionInNew == false) { - $action = 'replace'; - } elseif ($matchStartsAtCurrentPositionInOld == true && $matchStartsAtCurrentPositionInNew == false) { - $action = 'insert'; - } elseif ($matchStartsAtCurrentPositionInOld == false && $matchStartsAtCurrentPositionInNew == true) { - $action = 'delete'; - } else { // This occurs if the first few words are the same in both versions - $action = 'none'; - } - if ($action != 'none') { - $operations[] = new Operation( $action, $positionInOld, $match->startInOld, $positionInNew, $match->startInNew ); - } - if ( count( $match ) != 0 ) { - $operations[] = new Operation( 'equal', $match->startInOld, $match->endInOld(), $match->startInNew, $match->endInNew() ); - } - $positionInOld = $match->endInOld(); - $positionInNew = $match->endInNew(); - } - - return $operations; - } - - protected function matchingBlocks() - { - $matchingBlocks = array(); - $this->findMatchingBlocks( 0, count( $this->oldWords ), 0, count( $this->newWords ), $matchingBlocks ); - - return $matchingBlocks; - } - - protected function findMatchingBlocks($startInOld, $endInOld, $startInNew, $endInNew, &$matchingBlocks) - { - $match = $this->findMatch( $startInOld, $endInOld, $startInNew, $endInNew ); - if ($match !== null) { - if ($startInOld < $match->startInOld && $startInNew < $match->startInNew) { - $this->findMatchingBlocks( $startInOld, $match->startInOld, $startInNew, $match->startInNew, $matchingBlocks ); - } - $matchingBlocks[] = $match; - if ( $match->endInOld() < $endInOld && $match->endInNew() < $endInNew ) { - $this->findMatchingBlocks( $match->endInOld(), $endInOld, $match->endInNew(), $endInNew, $matchingBlocks ); - } - } - } - - protected function stripTagAttributes($word) - { - $word = explode( ' ', trim( $word, '<>' ) ); - - return '<' . $word[ 0 ] . '>'; - } - - protected function findMatch($startInOld, $endInOld, $startInNew, $endInNew) - { - $bestMatchInOld = $startInOld; - $bestMatchInNew = $startInNew; - $bestMatchSize = 0; - $matchLengthAt = array(); - for ($indexInOld = $startInOld; $indexInOld < $endInOld; $indexInOld++) { - $newMatchLengthAt = array(); - $index = $this->oldWords[ $indexInOld ]; - if ( $this->isTag( $index ) ) { - $index = $this->stripTagAttributes( $index ); - } - if ( !isset( $this->wordIndices[ $index ] ) ) { - $matchLengthAt = $newMatchLengthAt; - continue; - } - foreach ($this->wordIndices[ $index ] as $indexInNew) { - if ($indexInNew < $startInNew) { - continue; - } - if ($indexInNew >= $endInNew) { - break; - } - $newMatchLength = ( isset( $matchLengthAt[ $indexInNew - 1 ] ) ? $matchLengthAt[ $indexInNew - 1 ] : 0 ) + 1; - $newMatchLengthAt[ $indexInNew ] = $newMatchLength; - if ($newMatchLength > $bestMatchSize) { - $bestMatchInOld = $indexInOld - $newMatchLength + 1; - $bestMatchInNew = $indexInNew - $newMatchLength + 1; - $bestMatchSize = $newMatchLength; - } - } - $matchLengthAt = $newMatchLengthAt; - } - - // Skip match if none found or match consists only of whitespace - if ($bestMatchSize != 0 && - ( - !$this->isGroupDiffs() || - !preg_match('/^\s+$/', implode('', array_slice($this->oldWords, $bestMatchInOld, $bestMatchSize))) - ) - ) { - return new Match($bestMatchInOld, $bestMatchInNew, $bestMatchSize); - } - - return null; - } -} diff --git a/plugins/libkolab/vendor/Caxy/HtmlDiff/Match.php b/plugins/libkolab/vendor/Caxy/HtmlDiff/Match.php deleted file mode 100644 index c76cfba8..00000000 --- a/plugins/libkolab/vendor/Caxy/HtmlDiff/Match.php +++ /dev/null @@ -1,27 +0,0 @@ -startInOld = $startInOld; - $this->startInNew = $startInNew; - $this->size = $size; - } - - public function endInOld() - { - return $this->startInOld + $this->size; - } - - public function endInNew() - { - return $this->startInNew + $this->size; - } -} diff --git a/plugins/libkolab/vendor/Caxy/HtmlDiff/Operation.php b/plugins/libkolab/vendor/Caxy/HtmlDiff/Operation.php deleted file mode 100644 index b2276a7d..00000000 --- a/plugins/libkolab/vendor/Caxy/HtmlDiff/Operation.php +++ /dev/null @@ -1,21 +0,0 @@ -action = $action; - $this->startInOld = $startInOld; - $this->endInOld = $endInOld; - $this->startInNew = $startInNew; - $this->endInNew = $endInNew; - } -} diff --git a/plugins/libkolab/vendor/finediff.php b/plugins/libkolab/vendor/finediff.php deleted file mode 100644 index b3c416ce..00000000 --- a/plugins/libkolab/vendor/finediff.php +++ /dev/null @@ -1,688 +0,0 @@ -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; - $html = ''; - foreach ( $this->edits as $edit ) { - $n = $edit->getFromLen(); - if ( $edit instanceof FineDiffCopyOp ) { - $html .= FineDiff::renderDiffToHTMLFromOpcode('c', $this->from_text, $in_offset, $n); - } - else if ( $edit instanceof FineDiffDeleteOp ) { - $html .= FineDiff::renderDiffToHTMLFromOpcode('d', $this->from_text, $in_offset, $n); - } - else if ( $edit instanceof FineDiffInsertOp ) { - $html .= FineDiff::renderDiffToHTMLFromOpcode('i', $edit->getText(), 0, $edit->getToLen()); - } - else /* if ( $edit instanceof FineDiffReplaceOp ) */ { - $html .= FineDiff::renderDiffToHTMLFromOpcode('d', $this->from_text, $in_offset, $n); - $html .= FineDiff::renderDiffToHTMLFromOpcode('i', $edit->getText(), 0, $edit->getToLen()); - } - $in_offset += $n; - } - return $html; - } - - /**------------------------------------------------------------------------ - * 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) { - return FineDiff::renderFromOpcodes($from, $opcodes, array('FineDiff','renderToTextFromOpcode')); - } - - /**------------------------------------------------------------------------ - * Render the diff to an HTML string - */ - public static function renderDiffToHTMLFromOpcodes($from, $opcodes) { - return FineDiff::renderFromOpcodes($from, $opcodes, array('FineDiff','renderDiffToHTMLFromOpcode')); - } - - /**------------------------------------------------------------------------ - * Generic opcodes parser, user must supply callback for handling - * single opcode - */ - public static function renderFromOpcodes($from, $opcodes, $callback) { - if ( !is_callable($callback) ) { - return ''; - } - $out = ''; - $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 - $out .= call_user_func($callback, 'c', $from, $from_offset, $n, ''); - $from_offset += $n; - } - else if ( $opcode === 'd' ) { // delete n characters from source - $out .= call_user_func($callback, 'd', $from, $from_offset, $n, ''); - $from_offset += $n; - } - else /* if ( $opcode === 'i' ) */ { // insert n characters from opcodes - $out .= call_user_func($callback, 'i', $opcodes, $opcodes_offset + 1, $n); - $opcodes_offset += 1 + $n; - } - } - return $out; - } - - /** - * 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' ) { - return substr($from, $from_offset, $from_len); - } - return ''; - } - - private static function renderDiffToHTMLFromOpcode($opcode, $from, $from_offset, $from_len) { - if ( $opcode === 'c' ) { - return htmlentities(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); - } - return '' . htmlentities($deletion) . ''; - } - else /* if ( $opcode === 'i' ) */ { - return '' . htmlentities(substr($from, $from_offset, $from_len)) . ''; - } - return ''; - } - } - diff --git a/plugins/libkolab/vendor/finediff_modifications.diff b/plugins/libkolab/vendor/finediff_modifications.diff deleted file mode 100644 index 3a9ad5c9..00000000 --- a/plugins/libkolab/vendor/finediff_modifications.diff +++ /dev/null @@ -1,121 +0,0 @@ ---- finediff.php.orig 2014-07-29 14:24:10.000000000 +0200 -+++ finediff.php 2014-07-29 14:30:38.000000000 +0200 -@@ -234,25 +234,25 @@ - - public function renderDiffToHTML() { - $in_offset = 0; -- ob_start(); -+ $html = ''; - foreach ( $this->edits as $edit ) { - $n = $edit->getFromLen(); - if ( $edit instanceof FineDiffCopyOp ) { -- FineDiff::renderDiffToHTMLFromOpcode('c', $this->from_text, $in_offset, $n); -+ $html .= FineDiff::renderDiffToHTMLFromOpcode('c', $this->from_text, $in_offset, $n); - } - else if ( $edit instanceof FineDiffDeleteOp ) { -- FineDiff::renderDiffToHTMLFromOpcode('d', $this->from_text, $in_offset, $n); -+ $html .= FineDiff::renderDiffToHTMLFromOpcode('d', $this->from_text, $in_offset, $n); - } - else if ( $edit instanceof FineDiffInsertOp ) { -- FineDiff::renderDiffToHTMLFromOpcode('i', $edit->getText(), 0, $edit->getToLen()); -+ $html .= 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()); -+ $html .= FineDiff::renderDiffToHTMLFromOpcode('d', $this->from_text, $in_offset, $n); -+ $html .= FineDiff::renderDiffToHTMLFromOpcode('i', $edit->getText(), 0, $edit->getToLen()); - } - $in_offset += $n; - } -- return ob_get_clean(); -+ return $html; - } - - /**------------------------------------------------------------------------ -@@ -277,18 +277,14 @@ - * 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(); -+ return FineDiff::renderFromOpcodes($from, $opcodes, array('FineDiff','renderToTextFromOpcode')); - } - - /**------------------------------------------------------------------------ - * Render the diff to an HTML string - */ - public static function renderDiffToHTMLFromOpcodes($from, $opcodes) { -- ob_start(); -- FineDiff::renderFromOpcodes($from, $opcodes, array('FineDiff','renderDiffToHTMLFromOpcode')); -- return ob_get_clean(); -+ return FineDiff::renderFromOpcodes($from, $opcodes, array('FineDiff','renderDiffToHTMLFromOpcode')); - } - - /**------------------------------------------------------------------------ -@@ -297,8 +293,9 @@ - */ - public static function renderFromOpcodes($from, $opcodes, $callback) { - if ( !is_callable($callback) ) { -- return; -+ return ''; - } -+ $out = ''; - $opcodes_len = strlen($opcodes); - $from_offset = $opcodes_offset = 0; - while ( $opcodes_offset < $opcodes_len ) { -@@ -312,18 +309,19 @@ - $n = 1; - } - if ( $opcode === 'c' ) { // copy n characters from source -- call_user_func($callback, 'c', $from, $from_offset, $n, ''); -+ $out .= 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, ''); -+ $out .= 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); -+ $out .= call_user_func($callback, 'i', $opcodes, $opcodes_offset + 1, $n); - $opcodes_offset += 1 + $n; - } - } -+ return $out; - } - - /** -@@ -665,24 +663,26 @@ - */ - private static function renderToTextFromOpcode($opcode, $from, $from_offset, $from_len) { - if ( $opcode === 'c' || $opcode === 'i' ) { -- echo substr($from, $from_offset, $from_len); -+ return substr($from, $from_offset, $from_len); - } -+ return ''; - } - - private static function renderDiffToHTMLFromOpcode($opcode, $from, $from_offset, $from_len) { - if ( $opcode === 'c' ) { -- echo htmlentities(substr($from, $from_offset, $from_len)); -+ return htmlentities(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 '', htmlentities($deletion), ''; -+ return '' . htmlentities($deletion) . ''; - } - else /* if ( $opcode === 'i' ) */ { -- echo '', htmlentities(substr($from, $from_offset, $from_len)), ''; -+ return '' . htmlentities(substr($from, $from_offset, $from_len)) . ''; - } -+ return ''; - } - } -