diff options
Diffstat (limited to 'inc/Ui/Diff.php')
-rw-r--r-- | inc/Ui/Diff.php | 525 |
1 files changed, 80 insertions, 445 deletions
diff --git a/inc/Ui/Diff.php b/inc/Ui/Diff.php index 513e0667c..70f50f3fc 100644 --- a/inc/Ui/Diff.php +++ b/inc/Ui/Diff.php @@ -2,502 +2,137 @@ namespace dokuwiki\Ui; -use dokuwiki\ChangeLog\PageChangeLog; -use dokuwiki\ChangeLog\MediaChangeLog; -use dokuwiki\Extension\Event; -use dokuwiki\Form\Form; +use dokuwiki\ChangeLog\ChangeLog; /** * DokuWiki Diff Interface + * parent class of PageDiff and MediaDiff * * @package dokuwiki\Ui */ -class Diff extends Ui +abstract class Diff extends Ui { - protected $text; - protected $showIntro; - protected $difftype; + /* @var string */ + protected $id; // page id or media id + + /* @var int */ + protected $oldRev; // timestamp of older revision + protected $newRev; // timestamp of newer revision + + /* @var array */ + protected $preference = []; + + /* @var ChangeLog */ + protected $changelog; // PageChangeLog or MediaChangeLog object /** * Diff Ui constructor * - * @param string $text when non-empty: compare with this text with most current version - * @param bool $showIntro display the intro text - * @param string $difftype diff view type (inline or sidebyside) + * @param string $id page id or media id */ - public function __construct($text = '', $showIntro = true, $difftype = null) + public function __construct($id) { - $this->text = $text; - $this->showIntro = $showIntro; - - // determine diff view type - if (isset($difftype)) { - $this->difftype = $difftype; - } else { - global $INPUT; - global $INFO; - $this->difftype = $INPUT->str('difftype') ?: get_doku_pref('difftype', $difftype); - if (empty($this->difftype) && $INFO['ismobile']) { - $this->difftype = 'inline'; - } - } - if ($this->difftype !== 'inline') $this->difftype = 'sidebyside'; + $this->id = $id; + $this->setChangeLog(); } /** - * Show diff - * between current page version and provided $text - * or between the revisions provided via GET or POST - * - * @author Andreas Gohr <andi@splitbrain.org> - * - * @return void + * set class property changelog */ - public function show() - { - global $ID; - global $REV; - global $lang; - global $INPUT; - global $INFO; - $pagelog = new PageChangeLog($ID); - - /* - * Determine requested revision(s) - */ - // we're trying to be clever here, revisions to compare can be either - // given as rev and rev2 parameters, with rev2 being optional. Or in an - // array in rev2. - $rev1 = $REV; - - $rev2 = $INPUT->ref('rev2'); - if (is_array($rev2)) { - $rev1 = (int) $rev2[0]; - $rev2 = (int) $rev2[1]; - - if (!$rev1) { - $rev1 = $rev2; - unset($rev2); - } - } else { - $rev2 = $INPUT->int('rev2'); - } - - /* - * Determine left and right revision, its texts and the header - */ - $r_minor = ''; - $l_minor = ''; - - if ($this->text) { // compare text to the most current revision - $l_rev = ''; - $l_text = rawWiki($ID, ''); - $l_head = '<a class="wikilink1" href="'. wl($ID) .'">' - . $ID .' '. dformat((int) @filemtime(wikiFN($ID))) .'</a> ' - . $lang['current']; - - $r_rev = ''; - $r_text = cleanText($this->text); - $r_head = $lang['yours']; - } else { - if ($rev1 && isset($rev2) && $rev2) { // two specific revisions wanted - // make sure order is correct (older on the left) - if ($rev1 < $rev2) { - $l_rev = $rev1; - $r_rev = $rev2; - } else { - $l_rev = $rev2; - $r_rev = $rev1; - } - } elseif ($rev1) { // single revision given, compare to current - $r_rev = ''; - $l_rev = $rev1; - } else { // no revision was given, compare previous to current - $r_rev = ''; - $revs = $pagelog->getRevisions(0, 1); - $l_rev = $revs[0]; - $REV = $l_rev; // store revision back in $REV - } - - // when both revisions are empty then the page was created just now - if (!$l_rev && !$r_rev) { - $l_text = ''; - } else { - $l_text = rawWiki($ID, $l_rev); - } - $r_text = rawWiki($ID, $r_rev); - - list($l_head, $r_head, $l_minor, $r_minor) = $this->diffHead( - $l_rev, $r_rev, null, false, ($this->difftype == 'inline') - ); - } - - /* - * Build navigation - */ - $l_nav = ''; - $r_nav = ''; - if (!$this->text) { - list($l_nav, $r_nav) = $this->diffNavigation($pagelog, $l_rev, $r_rev); - } - /* - * Create diff object and the formatter - */ - $diff = new \Diff(explode("\n", $l_text), explode("\n", $r_text)); - - if ($this->difftype == 'inline') { - $diffformatter = new \InlineDiffFormatter(); - } else { - $diffformatter = new \TableDiffFormatter(); - } - /* - * Display intro - */ - if ($this->showIntro) print p_locale_xhtml('diff'); - - /* - * Display type and exact reference - */ - if (!$this->text) { - print '<div class="diffoptions group">'; - - // create the form to select difftype - $form = new Form(['action' => wl()]); - $form->setHiddenField('id', $ID); - $form->setHiddenField('rev2[0]', $l_rev); - $form->setHiddenField('rev2[1]', $r_rev); - $form->setHiddenField('do', 'diff'); - $options = array( - 'sidebyside' => $lang['diff_side'], - 'inline' => $lang['diff_inline'] - ); - $input = $form->addDropdown('difftype', $options, $lang['diff_type']) - ->val($this->difftype)->addClass('quickselect'); - $input->useInput(false); // inhibit prefillInput() during toHTML() process - $form->addButton('do[diff]', 'Go')->attr('type','submit'); - print $form->toHTML(); - - print '<p>'; - // link to exactly this view FS#2835 - print $this->diffViewlink('difflink', $l_rev, ($r_rev ?: $INFO['currentrev'])); - print '</p>'; + abstract protected function setChangeLog(); - print '</div>'; // .diffoptions - } - - /* - * Display diff view table - */ - print '<div class="table">'; - print '<table class="diff diff_'. $this->difftype .'">'; + /** + * Prepare revision info of comparison pair + */ + abstract protected function preProcess(); - //navigation and header - if ($this->difftype == 'inline') { - if (!$this->text) { - print '<tr>' - . '<td class="diff-lineheader">-</td>' - . '<td class="diffnav">'. $l_nav .'</td>' - . '</tr>'; - print '<tr>' - . '<th class="diff-lineheader">-</th>' - . '<th '. $l_minor .'>'. $l_head .'</th>' - .'</tr>'; - } - print '<tr>' - . '<td class="diff-lineheader">+</td>' - . '<td class="diffnav">'. $r_nav .'</td>' - .'</tr>'; - print '<tr>' - . '<th class="diff-lineheader">+</th>' - . '<th '. $r_minor .'>'. $r_head .'</th>' - . '</tr>'; + /** + * Set a pair of revisions to be compared + * + * @param int $oldRev + * @param int $newRev + * @return $this + */ + public function compare($oldRev, $newRev) + { + if ($oldRev < $newRev) { + [$this->oldRev, $this->newRev] = [$oldRev, $newRev]; } else { - if (!$this->text) { - print '<tr>' - . '<td colspan="2" class="diffnav">'. $l_nav .'</td>' - . '<td colspan="2" class="diffnav">'. $r_nav .'</td>' - . '</tr>'; - } - print '<tr>' - . '<th colspan="2" '. $l_minor .'>'. $l_head .'</th>' - . '<th colspan="2" '. $r_minor .'>'. $r_head .'</th>' - . '</tr>'; + [$this->oldRev, $this->newRev] = [$newRev, $oldRev]; } - - //diff view - print $this->insertSoftbreaks($diffformatter->format($diff)); - - print '</table>'; - print '</div>'; + return $this; } - /** - * Get header of diff HTML + * Gets or Sets preference of the Ui\Diff object * - * @param string $l_rev Left revisions - * @param string $r_rev Right revision - * @param string $id Page id, if null $ID is used - * @param bool $media If it is for media files - * @param bool $inline Return the header on a single line - * @return string[] HTML snippets for diff header + * @param string|array $prefs a key name or key-value pair(s) + * @param mixed $value value used when the first args is string + * @return array|$this */ - public function diffHead($l_rev, $r_rev, $id = null, $media = false, $inline = false) + public function preference($prefs = null, $value = null) { - global $lang; - if ($id === null) { - global $ID; - $id = $ID; - } - $head_separator = $inline ? ' ' : '<br />'; - $media_or_wikiFN = $media ? 'mediaFN' : 'wikiFN'; - $ml_or_wl = $media ? 'ml' : 'wl'; - $l_minor = $r_minor = ''; - - if ($media) { - $changelog = new MediaChangeLog($id); - } else { - $changelog = new PageChangeLog($id); - } - if (!$l_rev) { - $l_head = '—'; - } else { - $l_info = $changelog->getRevisionInfo($l_rev); - if ($l_info['user']) { - $l_user = '<bdi>'.editorinfo($l_info['user']).'</bdi>'; - if (auth_ismanager()) $l_user .= ' <bdo dir="ltr">('.$l_info['ip'].')</bdo>'; - } else { - $l_user = '<bdo dir="ltr">'.$l_info['ip'].'</bdo>'; - } - $l_user = '<span class="user">'.$l_user.'</span>'; - $l_sum = ($l_info['sum']) ? '<span class="sum"><bdi>'.hsc($l_info['sum']).'</bdi></span>' : ''; - if ($l_info['type'] === DOKU_CHANGE_TYPE_MINOR_EDIT) $l_minor = 'class="minor"'; - - $l_head_title = ($media) ? dformat($l_rev) : $id.' ['.dformat($l_rev).']'; - $l_head = '<bdi><a class="wikilink1" href="'.$ml_or_wl($id,"rev=$l_rev").'">' - . $l_head_title.'</a></bdi>'.$head_separator.$l_user.' '.$l_sum; - } - - if ($r_rev) { - $r_info = $changelog->getRevisionInfo($r_rev); - if ($r_info['user']) { - $r_user = '<bdi>'.editorinfo($r_info['user']).'</bdi>'; - if (auth_ismanager()) $r_user .= ' <bdo dir="ltr">('.$r_info['ip'].')</bdo>'; - } else { - $r_user = '<bdo dir="ltr">'.$r_info['ip'].'</bdo>'; - } - $r_user = '<span class="user">'.$r_user.'</span>'; - $r_sum = ($r_info['sum']) ? '<span class="sum"><bdi>'.hsc($r_info['sum']).'</bdi></span>' : ''; - if ($r_info['type'] === DOKU_CHANGE_TYPE_MINOR_EDIT) $r_minor = 'class="minor"'; - - $r_head_title = ($media) ? dformat($r_rev) : $id.' ['.dformat($r_rev).']'; - $r_head = '<bdi><a class="wikilink1" href="'.$ml_or_wl($id,"rev=$r_rev").'">' - . $r_head_title.'</a></bdi>'.$head_separator.$r_user.' '.$r_sum; - } elseif ($_rev = @filemtime($media_or_wikiFN($id))) { - $_info = $changelog->getRevisionInfo($_rev); - if ($_info['user']) { - $_user = '<bdi>'.editorinfo($_info['user']).'</bdi>'; - if (auth_ismanager()) $_user .= ' <bdo dir="ltr">('.$_info['ip'].')</bdo>'; - } else { - $_user = '<bdo dir="ltr">'.$_info['ip'].'</bdo>'; + // set + if (is_string($prefs) && isset($value)) { + $this->preference[$prefs] = $value; + return $this; + } elseif (is_array($prefs)) { + foreach ($prefs as $name => $value) { + $this->preference[$name] = $value; } - $_user = '<span class="user">'.$_user.'</span>'; - $_sum = ($_info['sum']) ? '<span class="sum"><bdi>'.hsc($_info['sum']).'</span></bdi>' : ''; - if ($_info['type'] === DOKU_CHANGE_TYPE_MINOR_EDIT) $r_minor = 'class="minor"'; - - $r_head_title = ($media) ? dformat($_rev) : $id.' ['.dformat($_rev).']'; - $r_head = '<bdi><a class="wikilink1" href="'.$ml_or_wl($id).'">' - . $r_head_title.'</a></bdi> '.'('.$lang['current'].')'.$head_separator.$_user.' '.$_sum; - }else{ - $r_head = '— ('.$lang['current'].')'; + return $this; } - - return array($l_head, $r_head, $l_minor, $r_minor); + // get + return $this->preference; } /** - * Create html for revision navigation + * Handle requested revision(s) * - * @param PageChangeLog $pagelog changelog object of current page - * @param int $l_rev left revision timestamp - * @param int $r_rev right revision timestamp - * @return string[] html of left and right navigation elements + * @return void */ - protected function diffNavigation($pagelog, $l_rev, $r_rev) + protected function handle() { - global $INFO, $ID; - - // last timestamp is not in changelog, retrieve timestamp from metadata - // note: when page is removed, the metadata timestamp is zero - if (!$r_rev) { - if (isset($INFO['meta']['last_change']['date'])) { - $r_rev = $INFO['meta']['last_change']['date']; - } else { - $r_rev = 0; - } - } + global $INPUT; - //retrieve revisions with additional info - list($l_revs, $r_revs) = $pagelog->getRevisionsAround($l_rev, $r_rev); - $l_revisions = array(); - if (!$l_rev) { - //no left revision given, add dummy - $l_revisions[0]= array('label' => '', 'attrs' => []); - } - foreach ($l_revs as $rev) { - $info = $pagelog->getRevisionInfo($rev); - $l_revisions[$rev] = array( - 'label' => dformat($info['date']) .' '. editorinfo($info['user'], true) .' '. $info['sum'], - 'attrs' => ['title' => $rev], - ); - if ($r_rev ? $rev >= $r_rev : false) $l_revisions[$rev]['attrs']['disabled'] = 'disabled'; - } - $r_revisions = array(); - if (!$r_rev) { - //no right revision given, add dummy - $r_revisions[0] = array('label' => '', 'attrs' => []); - } - foreach ($r_revs as $rev) { - $info = $pagelog->getRevisionInfo($rev); - $r_revisions[$rev] = array( - 'label' => dformat($info['date']) .' '. editorinfo($info['user'], true) .' '. $info['sum'], - 'attrs' => ['title' => $rev], - ); - if ($rev <= $l_rev) $r_revisions[$rev]['attrs']['disabled'] = 'disabled'; + // difflink icon click, eg. ?rev=123456789&do=diff + if ($INPUT->has('rev')) { + $this->oldRev = $INPUT->int('rev'); + $this->newRev = $this->changelog->currentRevision(); } - //determine previous/next revisions - $l_index = array_search($l_rev, $l_revs); - $l_prev = $l_index < count($l_revs) - 1 ? $l_revs[$l_index + 1] : null; - $l_next = $l_index > 1 ? $l_revs[$l_index - 1] : null; - if ($r_rev) { - $r_index = array_search($r_rev, $r_revs); - $r_prev = $r_index < count($r_revs) - 1 ? $r_revs[$r_index + 1] : null; - $r_next = $r_index > 1 ? $r_revs[$r_index - 1] : null; - } else { - //removed page - if ($l_next) { - $r_prev = $r_revs[0]; + // submit button with two checked boxes + $rev2 = $INPUT->arr('rev2', []); + if (count($rev2) > 1) { + if ($rev2[0] < $rev2[1]) { + [$this->oldRev, $this->newRev] = [$rev2[0], $rev2[1]]; } else { - $r_prev = null; + [$this->oldRev, $this->newRev] = [$rev2[1], $rev2[0]]; } - $r_next = null; } - /* - * Left side: - */ - $l_nav = ''; - //move back - if ($l_prev) { - $l_nav .= $this->diffViewlink('diffbothprevrev', $l_prev, $r_prev); - $l_nav .= $this->diffViewlink('diffprevrev', $l_prev, $r_rev); - } - //dropdown - $form = new Form(['action' => wl()]); - $form->setHiddenField('id', $ID); - $form->setHiddenField('difftype', $this->difftype); - $form->setHiddenField('rev2[1]', $r_rev); - $form->setHiddenField('do', 'diff'); - $input = $form->addDropdown('rev2[0]', $l_revisions)->val($l_rev)->addClass('quickselect'); - $input->useInput(false); // inhibit prefillInput() during toHTML() process - $form->addButton('do[diff]', 'Go')->attr('type','submit'); - $l_nav .= $form->toHTML(); - //move forward - if ($l_next && ($l_next < $r_rev || !$r_rev)) { - $l_nav .= $this->diffViewlink('diffnextrev', $l_next, $r_rev); - } - - /* - * Right side: - */ - $r_nav = ''; - //move back - if ($l_rev < $r_prev) { - $r_nav .= $this->diffViewlink('diffprevrev', $l_rev, $r_prev); - } - //dropdown - $form = new Form(['action' => wl()]); - $form->setHiddenField('id', $ID); - $form->setHiddenField('rev2[0]', $l_rev); - $form->setHiddenField('difftype', $this->difftype); - $form->setHiddenField('do', 'diff'); - $input = $form->addDropdown('rev2[1]', $r_revisions)->val($r_rev)->addClass('quickselect'); - $input->useInput(false); // inhibit prefillInput() during toHTML() process - $form->addButton('do[diff]', 'Go')->attr('type','submit'); - $r_nav .= $form->toHTML(); - //move forward - if ($r_next) { - if ($pagelog->isCurrentRevision($r_next)) { - //last revision is diff with current page - $r_nav .= $this->diffViewlink('difflastrev', $l_rev); - } else { - $r_nav .= $this->diffViewlink('diffnextrev', $l_rev, $r_next); - } - } else { - $r_nav .= $this->diffViewlink('diffbothnextrev', $l_next, $r_next); + if (!isset($this->oldRev, $this->newRev)) { + // no revision was given, compare previous to current + $revs = $this->changelog->getRevisions(-1, 2); + $this->newRev = $this->changelog->currentRevision(); + $this->oldRev = ($revs[0] == $this->newRev) ? $revs[1] : $revs[0]; } - return array($l_nav, $r_nav); } - /** - * Create html link to a diff view defined by two revisions - * - * @param string $linktype - * @param int $lrev oldest revision - * @param int $rrev newest revision or null for diff with current revision - * @return string html of link to a diff view - */ - protected function diffViewlink($linktype, $lrev, $rrev = null) - { - global $ID, $lang; - if ($rrev === null) { - $urlparam = array( - 'do' => 'diff', - 'rev' => $lrev, - 'difftype' => $this->difftype, - ); - } else { - $urlparam = array( - 'do' => 'diff', - 'rev2[0]' => $lrev, - 'rev2[1]' => $rrev, - 'difftype' => $this->difftype, - ); - } - return '<a class="'. $linktype .'" href="'. wl($ID, $urlparam) .'" title="'. $lang[$linktype] .'">' - . '<span>'. $lang[$linktype] .'</span>' - . '</a>'; - } + /** - * Insert soft breaks in diff html + * Build header of diff HTML * - * @param string $diffhtml - * @return string + * @param string $l_rev Left revisions + * @param string $r_rev Right revision + * @return string[] HTML snippets for diff header + * @deprecated 2020-12-31 */ - public function insertSoftbreaks($diffhtml) + public function buildDiffHead($l_rev, $r_rev) { - // search the diff html string for both: - // - html tags, so these can be ignored - // - long strings of characters without breaking characters - return preg_replace_callback('/<[^>]*>|[^<> ]{12,}/', function ($match) { - // if match is an html tag, return it intact - if ($match[0][0] == '<') return $match[0]; - // its a long string without a breaking character, - // make certain characters into breaking characters by inserting a - // word break opportunity (<wbr> tag) in front of them. - $regex = <<< REGEX -(?(?= # start a conditional expression with a positive look ahead ... -&\#?\\w{1,6};) # ... for html entities - we don't want to split them (ok to catch some invalid combinations) -&\#?\\w{1,6}; # yes pattern - a quicker match for the html entity, since we know we have one -| -[?/,&\#;:] # no pattern - any other group of 'special' characters to insert a breaking character after -)+ # end conditional expression -REGEX; - return preg_replace('<'.$regex.'>xu', '\0<wbr>', $match[0]); - }, $diffhtml); + dbg_deprecated('not used see '. \dokuwiki\Ui\PageDiff::class .'::show()'); } } |