self::MODE_PAGE,
                'date' => false,
            ];
        }
        $this->info = $info;
    }
    /**
     * Set or return whether this revision is current page or media file
     *
     * This method does not check exactly whether the revision is current or not. Instead,
     * set value of associated "current" key for internal use. Some UI element like diff
     * link button depend on relation to current page or media file. A changelog line does
     * not indicate whether it corresponds to current page or media file.
     *
     * @param bool $value true if the revision is current, otherwise false
     * @return bool
     */
    public function isCurrent($value = null)
    {
        return (bool) $this->val('current', $value);
    }
    /**
     * Return or set a value of associated key of revision information
     * but does not allow to change values of existing keys
     *
     * @param string $key
     * @param mixed $value
     * @return string|null
     */
    public function val($key, $value = null)
    {
        if (isset($value) && !array_key_exists($key, $this->info)) {
            // setter, only for new keys
            $this->info[$key] = $value;
        }
        if (array_key_exists($key, $this->info)) {
            // getter
            return $this->info[$key];
        }
        return null;
    }
    /**
     * Set extra key-value to the revision information
     * but does not allow to change values of existing keys
     * @param array $info
     * @return void
     */
    public function append(array $info)
    {
        foreach ($info as $key => $value) {
            $this->val($key, $value);
        }
    }
    /**
     * file icon of the page or media file
     * used in [Ui\recent]
     *
     * @return string
     */
    public function showFileIcon()
    {
        $id = $this->val('id');
        if ($this->val('mode') == self::MODE_MEDIA) {
            // media file revision
            return media_printicon($id);
        } elseif ($this->val('mode') == self::MODE_PAGE) {
            // page revision
            return ' ';
        }
    }
    /**
     * edit date and time of the page or media file
     * used in [Ui\recent, Ui\Revisions]
     *
     * @param bool $checkTimestamp  enable timestamp check, alter formatted string when timestamp is false
     * @return string
     */
    public function showEditDate($checkTimestamp = false)
    {
        $formatted = dformat($this->val('date'));
        if ($checkTimestamp && $this->val('timestamp') === false) {
            // exact date is unknown for externally deleted file
            // when unknown, alter formatted string "YYYY-mm-DD HH:MM" to "____-__-__ __:__"
            $formatted = preg_replace('/[0-9a-zA-Z]/', '_', $formatted);
        }
        return '' . $formatted . '';
    }
    /**
     * edit summary
     * used in [Ui\recent, Ui\Revisions]
     *
     * @return string
     */
    public function showEditSummary()
    {
        return '' . ' – ' . hsc($this->val('sum')) . '';
    }
    /**
     * editor of the page or media file
     * used in [Ui\recent, Ui\Revisions]
     *
     * @return string
     */
    public function showEditor()
    {
        if ($this->val('user')) {
            $html = '' . editorinfo($this->val('user')) . '';
            if (auth_ismanager()) {
                $html .= ' (' . $this->val('ip') . ')';
            }
        } else {
            $html = '' . $this->val('ip') . '';
        }
        return '' . $html . '';
    }
    /**
     * name of the page or media file
     * used in [Ui\recent, Ui\Revisions]
     *
     * @return string
     */
    public function showFileName()
    {
        $id = $this->val('id');
        $rev = $this->isCurrent() ? '' : $this->val('date');
        if ($this->val('mode') == self::MODE_MEDIA) {
            // media file revision
            $params = ['tab_details' => 'view', 'ns' => getNS($id), 'image' => $id];
            if ($rev) $params += ['rev' => $rev];
            $href = media_managerURL($params, '&');
            $display_name = $id;
            $exists = file_exists(mediaFN($id, $rev));
        } elseif ($this->val('mode') == self::MODE_PAGE) {
            // page revision
            $params = $rev ? ['rev' => $rev] : [];
            $href = wl($id, $params, false, '&');
            $display_name = useHeading('navigation') ? hsc(p_get_first_heading($id)) : $id;
            if (!$display_name) $display_name = $id;
            $exists = page_exists($id, $rev);
        }
        if ($exists) {
            $class = 'wikilink1';
        } elseif ($this->isCurrent()) {
            //show only not-existing link for current page, which allows for directly create a new page/upload
            $class = 'wikilink2';
        } else {
            //revision is not in attic
            return $display_name;
        }
        if ($this->val('type') == DOKU_CHANGE_TYPE_DELETE) {
            $class = 'wikilink2';
        }
        return '' . $display_name . '';
    }
    /**
     * Revision Title for PageDiff table headline
     *
     * @return string
     */
    public function showRevisionTitle()
    {
        global $lang;
        if (!$this->val('date')) return '—';
        $id = $this->val('id');
        $rev = $this->isCurrent() ? '' : $this->val('date');
        $params = ($rev) ? ['rev' => $rev] : [];
        // revision info may have timestamp key when external edits occurred
        $date = ($this->val('timestamp') === false)
            ? $lang['unknowndate']
            : dformat($this->val('date'));
        if ($this->val('mode') == self::MODE_MEDIA) {
            // media file revision
            $href = ml($id, $params, false, '&');
            $exists = file_exists(mediaFN($id, $rev));
        } elseif ($this->val('mode') == self::MODE_PAGE) {
            // page revision
            $href = wl($id, $params, false, '&');
            $exists = page_exists($id, $rev);
        }
        if ($exists) {
            $class = 'wikilink1';
        } elseif ($this->isCurrent()) {
            //show only not-existing link for current page, which allows for directly create a new page/upload
            $class = 'wikilink2';
        } else {
            //revision is not in attic
            return $id . ' [' . $date . ']';
        }
        if ($this->val('type') == DOKU_CHANGE_TYPE_DELETE) {
            $class = 'wikilink2';
        }
        return '' . $id . ' [' . $date . ']' . '';
    }
    /**
     * diff link icon in recent changes list, to compare (this) current revision with previous one
     * all items in "recent changes" are current revision of the page or media
     *
     * @return string
     */
    public function showIconCompareWithPrevious()
    {
        global $lang;
        $id = $this->val('id');
        $href = '';
        if ($this->val('mode') == self::MODE_MEDIA) {
            // media file revision
            // unlike page, media file does not copied to media_attic when uploaded.
            // diff icon will not be shown when external edit occurred
            // because no attic file to be compared with current.
            $revs = (new MediaChangeLog($id))->getRevisions(0, 1);
            $showLink = (count($revs) && file_exists(mediaFN($id, $revs[0])) && file_exists(mediaFN($id)));
            if ($showLink) {
                $param = ['tab_details' => 'history', 'mediado' => 'diff', 'ns' => getNS($id), 'image' => $id];
                $href = media_managerURL($param, '&');
            }
        } elseif ($this->val('mode') == self::MODE_PAGE) {
            // page revision
            // when a page just created anyway, it is natural to expect no older revisions
            // even if it had once existed but deleted before. Simply ignore to check changelog.
            if ($this->val('type') !== DOKU_CHANGE_TYPE_CREATE) {
                $href = wl($id, ['do' => 'diff'], false, '&');
            }
        }
        if ($href) {
            return ''
                  . '
';
        }
    }
    /**
     * edit date and time of the page or media file
     * used in [Ui\recent, Ui\Revisions]
     *
     * @param bool $checkTimestamp  enable timestamp check, alter formatted string when timestamp is false
     * @return string
     */
    public function showEditDate($checkTimestamp = false)
    {
        $formatted = dformat($this->val('date'));
        if ($checkTimestamp && $this->val('timestamp') === false) {
            // exact date is unknown for externally deleted file
            // when unknown, alter formatted string "YYYY-mm-DD HH:MM" to "____-__-__ __:__"
            $formatted = preg_replace('/[0-9a-zA-Z]/', '_', $formatted);
        }
        return '' . $formatted . '';
    }
    /**
     * edit summary
     * used in [Ui\recent, Ui\Revisions]
     *
     * @return string
     */
    public function showEditSummary()
    {
        return '' . ' – ' . hsc($this->val('sum')) . '';
    }
    /**
     * editor of the page or media file
     * used in [Ui\recent, Ui\Revisions]
     *
     * @return string
     */
    public function showEditor()
    {
        if ($this->val('user')) {
            $html = '' . editorinfo($this->val('user')) . '';
            if (auth_ismanager()) {
                $html .= ' (' . $this->val('ip') . ')';
            }
        } else {
            $html = '' . $this->val('ip') . '';
        }
        return '' . $html . '';
    }
    /**
     * name of the page or media file
     * used in [Ui\recent, Ui\Revisions]
     *
     * @return string
     */
    public function showFileName()
    {
        $id = $this->val('id');
        $rev = $this->isCurrent() ? '' : $this->val('date');
        if ($this->val('mode') == self::MODE_MEDIA) {
            // media file revision
            $params = ['tab_details' => 'view', 'ns' => getNS($id), 'image' => $id];
            if ($rev) $params += ['rev' => $rev];
            $href = media_managerURL($params, '&');
            $display_name = $id;
            $exists = file_exists(mediaFN($id, $rev));
        } elseif ($this->val('mode') == self::MODE_PAGE) {
            // page revision
            $params = $rev ? ['rev' => $rev] : [];
            $href = wl($id, $params, false, '&');
            $display_name = useHeading('navigation') ? hsc(p_get_first_heading($id)) : $id;
            if (!$display_name) $display_name = $id;
            $exists = page_exists($id, $rev);
        }
        if ($exists) {
            $class = 'wikilink1';
        } elseif ($this->isCurrent()) {
            //show only not-existing link for current page, which allows for directly create a new page/upload
            $class = 'wikilink2';
        } else {
            //revision is not in attic
            return $display_name;
        }
        if ($this->val('type') == DOKU_CHANGE_TYPE_DELETE) {
            $class = 'wikilink2';
        }
        return '' . $display_name . '';
    }
    /**
     * Revision Title for PageDiff table headline
     *
     * @return string
     */
    public function showRevisionTitle()
    {
        global $lang;
        if (!$this->val('date')) return '—';
        $id = $this->val('id');
        $rev = $this->isCurrent() ? '' : $this->val('date');
        $params = ($rev) ? ['rev' => $rev] : [];
        // revision info may have timestamp key when external edits occurred
        $date = ($this->val('timestamp') === false)
            ? $lang['unknowndate']
            : dformat($this->val('date'));
        if ($this->val('mode') == self::MODE_MEDIA) {
            // media file revision
            $href = ml($id, $params, false, '&');
            $exists = file_exists(mediaFN($id, $rev));
        } elseif ($this->val('mode') == self::MODE_PAGE) {
            // page revision
            $href = wl($id, $params, false, '&');
            $exists = page_exists($id, $rev);
        }
        if ($exists) {
            $class = 'wikilink1';
        } elseif ($this->isCurrent()) {
            //show only not-existing link for current page, which allows for directly create a new page/upload
            $class = 'wikilink2';
        } else {
            //revision is not in attic
            return $id . ' [' . $date . ']';
        }
        if ($this->val('type') == DOKU_CHANGE_TYPE_DELETE) {
            $class = 'wikilink2';
        }
        return '' . $id . ' [' . $date . ']' . '';
    }
    /**
     * diff link icon in recent changes list, to compare (this) current revision with previous one
     * all items in "recent changes" are current revision of the page or media
     *
     * @return string
     */
    public function showIconCompareWithPrevious()
    {
        global $lang;
        $id = $this->val('id');
        $href = '';
        if ($this->val('mode') == self::MODE_MEDIA) {
            // media file revision
            // unlike page, media file does not copied to media_attic when uploaded.
            // diff icon will not be shown when external edit occurred
            // because no attic file to be compared with current.
            $revs = (new MediaChangeLog($id))->getRevisions(0, 1);
            $showLink = (count($revs) && file_exists(mediaFN($id, $revs[0])) && file_exists(mediaFN($id)));
            if ($showLink) {
                $param = ['tab_details' => 'history', 'mediado' => 'diff', 'ns' => getNS($id), 'image' => $id];
                $href = media_managerURL($param, '&');
            }
        } elseif ($this->val('mode') == self::MODE_PAGE) {
            // page revision
            // when a page just created anyway, it is natural to expect no older revisions
            // even if it had once existed but deleted before. Simply ignore to check changelog.
            if ($this->val('type') !== DOKU_CHANGE_TYPE_CREATE) {
                $href = wl($id, ['do' => 'diff'], false, '&');
            }
        }
        if ($href) {
            return ''
                  . '![' . $lang['diff'] . ' ' . $lang['diff'] . '](' . DOKU_BASE . 'lib/images/diff.png) '
                  . '';
        } else {
            return '
'
                  . '';
        } else {
            return ' ';
        }
    }
    /**
     * diff link icon in revisions list, compare this revision with current one
     * the icon does not displayed for the current revision
     *
     * @return string
     */
    public function showIconCompareWithCurrent()
    {
        global $lang;
        $id = $this->val('id');
        $rev = $this->isCurrent() ? '' : $this->val('date');
        $href = '';
        if ($this->val('mode') == self::MODE_MEDIA) {
            // media file revision
            if (!$this->isCurrent() && file_exists(mediaFN($id, $rev))) {
                $param = ['mediado' => 'diff', 'image' => $id, 'rev' => $rev];
                $href = media_managerURL($param, '&');
            }
        } elseif ($this->val('mode') == self::MODE_PAGE) {
            // page revision
            if (!$this->isCurrent()) {
                $href = wl($id, ['rev' => $rev, 'do' => 'diff'], false, '&');
            }
        }
        if ($href) {
            return ''
                  . '
';
        }
    }
    /**
     * diff link icon in revisions list, compare this revision with current one
     * the icon does not displayed for the current revision
     *
     * @return string
     */
    public function showIconCompareWithCurrent()
    {
        global $lang;
        $id = $this->val('id');
        $rev = $this->isCurrent() ? '' : $this->val('date');
        $href = '';
        if ($this->val('mode') == self::MODE_MEDIA) {
            // media file revision
            if (!$this->isCurrent() && file_exists(mediaFN($id, $rev))) {
                $param = ['mediado' => 'diff', 'image' => $id, 'rev' => $rev];
                $href = media_managerURL($param, '&');
            }
        } elseif ($this->val('mode') == self::MODE_PAGE) {
            // page revision
            if (!$this->isCurrent()) {
                $href = wl($id, ['rev' => $rev, 'do' => 'diff'], false, '&');
            }
        }
        if ($href) {
            return ''
                  . '![' . $lang['diff'] . ' ' . $lang['diff'] . '](' . DOKU_BASE . 'lib/images/diff.png) '
                  . '';
        } else {
            return '
'
                  . '';
        } else {
            return ' ';
        }
    }
    /**
     * icon for revision action
     * used in [Ui\recent]
     *
     * @return string
     */
    public function showIconRevisions()
    {
        global $lang;
        if (!actionOK('revisions')) {
            return '';
        }
        $id = $this->val('id');
        if ($this->val('mode') == self::MODE_MEDIA) {
            // media file revision
            $param  = ['tab_details' => 'history', 'ns' => getNS($id), 'image' => $id];
            $href = media_managerURL($param, '&');
        } elseif ($this->val('mode') == self::MODE_PAGE) {
            // page revision
            $href = wl($id, ['do' => 'revisions'], false, '&');
        }
        return ''
              . '
';
        }
    }
    /**
     * icon for revision action
     * used in [Ui\recent]
     *
     * @return string
     */
    public function showIconRevisions()
    {
        global $lang;
        if (!actionOK('revisions')) {
            return '';
        }
        $id = $this->val('id');
        if ($this->val('mode') == self::MODE_MEDIA) {
            // media file revision
            $param  = ['tab_details' => 'history', 'ns' => getNS($id), 'image' => $id];
            $href = media_managerURL($param, '&');
        } elseif ($this->val('mode') == self::MODE_PAGE) {
            // page revision
            $href = wl($id, ['do' => 'revisions'], false, '&');
        }
        return ''
              . '![' . $lang['btn_revs'] . ' ' . $lang['btn_revs'] . '](' . DOKU_BASE . 'lib/images/history.png) '
              . '';
    }
    /**
     * size change
     * used in [Ui\recent, Ui\Revisions]
     *
     * @return string
     */
    public function showSizeChange()
    {
        $class = 'sizechange';
        $value = filesize_h(abs($this->val('sizechange')));
        if ($this->val('sizechange') > 0) {
            $class .= ' positive';
            $value = '+' . $value;
        } elseif ($this->val('sizechange') < 0) {
            $class .= ' negative';
            $value = '-' . $value;
        } else {
            $value = '±' . $value;
        }
        return '' . $value . '';
    }
    /**
     * current indicator, used in revision list
     * not used in Ui\Recent because recent files are always current one
     *
     * @return string
     */
    public function showCurrentIndicator()
    {
        global $lang;
        return $this->isCurrent() ? '(' . $lang['current'] . ')' : '';
    }
}
'
              . '';
    }
    /**
     * size change
     * used in [Ui\recent, Ui\Revisions]
     *
     * @return string
     */
    public function showSizeChange()
    {
        $class = 'sizechange';
        $value = filesize_h(abs($this->val('sizechange')));
        if ($this->val('sizechange') > 0) {
            $class .= ' positive';
            $value = '+' . $value;
        } elseif ($this->val('sizechange') < 0) {
            $class .= ' negative';
            $value = '-' . $value;
        } else {
            $value = '±' . $value;
        }
        return '' . $value . '';
    }
    /**
     * current indicator, used in revision list
     * not used in Ui\Recent because recent files are always current one
     *
     * @return string
     */
    public function showCurrentIndicator()
    {
        global $lang;
        return $this->isCurrent() ? '(' . $lang['current'] . ')' : '';
    }
}