<?php
/**
 * SeekQuarry/Yioop --
 * Open Source Pure PHP Search Engine, Crawler, and Indexer
 *
 * Copyright (C) 2009 - 2026  Chris Pollett chris@pollett.org
 *
 * LICENSE:
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * END LICENSE
 *
 * @author Chris Pollett chris@pollett.org
 * @license https://www.gnu.org/licenses/ GPL3
 * @link https://www.seekquarry.com/
 * @copyright 2009 - 2026
 * @filesource
 */
namespace seekquarry\yioop\views\elements;

use seekquarry\yioop as B;
use seekquarry\yioop\configs as C;
use seekquarry\yioop\library as L;
use seekquarry\yioop\library\CrawlConstants;
use seekquarry\yioop\models\Model;

/** For tl, getLocaleTag and Yioop constants */
require_once __DIR__.'/../../library/Utility.php';
/**
 * Element responsible for drawing wiki pages in group view
 * It is also responsible for rendering wiki history pages, and listings of
 * wiki pages available for a group
 *
 * @author Chris Pollett
 */
class WikiElement extends Element implements CrawlConstants
{
    /**
     * Draw a wiki page for group, or, depending on $data['MODE'] a listing
     * of all pages for a group, or the history of revisions of a given page
     * or the edit page form
     *
     * @param array $data fields contain data about the page being
     *      displayed or edited, or the list of pages being displayed.
     */
    public function render($data)
    {
        $logged_in = isset($data["ADMIN"]) && $data["ADMIN"];
        $can_edit = $logged_in && isset($data["CAN_EDIT"]) && $data["CAN_EDIT"];
        $public_source = (!empty($data['HEAD']['public_source']));
        $other_controller = "group";
        $base_query = htmlentities(B\wikiUrl("", true, $data['CONTROLLER'],
            $data["GROUP"]["GROUP_ID"]));
        $csrf_token = "";
        if ($logged_in) {
            $csrf_token = C\p('CSRF_TOKEN') . "=" . $data[C\p('CSRF_TOKEN')];
            $base_query .= $csrf_token;
        }
        $mode = " wiki-" . $data["MODE"] . " " ?? "";
        if (isset($data['page_type']) && $data['page_type']
            == 'presentation' && $data["MODE"] == 'read') {
            e('<div class="presentation-activity ' . $mode . '">');
            if (isset($data['QUERY_STATISTICS'])) {
                $this->view->layout_object->presentation = true;
            }
        } else {
            $page_border = "";
            if (isset($data["HEAD"]['page_border']) &&
                $data["HEAD"]['page_border'] &&
                $data['HEAD']['page_border'] != 'none') {
                $page_border = $data['HEAD']['page_border'];
            } else if (in_array($data["MODE"], ["edit", "source"])) {
                $page_border = "solid-border";
            }
            e('<div class="small-margin-current-activity '.
                $page_border. $mode .'">');
        }
        if (isset($data['BACK_URL'])) {
            $this->view->helper("close")->render(C\SHORT_BASE_URL . "?" .
                $data['BACK_URL'] . "&amp;" . C\p('CSRF_TOKEN') . "=" .
                $data[C\p('CSRF_TOKEN')]);
        }
        $folder_prefix = $base_query . '&amp;arg=read&amp;page_name='.
            $data['PAGE_NAME'];
        if (!empty($data['MEDIA_NAME'])) {
            if (!empty($data["PAGE"]) && stristr($data["PAGE"], "iframe") !==
                false) { ?>
                <div class="float-opposite"><a href="javascript:<?= "";
                    ?>window.location=tag('iframe')[0].src;">&#8648;</a><?php
                ?></div><?php
            }
            ?><div class="top-margin media-controls"><?php
                e("<b>".tl('wiki_element_places')."</b>");
                if (!empty($data['PREV_LINK'])) {
                    e("<a id='prev-link' href='".
                        "{$data['PREV_LINK']}'>&lt;&lt;</a>&nbsp;");
                } else {
                    e("<a id='prev-link' class='none'" .
                        " href=''>&lt;&lt;</a>&nbsp;");
                }
                $name_parts = pathinfo($data['MEDIA_NAME']);
                $this->renderPath('media-path', $data, [$folder_prefix =>
                    $data['PAGE_NAME']], "", $name_parts['filename']);
                if (!empty($data['NEXT_LINK'])) {
                    e("&nbsp;<a id='next-link' href='" .
                        "{$data['NEXT_LINK']}'>&gt;&gt;</a>");
                } else {
                    e("&nbsp;<a id='next-link' class='none'" .
                        " href=''>&gt;&gt;</a>");
                }
                ?>
            </div>
            <?php
        }
        switch ($data["MODE"]) {
            case "edit":
                $this->renderEditPageForm($data);
                break;
            case "pages":
                $this->renderPages($data, $can_edit, $logged_in);
                break;
            case "history":
                if (empty($data['NO_HISTORY_SOURCE'])) {
                    $this->renderHistory($data);
                } else {
                    echo tl('wiki_element_no_history');
                }
                break;
            case "diff":
                if (isset($data["PAGE"])) {
                    e($data["PAGE"]);
                }
                break;
            case "source":
                if (empty($data['NO_HISTORY_SOURCE'])) {
                    $this->renderSourcePage($data);
                } else {
                    echo tl('wiki_element_no_source');
                }
                break;
            case "resources":
                $this->renderResources($data);
                break;
            case "relationships":
                $this->renderRelationships($data);
                break;
            case "ballot-init":
                $this->renderBallotInit($data);
                break;
            case "count-ballots":
                $this->renderBallotCount($data);
                break;
            case "ballot-concluded":
                $this->renderBallotConcluded($data);
                break;
            case "read":
                // no break
            case "show":
            default:
                $is_page_and_feedback = (!empty($data["HEAD"]['page_type']) &&
                    $data["HEAD"]['page_type'] == 'page_and_feedback');
                $is_api = ($data['VIEW'] == 'api');
                if (!$is_page_and_feedback || !$is_api) {
                    $this->renderReadPage($data, $can_edit, $logged_in);
                }
                if ($is_page_and_feedback) {
                    if ($is_api) {
                        $data['API'] = true;
                    } else {
                        ?><hr ><?php
                    }
                    $base_query = htmlentities(B\wikiUrl("", true,
                        $data['CONTROLLER'], $data["GROUP"]["GROUP_ID"]));
                    if ($logged_in) {
                        $csrf_token = C\p('CSRF_TOKEN') . "=" .
                            $data[C\p('CSRF_TOKEN')];
                        $base_query .= $csrf_token;
                    }
                    $data['FRAGMENT'] = '#result-' . $data['DISCUSS_THREAD'];
                    $this->view->element('groupfeed')->renderUngroupedView(
                        $can_edit, $base_query, $data['PAGING_QUERY'], $data);
                    if (!$is_api) {
                        $this->view->element('groupfeed')->renderScripts($data);
                    }
                }
                break;
        }
        e('</div>');
    }
    /**
     * Used to draw a Wiki Page for reading. If the page does not exist
     * various create/login-to-create etc messages are displayed depending
     * of it the user is logged in. and has write permissions on the group
     *
     * @param array $data fields PAGE used for page contents
     * @param bool $can_edit whether the current user has permissions to
     *     edit or create this page
     * @param bool $logged_in whether current user is logged in or not
     */
    public function renderReadPage($data, $can_edit, $logged_in)
    {
        $group_id = (empty($data["GROUP"]["GROUP_ID"])) ? C\PUBLIC_GROUP_ID:
            $data["GROUP"]["GROUP_ID"];
        $stretch = ($_SERVER["MOBILE"]) ? 3.8 : 6;
        $word_wrap_len = intval(ceil($stretch * C\NAME_TRUNCATE_LEN));
        $base_query = htmlentities(B\wikiUrl("", true, $data['CONTROLLER'],
            $data["GROUP"]["GROUP_ID"]));
        if ($logged_in) {
            $csrf_token = C\p('CSRF_TOKEN') . "=" . $data[C\p('CSRF_TOKEN')];
            $base_query .= $csrf_token;
        }
        if (!empty($data["RESOURCE_PATH_ERROR"])) {
            e('<div class="resource-path-warning">' .
                tl("wiki_element_resource_path_error") . '</div>');
        }
        if (!empty($data["RESOURCE_PATH_NOT_GIT"])) {
            e('<div class="resource-path-warning">' .
                tl("wiki_element_resource_path_not_git") . '</div>');
        }
        if (isset($data["HEAD"]['page_type']) && $data["HEAD"]['page_type'] ==
            'media_list') {
            $this->renderResources($data, "read", $logged_in);
        } elseif (isset($data["PAGE"]) && isset($data["HEAD"]['page_type']) &&
            $data["HEAD"]['page_type'] == 'share') {
            $this->renderShareWall($data);
        } elseif (isset($data["HEAD"]['page_type']) &&
            $data["HEAD"]['page_type'] == 'git_repository') {
            $this->renderGitRepository($data);
        } else if (!empty($data["PAGE"])) {
            $sub_path = (empty($data['SUB_PATH'])) ? "": $data['SUB_PATH'];
            $media_id = "";
            if (!empty($data['MEDIA_NAME'])) {
                $media_id = L\crawlHash($data['PAGE_ID'] . $data['MEDIA_NAME'] .
                    $sub_path);
            }
            ?><div><?= $this->dynamicSubstitutions($group_id, $data,
                $data["PAGE"]); ?></div><?php
            if (!empty($data['NEXT_LINK'])) {
                $num_resources = count($data['RESOURCES_INFO']['resources']);
                $url_prefix = html_entity_decode($data["URL_PREFIX"]);
                if ($logged_in) {
                    $csrf_token = C\p('CSRF_TOKEN') .
                        "=" .$data[C\p('CSRF_TOKEN')];
                    if (C\REDIRECTS_ON) {
                        $url_prefix =  preg_replace("/\/-\//u",
                            "/$csrf_token/", $url_prefix);
                    } else {
                        $csrf_token = "&" . $csrf_token;
                        $url_prefix .= $csrf_token;
                    }
                } else {
                    $csrf_token = "";
                }
                ?>
                <script>
                media_elt = document.getElementById('<?=$media_id ?>');
                media_prefix = '<?=$url_prefix ?>';
                url_prefix = '<?=html_entity_decode($data["URL_PREFIX"]) ?>';
                next_resources = [<?php
                    $comma = "";
                    $prev_type = false;
                    for ($i = $data['NEXT_INDEX'] - 1;
                        $i < $num_resources; $i++) {
                        $resource = $data['RESOURCES_INFO']['resources'][$i];
                        if (!$resource['media_type'] || ($prev_type &&
                            $prev_type != $resource['media_type'])) {
                            break;
                        } else {
                            echo $comma . json_encode($resource['name']);
                        }
                        $prev_type = $resource['media_type'];
                        $comma = ",";
                    }
                    ?>];
                end_next_resource = false;
                <?php
                $url_connective =  "&n=";
                if ($i < $num_resources) { ?>
                    end_next_resource = url_prefix + '<?=$url_connective .
                        urlencode($resource["name"]) ?>';<?php
                }
                ?>
                current_resource_index = 1;
                if (media_elt) {
                    if (media_elt.tagName == 'AUDIO' ||
                        media_elt.tagName == 'VIDEO' ) {
                        if (localStorage) {
                            if (media_current_time = localStorage.getItem(
                                "current_time" + media_elt.id)) {
                                media_elt.currentTime = media_current_time;
                            }
                            setInterval(function () {
                                media_elt = document.getElementById('<?=
                                    $media_id ?>');
                                localStorage.setItem("current_time" +
                                    media_elt.id, media_elt.currentTime);
                                epsilon_time = 150;
                                if (media_elt.duration &&
                                    media_elt.duration < 600) {
                                    epsilon_time = Math.abs(0.05 *
                                        media_elt.duration);
                                }
                                if (media_elt.duration &&
                                     media_elt.duration -
                                     media_elt.currentTime < epsilon_time) {
                                     localStorage.removeItem(
                                         "current_time" + media_elt.id);
                                }

                            }, 5000);
                        }
                        media_elt.onended = function(evt) {
                            sources = media_elt.getElementsByTagName('source');
                            source = sources[0];
                            elt('prev-link').href= url_prefix +
                                '<?=$url_connective ?>' +
                                next_resources[current_resource_index - 1];
                            elt('prev-link').style.display = 'inline';
                            if (current_resource_index + 1 <
                                next_resources.length) {
                                elt('next-link').href= url_prefix +
                                    '<?=$url_connective ?>' +
                                    next_resources[current_resource_index + 1]
                            } else if (current_resource_index + 1 ==
                                next_resources.length && end_next_resource) {
                                elt('next-link').href = end_next_resource;
                            } else if (end_next_resource) {
                                window.location = end_next_resource;
                                return;
                            } else {
                                elt('next-link').style.display = 'none';
                            }
                            let old_src = source.getAttribute('src');
                            old_src = old_src.split("+").join("%20");
                            let old_resource =
                                next_resources[current_resource_index - 1];
                            source.setAttribute('src', old_src.replace(
                                encodeURIComponent(old_resource),
                                encodeURIComponent(
                                    next_resources[current_resource_index])));
                            if (next_resources[current_resource_index]) {
                                let next_resource_name =
                                    next_resources[current_resource_index
                                    ].replace(/\.\S{3,5}$/, "");
                                page_path_elt = elt('selected-media-path');
                                page_path_elt.innerHTML = next_resource_name;
                                page_path_elt = elt('selected2-media-path');
                                page_path_elt.innerHTML = next_resource_name;
                                current_resource_index++;
                                media_elt.load();
                                media_elt.play();
                            }
                        }
                    }
                }
                </script>
                <?php
            }
        } else if (!empty($data["HEAD"]['page_alias'])) {
            $alias = $data["HEAD"]['page_alias'];
            $data['PAGE']["DESCRIPTION"] = tl('wiki_element_redirect_to').
                " <a href='$base_query&amp;".
                "page_name=$alias'>$alias</a>";
            ?>
            <?=$data['PAGE']["DESCRIPTION"] ?>
            <?php
        } elseif ($can_edit) {
            ?>
            <h2><?= tl("wiki_element_page_no_exist",
                wordwrap(urldecode($data["PAGE_NAME"]), $word_wrap_len ,
                "\n", true)) ?></h2>
            <p><?= tl("wiki_element_create_edit") ?></p>
            <p><?= tl("wiki_element_use_form") ?></p>
            <form id="editpageForm" method="get" action="<?=C\SHORT_BASE_URL
            ?>" >
            <input type="hidden" name="c" value="<?= $data['CONTROLLER']?>" >
            <input type="hidden" name="<?= C\p('CSRF_TOKEN') ?>" value="<?=
                $data[C\p('CSRF_TOKEN')] ?>" >
            <input type="hidden" name="a" value="wiki" >
            <input type="hidden" name="arg" value="edit" >
            <input type="hidden" name="group_id" value="<?=
                $data['GROUP']['GROUP_ID'] ?>" >
            <input aria-label="<?= tl("wiki_element_create_load_page"); ?>"
                type="text" name="page_name" class="narrow-field"
                value="<?=urldecode($data["PAGE_NAME"]) ?>" >
            <button class="button-box" type="submit"><?=
                tl('wiki_element_go') ?></button>
            </form>
            <?php
            $syntax_page = intval($data["GROUP"]["RENDER_ENGINE"] ??
                C\MEDIAWIKI_ENGINE) == C\MARKDOWN_ENGINE ?
                "Markdown_Syntax" : "Syntax";
            e("<p><a href=\"" . htmlentities(
                B\wikiUrl($syntax_page, true,
                $data['CONTROLLER'], C\PUBLIC_GROUP_ID)) .
                C\p('CSRF_TOKEN') .'='.$data[C\p('CSRF_TOKEN')] .
                    '&amp;arg=read'.
                "\">". tl('wiki_element_syntax_summary') .
                "</a>.</p>");
        } elseif (!$logged_in) {
            e("<h2>".tl("wiki_element_page_no_exist",
                wordwrap(urldecode($data["PAGE_NAME"]), $word_wrap_len ,
                "\n", true)) . "</h2>");
            e("<p>".tl("wiki_element_signin_edit")."</p>");
        } else {
            e("<h2>".tl("wiki_element_page_no_exist",
                wordwrap(urldecode($data["PAGE_NAME"]), $word_wrap_len ,
                "\n", true)) .
                "</h2>");
        }
    }
    /**
     *
     * @param array $data fields contain data about the page with the
     *  page being shared
     */
    public function renderShareWall($data)
    {
        ?>
        <h2><?= tl("wiki_element_share_wall", $data["PAGE_NAME"]) ?></h2><?php
        if (empty($data["PAGE"])) {?>
            <div class="red"><?=tl("wiki_element_no_data_shared") ?></div>
            <?php
        } else { ?>
            <div><?=tl("wiki_element_last_edited",
                $data['LAST_EDITOR'], date("r", $data['LAST_EDIT_TIME']));
            ?></div>
            <div class="formatted-share-wall"><div><div><?=
                str_replace("\n", "</div>\n<div>", $data["PAGE"]);
            ?></div></div>
            </div><?php
        } ?>
        <form method="post">
        <input type="hidden" name="c" value="<?=$data['CONTROLLER']
            ?>" >
        <input type="hidden" name="a" value="wiki" >
        <input type="hidden" name="arg" value="edit" >
        <input type="hidden" name="<?=C\p('CSRF_TOKEN') ?>" value="<?=
            $data[C\p('CSRF_TOKEN')];
        ?>" >
        <div id="share-container">
        <h3 class="top-margin"><label for='share-wall'><?=
            tl("wiki_element_raw_content") ?></label></h3>
        <p class="green"><?=tl("wiki_element_add_share")?></p>
        <textarea id="share-wall" class="share-wall"
            name="share_wall_data"><?= $data["PAGE"]
        ?></textarea>
        </div>
        <div class="top-margin center">
        <button class="button-box" type="submit"><?=
            tl("wiki_element_savebutton")
        ?></button>
        </div>
        </form><?php
    }
    /**
     * Draws a Git repository wiki page as a browsable file tree. Shows the
     * address to clone from, the branch being viewed, breadcrumb links back
     * up through the folders, and then either the contents of one file or
     * the list of files and folders at the current spot, with any README
     * rendered below the list. Every value shown is prepared and escaped by
     * the controller, so this method only lays them out.
     *
     * @param array $data fields prepared by initializeGitRepositoryReadMode,
     *      among them GIT_CLONE_URL, GIT_BRANCH, GIT_CRUMBS, GIT_ENTRIES,
     *      GIT_FILE, and GIT_README_HTML
     */
    public function renderGitRepository($data)
    {
        $icon_helper = $this->view->helper('iconlink');
        ?>
        <div class="git-repository">
        <?php
        if (!empty($data["GIT_EMPTY"])) { ?>
            <div class="git-clone-line git-open">
            <code class="git-clone-cmd">git clone <?=
                $data["GIT_CLONE_URL"] ?></code>
            <?php $icon_helper->renderFormButton("button", "clone_copy",
                false, "git-icon-control", 'onclick="gitCopyClone(this)" ' .
                'data-clone="' . $data["GIT_CLONE_URL"] . '"'); ?>
            </div>
            <p class="git-empty"><?= tl("wiki_element_git_empty") ?></p>
            </div>
            <?php
            return;
        }
        if (($data["GIT_VIEW"] ?? "") === "issues") {
            $this->renderGitIssues($data);
            ?></div><?php
            return;
        } ?>
        <div class="git-bar">
        <span class="git-branch">
        <select id="git-branch-select" class="git-branch-select"
            onchange="window.location=this.value;"><?php
        foreach ($data["GIT_BRANCH_OPTIONS"] as $option) {
            $chosen = ($option["SELECTED"]) ? " selected='selected'" : "";
            e("<option value='" . $option["URL"] . "'" . $chosen . ">" .
                $option["NAME"] . "</option>");
        }
        ?></select>
        </span>
        <span class="git-ref">
        <button type="button" class="git-ref-button"
            onclick="toggleGitPanel('git-ref-menu')"><?=
            $data["GIT_REF_LABEL"] ?> <span class="git-caret">&#9662;</span>
        </button>
        <div id="git-ref-menu" class="git-ref-menu git-panel"><?php
        foreach ($data["GIT_REF_MENU"] as $item) {
            $item_class = ($item["KIND"] === "head") ? "git-ref-head" :
                "git-ref-item";
            e("<a class='" . $item_class . "' href='" . $item["URL"] .
                "'>" . $item["LABEL"] . "</a>");
        }
        ?></div>
        </span>
        <span class="git-bar-actions">
        <span class="git-download">
        <button type="button" class="git-icon-button"
            onclick="toggleGitPanel('git-download-menu')"
            aria-label="<?= tl("wiki_element_git_download") ?>"><span
            class="git-icon">&#10515;</span></button>
        <div id="git-download-menu" class="git-download-menu git-panel">
        <a href="<?= $data["GIT_DOWNLOAD_TARGZ"] ?>">tar.gz</a>
        <a href="<?= $data["GIT_DOWNLOAD_ZIP"] ?>">zip</a>
        </div>
        </span>
        <button type="button" class="git-icon-button"
            onclick="toggleGitPanel('git-search-line')"
            aria-label="<?= tl("wiki_element_git_search") ?>"><span
            class="git-icon">&#128269;</span></button>
        </span>
        </div>
        <div id="git-search-line" class="git-search-line git-panel<?=
            (!empty($data["GIT_LIST_SEARCH"])) ? " git-open" : "" ?>"><?php
        if (($data["GIT_VIEW"] ?? "") === "commits" ||
            ($data["GIT_VIEW"] ?? "") === "tags") { ?>
            <input type="text" class="git-search-field"
                value="<?= $data["GIT_LIST_SEARCH"] ?? "" ?>"
                placeholder="<?= tl("wiki_element_git_search") ?>"
                onkeydown="gitListSearch(event, '<?=
                    $data["GIT_LIST_SORT_BASE"] ?? "" ?>')">
        <?php } else { ?>
            <input type="text" class="git-search-field"
                placeholder="<?= tl("wiki_element_git_search") ?>"
                oninput="gitTreeFilter(this)">
        <?php } ?>
        </div>
        <?php if (count($data["GIT_CRUMBS"]) > 1) { ?>
        <div class="git-path"><?php
        $crumb_count = count($data["GIT_CRUMBS"]);
        e("<a href='" . $data["GIT_CRUMBS"][0]["URL"] . "'>/</a>");
        $separator = " ";
        foreach (array_slice($data["GIT_CRUMBS"], 1) as $index => $crumb) {
            if ($index === $crumb_count - 2) {
                e($separator . $crumb["NAME"]);
            } else {
                e($separator . "<a href='" . $crumb["URL"] . "'>" .
                    $crumb["NAME"] . "</a>");
            }
            $separator = " / ";
        }
        if (!empty($data["GIT_RESOURCE_DOWNLOAD_URL"])) {
            e("<a class='git-path-download' title='" .
                tl("wiki_element_git_download") . "' href='" .
                $data["GIT_RESOURCE_DOWNLOAD_URL"] . "'>&#10515;</a>");
        }
        ?></div>
        <?php } ?>
        <?php
        if (($data["GIT_VIEW"] ?? "") === "diff") {
            $this->renderGitDiff($data);
        } else if (!empty($data["GIT_VIEW"])) {
            $this->renderGitHistory($data);
        } else if (isset($data["GIT_FILE"]) ||
            !empty($data["GIT_FILE_MEDIA"]) ||
            !empty($data["GIT_FILE_BINARY"]) ||
            !empty($data["GIT_FILE_TOO_LARGE"])) { ?>
            <div class="git-file">
            <?php if (!empty($data["GIT_FILE_MEDIA"])) {
                $media_kind = $data["GIT_FILE_MEDIA_KIND"] ?? "image";
                if ($media_kind === "pdf") { ?>
                    <embed class="git-file-pdf" type="application/pdf"
                        src="<?= $data["GIT_FILE_MEDIA"] ?>">
                <?php } else if ($media_kind === "audio") { ?>
                    <audio class="git-file-audio" controls
                        src="<?= $data["GIT_FILE_MEDIA"] ?>"></audio>
                <?php } else if ($media_kind === "video") { ?>
                    <video class="git-file-video" controls
                        src="<?= $data["GIT_FILE_MEDIA"] ?>"></video>
                <?php } else { ?>
                    <img class="git-file-image" alt=""
                        src="<?= $data["GIT_FILE_MEDIA"] ?>">
                <?php }
            } else if (!empty($data["GIT_FILE_BINARY"])) { ?>
                <p class="git-note"><?= tl("wiki_element_git_binary") ?></p>
            <?php } else if (!empty($data["GIT_FILE_TOO_LARGE"])) { ?>
                <p class="git-note"><?= tl("wiki_element_git_too_large") ?></p>
            <?php } else { ?>
                <pre class="git-blob"><?= $data["GIT_FILE"] ?></pre>
            <?php } ?>
            </div>
            <?php
        } else { ?>
            <div class="git-listing-box">
            <table class="git-listing" id="git-listing">
            <tr><th onclick="gitTableSort(this)"><div class="resizable"><?=
                tl("wiki_element_git_name") ?></div></th>
                <th class="git-hide-narrow" onclick="gitTableSort(this)"><div
                class="resizable"><?=
                tl("wiki_element_git_message") ?></div></th>
                <th class="git-hide-narrow" onclick="gitTableSort(this)"><div
                class="resizable"><?=
                tl("wiki_element_git_author") ?></div></th>
                <th data-sort-type="number" onclick="gitTableSort(this)"><?=
                tl("wiki_element_git_age") ?></th></tr>
            <?php
            if (!empty($data["GIT_PARENT_URL"])) {
                e("<tr class='git-no-sort'><td class='git-name-col'><a href='"
                    . $data["GIT_PARENT_URL"] . "'>&#8593; " .
                    tl("wiki_element_parent_folder") . "</a></td>" .
                    "<td class='git-commit-col git-hide-narrow'></td>" .
                    "<td class='git-author-col git-hide-narrow'></td>" .
                    "<td class='git-age-col'></td></tr>");
            }
            $time_formatter = $this->view->helper('timeformatter');
            foreach ($data["GIT_ENTRIES"] as $entry) {
                $suffix = ($entry["IS_DIR"]) ? "/" : "";
                $commit_time = $entry["COMMIT_TIME"] ?? 0;
                e("<tr><td class='git-name-col'><a href='" .
                    $entry["URL"] . "'>" . $entry["NAME"] . $suffix .
                    "</a></td>" .
                    "<td class='git-commit-col git-hide-narrow'>" .
                    ($entry["COMMIT_SUMMARY"] ?? "") . "</td>" .
                    "<td class='git-author-col git-hide-narrow'>" .
                    ($entry["COMMIT_AUTHOR"] ?? "") . "</td>" .
                    "<td class='git-age-col' data-sort-key='" .
                    $commit_time . "'>" .
                    $time_formatter->timeAgo($commit_time) .
                    "</td></tr>");
            } ?>
            </table>
            </div>
            <?php if (!empty($data["GIT_README_HTML"])) {
                $this->renderGitReadme($data);
            } ?>
        <?php } ?>
        </div>
        <?php
    }
    /**
     * Draws the issue view of a Git repository page. Along the top a search
     * box stretches across the activity, ending in a magnifying-glass
     * button and a funnel button that drops down the choices for narrowing
     * the list: unassigned, open, closed, or the visitor's own issues. For a
     * visitor allowed to report one there is also a beetle button that shows
     * or hides the new-issue form. That form reads like a small editor, its
     * fields hinted by placeholders rather than labels, and it lets the
     * reporter pick the branch and the version the issue was seen on but not
     * its urgency. Beneath comes the list of issues, each a bordered row with
     * a narrow number, a title, a status, and when it was last touched.
     *
     * @param array $data view data holding the prepared issue rows, the
     *      branch and version choices, and, for a visitor who may report one,
     *      the form's security token
     */
    private function renderGitIssues($data)
    {
        if (!empty($data["GIT_ISSUE_DETAIL"])) {
            $this->renderGitIssueDetail($data);
            return;
        }
        $icon_helper = $this->view->helper('iconlink');
        $can_report = !empty($data["GIT_ISSUE_CAN_REPORT"]);
        $filter_url = $data["GIT_ISSUE_FILTER_URL"] ?? "";
        ?>
        <div class="git-issues">
        <div class="git-issue-bar">
        <?php
        $active_filter = $data["GIT_ISSUE_FILTER"] ?? "open";
        $filter_active = ($active_filter !== "all") ?
            " git-issue-filter-active" : "";
        $filter_options = [
            "all" => tl("wiki_element_git_issue_all"),
            "open" => tl("wiki_element_git_issue_open_issues"),
            "closed" => tl("wiki_element_git_issue_closed_issues"),
            "reported" => tl("wiki_element_git_issue_reported"),
            "assigned" => tl("wiki_element_git_issue_assigned"),
            "marked_fixed" => tl("wiki_element_git_issue_marked_fixed"),
            "marked_wont_fix" =>
                tl("wiki_element_git_issue_marked_wont_fix"),
            "mine" => tl("wiki_element_git_issue_mine"),
        ];
        ?>
        <span class="git-issue-search-box">
        <input type="text" id="git-issue-search" class="git-issue-search"
            placeholder="<?= tl("wiki_element_git_issue_search") ?>"
            oninput="gitIssueFilter(this)">
        <?php $icon_helper->renderFormButton("button", "search", false,
            "icon-button-container git-issue-search-button",
            "onclick=\"gitIssueFilter(elt('git-issue-search'))\""); ?>
        <span class="git-issue-filter-wrap">
        <?php $icon_helper->renderFormButton("button", "filter", false,
            "icon-button-container git-issue-filter-button" . $filter_active,
            "onclick=\"toggleGitPanel('git-issue-filter-menu')\""); ?>
        <div id="git-issue-filter-menu"
            class="git-panel git-issue-filter-menu">
        <?php foreach ($filter_options as $filter_key => $filter_label) {
            $mark = ($filter_key === $active_filter) ?
                "<span class='git-issue-filter-check'>&#10003;</span> " : "";
            e("<a href='" . $filter_url . $filter_key . "'>" . $mark .
                $filter_label . "</a>");
        } ?>
        </div>
        </span>
        </span>
        <?php if ($can_report) {
            $icon_helper->renderFormButton("button", "new_issue", false,
                "icon-button-container git-issue-new-button",
                "onclick=\"toggleGitPanel('git-issue-form')\"");
        } ?>
        </div>
        <div class="git-issue-search-hint"><?=
            tl("wiki_element_git_issue_search_hint") ?></div>
        <?php if ($can_report) { ?>
        <div id="git-issue-form" class="git-panel git-issue-form">
        <form method="post" action="<?= $data["GIT_ISSUES_URL"] ?>">
        <input type="hidden" name="<?= C\p('CSRF_TOKEN') ?>"
            value="<?= $data["GIT_ISSUE_TOKEN"] ?>">
        <input type="text" class="git-issue-title" name="issue_title"
            placeholder="<?= tl("wiki_element_git_issue_title") ?>">
        <textarea class="git-issue-body" name="issue_description"
            placeholder="<?=
            tl("wiki_element_git_issue_description") ?>"></textarea>
        <div class="git-issue-form-foot">
        <select name="issue_branch" class="git-issue-select"
            title="<?= tl("wiki_element_git_issue_branch") ?>">
        <?php foreach ($data["GIT_ISSUE_BRANCHES"] as $branch_name) {
            $selected = ($branch_name ===
                ($data["GIT_ISSUE_DEFAULT_BRANCH"] ?? "")) ?
                " selected='selected'" : "";
            e("<option value='" . $branch_name . "'" . $selected . ">" .
                $branch_name . "</option>");
        } ?>
        </select>
        <select name="issue_version" class="git-issue-select"
            title="<?= tl("wiki_element_git_issue_version") ?>">
        <option value="current"><?=
            tl("wiki_element_git_issue_current") ?></option>
        <?php foreach ($data["GIT_ISSUE_VERSIONS"] as $version_name) {
            e("<option value='" . $version_name . "'>" . $version_name .
                "</option>");
        } ?>
        </select>
        <button type="submit" class="git-issue-submit"><?=
            tl("wiki_element_git_issue_new") ?></button>
        </div>
        </form>
        </div>
        <?php } ?>
        <?php if (empty($data["GIT_ISSUE_ROWS"])) { ?>
        <p class="git-note"><?= tl("wiki_element_git_no_issues") ?></p>
        <?php } else { ?>
        <div class="git-issue-list-box">
        <table class="git-issue-list" id="git-issue-list">
        <tr><th class="git-issue-num-col" data-sort-type="number"
                onclick="gitTableSort(this)"><div class="resizable"><?=
                tl("wiki_element_git_issue_number") ?></div></th>
            <th onclick="gitTableSort(this)"><div class="resizable"><?=
                tl("wiki_element_git_issue_priority") ?></div></th>
            <th onclick="gitTableSort(this)"><div class="resizable"><?=
                tl("wiki_element_git_issue_summary") ?></div></th>
            <th onclick="gitTableSort(this)"><div class="resizable"><?=
                tl("wiki_element_git_issue_status") ?></div></th>
            <th class="git-hide-narrow"
                onclick="gitTableSort(this)"><div class="resizable"><?=
                tl("wiki_element_git_issue_user") ?></div></th>
            <th class="git-hide-narrow"
                onclick="gitTableSort(this)"><?=
                tl("wiki_element_git_issue_updated") ?></th></tr>
        <?php
        $time_formatter = $this->view->helper('timeformatter');
        foreach ($data["GIT_ISSUE_ROWS"] as $row) {
            $status_word = $this->gitIssueStatusWord($row["STATUS"]);
            $updated_time = $row["UPDATED_TIME"];
            if ($updated_time > 0 &&
                (time() - $updated_time) < C\ONE_DAY) {
                $updated = $time_formatter->timeAgo($updated_time);
            } else {
                $updated = date("Y-m-d", $updated_time);
            }
            e("<tr class='git-issue-row' data-number='" . $row["NUMBER"] .
                "' data-reporter='" . $row["REPORTER"] .
                "' data-assignee='" . $row["ASSIGNEE"] .
                "' data-status='" . $status_word .
                "' data-updated='" . date("Y-m-d", $row["UPDATED_TIME"]) .
                "'>" .
                "<td class='git-issue-num-col'><a href='" . $row["URL"] .
                "'>#" . $row["NUMBER"] . "</a></td>" .
                "<td class='git-issue-priority-col'>" .
                $this->gitIssuePriorityMarkup($row["PRIORITY"]) . "</td>" .
                "<td class='git-issue-summary-col'><a href='" .
                $row["URL"] . "'>" . $row["TITLE"] . "</a></td>" .
                "<td>" . $status_word . "</td>" .
                "<td class='git-hide-narrow'>" .
                $row["STATUS_USER"] . "</td>" .
                "<td class='git-hide-narrow'>" . $updated .
                "</td></tr>");
        } ?>
        </table>
        </div>
        <?php } ?>
        </div>
        <?php
    }
    /**
     * Makes the small status marker shown for an issue: a red dot for one
     * only reported, a figure for one assigned, a check for one marked
     * fixed, and a no-entry sign for one marked won't fix. Each carries the
     * status name so hovering or a screen reader still gives it in words.
     *
     * @param string $status one of the four display-status names
     * @return string the marker as a span ready to place in a cell
     */
    /**
     * Gives the status of an issue in words for the status column and for
     * the read-only status line: reported, assigned, marked fixed, or marked
     * won't fix. Words are used rather than a bare coloured mark so the
     * meaning is plain.
     *
     * @param string $status one of the four display-status names
     * @return string the status in words
     */
    private function gitIssueStatusWord($status)
    {
        if ($status === L\WikiIssue::DISPLAY_ASSIGNED) {
            return tl("wiki_element_git_issue_assigned");
        }
        if ($status === L\WikiIssue::DISPLAY_FIXED) {
            return tl("wiki_element_git_issue_marked_fixed");
        }
        if ($status === L\WikiIssue::DISPLAY_WONT_FIX) {
            return tl("wiki_element_git_issue_marked_wont_fix");
        }
        return tl("wiki_element_git_issue_reported");
    }
    /**
     * Draws the priority of an issue as a small marked shape for the issue
     * list: one exclamation in a circle for low, two in a triangle for
     * medium, and three in a stop sign for high, so urgency reads at a
     * glance and takes little width on a narrow screen. An unknown priority
     * draws nothing.
     *
     * @param int $priority the issue's priority, low, medium, or high
     * @return string the marked-shape HTML, or an empty string
     */
    private function gitIssuePriorityMarkup($priority)
    {
        $level = (int)$priority;
        $marks = [L\WikiIssue::PRIORITY_LOW => "!",
            L\WikiIssue::PRIORITY_MEDIUM => "!!",
            L\WikiIssue::PRIORITY_HIGH => "!!!"];
        $shapes = [L\WikiIssue::PRIORITY_LOW => "git-priority-low",
            L\WikiIssue::PRIORITY_MEDIUM => "git-priority-medium",
            L\WikiIssue::PRIORITY_HIGH => "git-priority-high"];
        if (!isset($marks[$level])) {
            return "";
        }
        $labels = [L\WikiIssue::PRIORITY_LOW =>
            tl("wiki_element_git_priority_low"),
            L\WikiIssue::PRIORITY_MEDIUM =>
            tl("wiki_element_git_priority_medium"),
            L\WikiIssue::PRIORITY_HIGH =>
            tl("wiki_element_git_priority_high")];
        return "<span class='git-priority " . $shapes[$level] .
            "' title='" . $labels[$level] . "'>" . $marks[$level] .
            "</span>";
    }
    /**
     * Draws the detail page for one issue. At the top a back link returns to
     * the list, followed by the issue number. Then comes the dated list of
     * status changes it has been through, each naming who made it. A group
     * editor gets a status control set to the current status: choosing
     * assigned reveals a username box and choosing marked fixed reveals a
     * commit box, each with an arrow to send it, while the other choices send
     * at once; a reader who cannot edit just sees the status in words. The
     * priority follows the same way, then the title and description.
     *
     * @param array $data view data holding the prepared issue detail
     */
    private function renderGitIssueDetail($data)
    {
        $can_edit = !empty($data["GIT_ISSUE_CAN_EDIT"]);
        $status = $data["GIT_ISSUE_DISPLAY_STATUS"];
        $priority = (int)$data["GIT_ISSUE_PRIORITY"];
        ?>
        <div class="git-issues git-issue-detail">
        <div class="git-issue-detail-head">
        <a class="git-issue-back"
            href="<?= $data["GIT_ISSUES_URL"] ?>">&laquo;
            <?= tl("wiki_element_git_issue_back") ?></a>
        <span class="git-issue-detail-number"><?=
            tl("wiki_element_git_issue_number") ?>
            #<?= $data["GIT_ISSUE_NUMBER"] ?></span>
        </div>
        <div class="git-issue-detail-info">
        <ul class="git-issue-history">
        <?php
        /* The first line of an issue's history says when it was reported
           and by whom, and so stands for the issue itself; the way to
           throw the issue away sits on that line, where someone allowed
           to edit will look for it. */
        $is_first_line = true;
        foreach ($data["GIT_ISSUE_HISTORY"] as $line) {
            $delete_markup = "";
            if ($is_first_line && !empty($data["GIT_ISSUE_DELETE_URL"])) {
                $delete_markup = " " .
                    $this->view->helper("iconlink")->renderButton(
                    $data["GIT_ISSUE_DELETE_URL"], "delete", "", true);
            }
            e("<li><span class='git-issue-history-when'>" . $line["WHEN"] .
                "</span> " . $line["LABEL"] . $delete_markup . "</li>");
            $is_first_line = false;
        } ?>
        </ul>
        <?php if ($can_edit) { ?>
        <form class="git-issue-change" method="post"
            action="<?= $data["GIT_ISSUE_DETAIL_URL"] ?>">
        <input type="hidden" name="<?= C\p('CSRF_TOKEN') ?>"
            value="<?= $data["GIT_ISSUE_TOKEN"] ?>">
        <label class="git-issue-detail-label"><?=
            tl("wiki_element_git_issue_status") ?>:</label>
        <select name="issue_action" class="git-issue-select"
            onchange="gitIssueStatusChange(this)">
        <option value="reported"<?=
            ($status === L\WikiIssue::DISPLAY_REPORTED) ?
            " selected='selected'" : "" ?>><?=
            tl("wiki_element_git_issue_reported") ?></option>
        <option value="assigned"<?=
            ($status === L\WikiIssue::DISPLAY_ASSIGNED) ?
            " selected='selected'" : "" ?>><?=
            tl("wiki_element_git_issue_assigned") ?></option>
        <option value="marked_fixed"<?=
            ($status === L\WikiIssue::DISPLAY_FIXED) ?
            " selected='selected'" : "" ?>><?=
            tl("wiki_element_git_issue_marked_fixed") ?></option>
        <option value="marked_wont_fix"<?=
            ($status === L\WikiIssue::DISPLAY_WONT_FIX) ?
            " selected='selected'" : "" ?>><?=
            tl("wiki_element_git_issue_marked_wont_fix") ?></option>
        </select>
        <span id="git-issue-assignee-group" class="git-issue-change-field">
        <input type="text" name="issue_assignee"
            placeholder="<?= tl("wiki_element_git_issue_username") ?>">
        <button type="submit" class="git-issue-arrow"
            title="<?= tl("wiki_element_git_issue_apply") ?>">&#8594;</button>
        </span>
        <span id="git-issue-commit-group" class="git-issue-change-field">
        <input type="text" name="issue_commit"
            placeholder="<?= tl("wiki_element_git_issue_commit") ?>">
        <button type="submit" class="git-issue-arrow"
            title="<?= tl("wiki_element_git_issue_apply") ?>">&#8594;</button>
        </span>
        </form>
        <form class="git-issue-priority-form" method="post"
            action="<?= $data["GIT_ISSUE_DETAIL_URL"] ?>">
        <input type="hidden" name="<?= C\p('CSRF_TOKEN') ?>"
            value="<?= $data["GIT_ISSUE_TOKEN"] ?>">
        <label class="git-issue-detail-label"><?=
            tl("wiki_element_git_issue_priority") ?>:</label>
        <select name="issue_priority" class="git-issue-select"
            onchange="this.form.submit()">
        <option value="1"<?= ($priority == 1) ?
            " selected='selected'" : "" ?>><?=
            tl("wiki_element_git_issue_low") ?></option>
        <option value="2"<?= ($priority == 2) ?
            " selected='selected'" : "" ?>><?=
            tl("wiki_element_git_issue_medium") ?></option>
        <option value="3"<?= ($priority == 3) ?
            " selected='selected'" : "" ?>><?=
            tl("wiki_element_git_issue_high") ?></option>
        </select>
        </form>
        <?php } else { ?>
        <div class="git-issue-detail-line"><?=
            tl("wiki_element_git_issue_status") ?>:
            <?= $this->gitIssueStatusWord($status) ?></div>
        <div class="git-issue-detail-line"><?=
            tl("wiki_element_git_issue_priority") ?>: <?php
        if ($priority >= 3) {
            e(tl("wiki_element_git_issue_high"));
        } else if ($priority == 2) {
            e(tl("wiki_element_git_issue_medium"));
        } else {
            e(tl("wiki_element_git_issue_low"));
        } ?></div>
        <?php } ?>
        <div class="git-issue-detail-title-row">
        <h3 class="git-issue-detail-title"><?=
            $data["GIT_ISSUE_TITLE"] ?></h3>
        <span class="git-issue-comment-controls">
        <button type="button" class="git-issue-comment-latest"
            title="<?= tl("wiki_element_git_issue_latest") ?>"
            onclick="gitIssueScrollComments()">&#8595;</button>
        <?php if (!empty($data["GIT_ISSUE_CAN_COMMENT"])) { ?>
        <button type="button" class="git-issue-comment-add"
            title="<?= tl("wiki_element_git_issue_add_comment") ?>"
            onclick="toggleDisplay('add-comment')">&#128172;</button>
        <?php } ?>
        </span>
        </div>
        <div class="git-issue-comments" id="git-issue-comments">
        <?php foreach ($data["GIT_ISSUE_COMMENTS"] as $comment) {
            e("<div class='git-issue-comment'>" .
                "<div class='git-issue-comment-head'>" .
                "<span class='git-issue-comment-who'>" . $comment["NAME"] .
                "</span> <span class='git-issue-comment-when'>" .
                $comment["WHEN"] . "</span></div>" .
                "<div class='git-issue-comment-body'>" . $comment["BODY"] .
                "</div></div>");
        } ?>
        </div>
        <?php if (!empty($data["GIT_ISSUE_CAN_COMMENT"])) { ?>
        <div id="add-comment" class="git-issue-comment-form">
        <form method="post" action="<?= $data["GIT_ISSUE_DETAIL_URL"] ?>">
        <input type="hidden" name="<?= C\p('CSRF_TOKEN') ?>"
            value="<?= $data["GIT_ISSUE_TOKEN"] ?>">
        <textarea id="comment-add-comment" name="description"
            class="medium-text-area"
            data-buttons="all,!wikibtn-search,!wikibtn-heading,!wikibtn-slide"
            ></textarea>
        <script>
        document.write('<div class="upload-box">' +
            '<input type="file" id="file-add-comment" ' +
            'name="file_add-comment" class="none" multiple="multiple" >' +
            '<?= tl("groupfeed_element_drag_textarea") ?>' +
            '<a href="javascript:elt(\'file-add-comment\').click()"><?=
            tl("groupfeed_element_click_textarea") ?></a></div>');
        </script>
        <button class="button-box float-opposite" type="submit"><?=
            tl("wiki_element_git_issue_comment_send") ?></button>
        <div>&nbsp;<br><br></div>
        </form>
        </div>
        <?php } ?>
        </div>
        </div>
        <?php
    }
    /**
     * Draws the repository statistics beneath a Git page's file listing: a
     * line of totals followed by small bar charts of the busiest authors,
     * the commits made each month, and the commonest file endings. Nothing
     * is drawn when a page has no statistics, such as an empty repository.
     *
     * @param array $data view data holding the prepared statistics
     */
    private function renderGitStatistics($data)
    {
        if (!isset($data["GIT_STATS_COMMITS"])) {
            return;
        }
        ?><div class="git-stats">
        <h2><?= tl("wiki_element_git_statistics") ?></h2>
        <p class="git-stats-totals"><?=
            tl("wiki_element_git_stats_totals", $data["GIT_STATS_COMMITS"],
            $data["GIT_STATS_FILES"]) ?></p>
        <?php
        $this->renderGitStatGroup(tl("wiki_element_git_stats_authors"),
            $data["GIT_STATS_AUTHORS"], "authors");
        $this->renderGitStatGroup(tl("wiki_element_git_stats_months"),
            $data["GIT_STATS_MONTHS"], "months");
        $this->renderGitStatGroup(tl("wiki_element_git_stats_types"),
            $data["GIT_STATS_TYPES"], "types");
        ?>
        </div><?php
    }
    /**
     * Draws one titled group of statistic bars. Each row shows its label, a
     * bar whose width is already worked out as a percentage, and the count.
     * Every row is drawn; the rows sit in a box tall enough for a useful
     * number of them (set per group in the style sheet by the group key) and
     * the box scrolls when there are more, so a long group does not fill the
     * page yet nothing is hidden behind a control.
     *
     * @param string $title heading for the group, already localized
     * @param array $bars rows to draw, each with a label, a count, and a
     *      bar width
     * @param string $key short name unique to this group, used to pick the
     *      box height in the style sheet
     */
    private function renderGitStatGroup($title, $bars, $key)
    {
        if (empty($bars)) {
            return;
        }
        ?><div class="git-stats-group">
        <h3><?= $title ?></h3>
        <div class="git-stats-rows git-stats-<?= $key ?>">
        <?php foreach ($bars as $bar) {
            $this->renderGitStatRow($bar);
        } ?>
        </div>
        </div><?php
    }
    /**
     * Draws a single statistic row: its label, the bar whose width is
     * already worked out as a percentage, and the count. A file ending that
     * is empty is shown with a readable stand-in name.
     *
     * @param array $bar the row to draw, with a label, a count, and a bar
     *      width
     */
    private function renderGitStatRow($bar)
    {
        $label = ($bar["label"] === "") ?
            tl("wiki_element_git_no_ending") : $bar["label"];
        ?><div class="git-stat-row">
        <span class="git-stat-label"><?= $label ?></span>
        <span class="git-stat-track"><span class="git-stat-bar"
            style="width: <?= $bar["width"] ?>%"></span></span>
        <span class="git-stat-count"><?= $bar["count"] ?></span>
        </div><?php
    }
    /**
     * Draws the commit list or the tag list in place of the file listing.
     * A header row names the columns, the first page of rows is placed in
     * the table body, and a small script wires the box so that scrolling
     * near its bottom fetches and appends the next page. The rows and the
     * address to fetch further pages from are prepared and escaped by the
     * controller.
     *
     * @param array $data view data holding GIT_VIEW, GIT_LIST_ROWS,
     *      GIT_LIST_SCROLL_URL, and GIT_LIST_PAGE_SIZE
     */
    private function renderGitHistory($data)
    {
        $is_tags = ($data["GIT_VIEW"] === "tags");
        e('<div id="git-list-box" class="git-listing-box">');
        e('<table class="git-listing git-history"><thead><tr>');
        if ($is_tags) {
            e($this->gitSortHeader($data, "tag",
                tl("wiki_element_git_tag"), "git-name-col"));
            e($this->gitSortHeader($data, "date",
                tl("wiki_element_git_date"), "git-date-col"));
        } else {
            e($this->gitSortHeader($data, "date",
                tl("wiki_element_git_date"), "git-date-col"));
            e($this->gitSortHeader($data, "author",
                tl("wiki_element_git_author"), "git-author-col"));
        }
        e($this->gitSortHeader($data, "message",
            tl("wiki_element_git_message"), "git-msg-col"));
        e('<th class="git-actions-col">' .
            tl("wiki_element_git_actions") . '</th></tr></thead>');
        e('<tbody id="git-list-rows">' . $data["GIT_LIST_ROWS"] .
            '</tbody></table></div>');

    }
    /**
     * Builds one clickable column header for the commit or tag list. The
     * header links back to the same list re-sorted by this column; if the
     * list is already sorted by it, the link flips the direction and an
     * arrow shows which way it currently runs. The link address is the
     * escaped base the controller prepared, so this only assembles it.
     *
     * @param array $data view data holding GIT_LIST_SORT_BASE and the
     *      current GIT_LIST_SORT and GIT_LIST_DIR
     * @param string $column the column key to sort by ("date", "author",
     *      "message", or "tag")
     * @param string $label the heading text to show
     * @param string $css_class the column's cell class
     * @return string the header cell as HTML
     */
    private function gitSortHeader($data, $column, $label, $css_class)
    {
        $base = $data["GIT_LIST_SORT_BASE"] ?? "";
        $current = $data["GIT_LIST_SORT"] ?? "";
        $direction = $data["GIT_LIST_DIR"] ?? "desc";
        $next = ($current === $column && $direction === "asc") ?
            "desc" : "asc";
        $arrow = "";
        if ($current === $column) {
            $arrow = ($direction === "asc") ? " &#9650;" : " &#9660;";
        }
        return '<th class="' . $css_class . '"><a class="git-sort" ' .
            'href="' . $base . "&repo_sort=" . $column . "&repo_dir=" .
            $next . '">' . $label . $arrow . '</a></th>';
    }
    /**
     * Draws one commit's changes: its message and author, then each
     * changed file with a line by line comparison of its earlier and later
     * contents. Every value is prepared and escaped by the controller, so
     * this method only lays them out.
     *
     * @param array $data view data holding GIT_DIFF_SUBJECT,
     *      GIT_DIFF_AUTHOR, GIT_DIFF_FILES, and GIT_DIFF_EMPTY
     */
    private function renderGitDiff($data)
    {
        ?>
        <div class="git-diff">
        <p class="git-diff-head"><?= $data["GIT_DIFF_SUBJECT"] ?>
        <span class="git-diff-author"><?=
            $data["GIT_DIFF_AUTHOR"] ?></span></p>
        <?php if (!empty($data["GIT_DIFF_EMPTY"])) { ?>
            <p class="git-note"><?=
                tl("wiki_element_git_no_changes") ?></p>
        <?php } else {
            e($data["GIT_DIFF_FILES"]);
        } ?>
        </div>
        <?php
    }
    /**
     * Draws the README box that sits under a Git repository's file list: a
     * bar carrying a book icon and the word README, an optional contents
     * button that opens the list of the README's own section headings so a
     * reader can jump to one, and then the rendered README itself. The
     * heading list and the rendered README are prepared and escaped by the
     * controller, so this method only lays them out.
     *
     * @param array $data view data holding GIT_README_HTML and, when the
     *      README has more than one heading, GIT_README_TOC
     */
    private function renderGitReadme($data)
    {
        $book = '<span class="git-readme-icon">&#128214;</span>';
        $contents = '<span class="git-readme-icon">&#9776;</span>';
        e('<div class="git-readme-box"><div class="git-readme-bar">');
        e('<span class="git-readme-title">' . $book . ' ' .
            tl("wiki_element_git_readme") . '</span>');
        if (!empty($data["GIT_README_TOC"])) {
            e('<button type="button" class="git-readme-toc-toggle" ' .
                'onclick="toggleGitPanel(\'git-readme-toc\')" ' .
                'aria-label="' .
                tl("wiki_element_git_contents") . '">' . $contents .
                '</button>');
        }
        e('</div>');
        if (!empty($data["GIT_README_TOC"])) {
            e('<ul id="git-readme-toc" class="git-readme-toc git-panel">');
            foreach ($data["GIT_README_TOC"] as $item) {
                e('<li class="git-toc-level' . $item["LEVEL"] . '">' .
                    '<a href="' . $item["URL"] . '">' . $item["TEXT"] .
                    '</a></li>');
            }
            e('</ul>');
        }
        e('<div class="git-readme">' . $data["GIT_README_HTML"] . '</div>');
        e('</div>');
    }
    /**
     * Used to drawn the form that let's someone edit a wiki page
     *
     * @param array $data fields contain data about the page being
     *      edited. In particular, PAGE contains the raw page data
     */
    public function renderEditPageForm($data)
    {
        $icon_helper = $this->view->helper('iconlink');
        $simple_base_url = B\wikiUrl("", true,
            $data['CONTROLLER'], $data['GROUP']['GROUP_ID']) .
            C\p('CSRF_TOKEN').'='.$data[C\p('CSRF_TOKEN')];
        $base_url = htmlentities($simple_base_url);
        $simple_view_url = B\wikiUrl($data['PAGE_NAME'], true,
           $data['CONTROLLER'], $data['GROUP']['GROUP_ID']) .
           C\p('CSRF_TOKEN') . '=' . $data[C\p('CSRF_TOKEN')];
        $simple_current_url = $simple_view_url . "&arg=edit";
        $is_settings_view = !empty($data['settings']) &&
            $data['settings'] != "false";
        $is_resources_view = !empty($data['resources']) &&
            $data['resources'] != "false";
        $is_media_list =
            !empty($data['page_type']) && $data['page_type'] == 'media_list';
        $is_page_alias =
            !empty($data['page_type']) && $data['page_type'] == 'page_alias';
        $is_share =
            !empty($data['page_type']) && $data['page_type'] == 'share';
        $is_shortener =
            !empty($data['page_type']) && $data['page_type'] == 'url_shortener';
        $is_git_repository = !empty($data['page_type']) &&
            $data['page_type'] == 'git_repository';
        if (!empty($data['SUB_PATH'])) {
            $simple_view_url .= "&sf=".urlencode($data['SUB_PATH']);
            $simple_current_url .= "&sf=".urlencode($data['SUB_PATH']);
        }
        $current_url = htmlentities($simple_current_url);
        $new_settings = $is_settings_view ? "&amp;settings=false" :
            "&amp;settings=true";
        $new_resources = $is_resources_view ? "&amp;resources=false" :
            "&amp;resources=true";
        if ($is_resources_view) {
            $simple_current_url .= "&resources=true";
        }
        $settings_toggle_url = $current_url . $new_settings;
        $resources_toggle_url = $current_url . $new_resources;
        if (isset($data['OTHER_BACK_URL'])) {
            $append = $data['OTHER_BACK_URL'];
        }
        $icon_helper = $this->view->helper('iconlink');
        if ($is_settings_view || ($is_resources_view && !$is_media_list)) {
            $toggle_url = ($is_settings_view) ? $settings_toggle_url :
                $resources_toggle_url;
            ?>
            <div class="float-opposite" ><?=
            $this->view->helper('close')->render($toggle_url);
            ?></div>
            <?php
        } else if (!$is_git_repository) {?>
            <div class="float-opposite wiki-history-discuss" >
            <?=
            $icon_helper->renderButton( $base_url . $append .
                '&amp;arg=history&amp;page_id='.$data['PAGE_ID'], "history");
            ?>
            </div><?php
        }?>
        <form id="editpageForm" method="post"
            enctype="multipart/form-data"
            onsubmit="
                var caret_pos = elt('caret-pos');
                var scroll_top = elt('scroll-top');
                var wiki_page = elt('wiki-page');
                if (caret_pos && scroll_top && wiki_page) {
                    caret_pos.value =
                    (wiki_page.selectionStart) ?
                    wiki_page.selectionStart : 0;
                    scroll_top.value= (wiki_page.scrollTop) ?
                    wiki_page.scrollTop : 0;
                }" >
            <input type="hidden" name="c" value="<?=$data['CONTROLLER']
            ?>" >
            <input type="hidden" name="<?=C\p('CSRF_TOKEN') ?>" value="<?=
                $data[C\p('CSRF_TOKEN')] ?>" >
            <input type="hidden" name="a" value="wiki" >
            <input type="hidden" name="arg" value="edit" >
            <?php
            if (isset($data['RESOURCE_NAME'])) { ?>
                <input type="hidden" name="n" value="<?=
                    urlencode($data['RESOURCE_NAME']) ?>" >
                <?php
            }
            if (isset($data['BACK_PARAMS'])) {
                foreach ($data["BACK_PARAMS"] as
                         $back_param_key => $back_param_value) {
                    e('<input type="hidden" '
                        . 'name="' . $back_param_key .
                        '" value="' .
                        $back_param_value
                        . '" >');
                }
            }
            ?>
            <input type="hidden" name="group_id" value="<?=
                $data['GROUP']['GROUP_ID'] ?>" >
            <input type="hidden" name="page_name" value="<?=
                $data['PAGE_NAME'] ?>" >
            <input type="hidden" name="caret" id="caret-pos" >
            <input type="hidden" name="scroll_top" id="scroll-top" >
            <input type="hidden" id="p-settings" name="settings" value="<?=
                $data['settings'] ?>" >
            <input type="hidden" id="p-resources" name="resources" value="<?=
                $data['resources'] ?>" >
            <div class="top-margin">
                <b><?=tl('wiki_element_locale_name',
                    $data['CURRENT_LOCALE_TAG']) ?></b><br>
                <b><label for="wiki-page"><?php
                $human_page_name = preg_replace("/\_/", " ",
                    urldecode($data['PAGE_NAME']));
                e(tl('wiki_element_page', $human_page_name));
                if (isset($data['RESOURCE_NAME'])) {
                    e("<br>" .
                        tl('wiki_element_resource_name',
                        $data['RESOURCE_NAME']));
                    ?></label></b><?php
                } else {
                    ?></label></b><?php
                    if (!$is_settings_view &&
                        (!$is_resources_view || $is_media_list)) { ?>
                        <div id="toggle-settings">
                        <?php
                        $icon_helper->renderButton(['settings-toggle-button',
                            $settings_toggle_url], 'settings', "");
                        if (!$is_media_list && !$is_page_alias && !$is_share &&
                            !$is_shortener && !$is_git_repository) {
                            $icon_helper->renderButton(
                                ['resources-toggle-button',
                                $resources_toggle_url], 'resources', "");
                        }
                        if (!empty($data["ADMIN"]) ||
                            !empty($data['GROUP']['PAGE_LIST_ALLOWED'])) {
                            $icon_helper->renderButton(htmlentities(
                                B\wikiUrl("pages", true, "group",
                                $data['GROUP']['GROUP_ID'])) .
                                C\p('CSRF_TOKEN') .'=' .
                                    $data[C\p('CSRF_TOKEN')],
                                'pages', "", false,
                                "media-buttons-container small-margin" ,
                                "media-anchor-button small-font", true);
                        }
                        if (!$is_git_repository) {
                            $syntax_page = intval(
                                $data["GROUP"]["RENDER_ENGINE"] ??
                                C\MEDIAWIKI_ENGINE) == C\MARKDOWN_ENGINE ?
                                "Markdown_Syntax" : "Syntax";
                            $icon_helper->renderButton(htmlentities(
                                B\wikiUrl($syntax_page,
                                true, $data['CONTROLLER'],
                                C\PUBLIC_GROUP_ID)) . C\p('CSRF_TOKEN') . '=' .
                                $data[C\p('CSRF_TOKEN')] . '&amp;arg=read',
                                'wiki_syntax');
                        }?>
                        </div><?php
                    }
                }
                ?>
            </div>
            <?php if (!($is_git_repository && !$is_settings_view)) { ?>
            <div id='page-settings'>
            <div class="top-margin">
            <label for="page-type"><b><?=tl('wiki_element_page_type')
            ?></b></label><?php
            $attributes = ($is_settings_view) ? [] : ['disabled' => 'disabled'];
            $this->view->helper("options")->render("page-type", "page_type",
                $data['page_types'], $data['current_page_type'], true,
                $attributes);
            ?>
            </div>
            <div id='alias-type'>
            <div class="top-margin">
            <label for="page-alias"><b><?=tl('wiki_element_page_alias')
            ?></b></label><input type="text" id='page-alias'
                name="page_alias" value="<?= $data['page_alias']?>"
                maxlength="<?= C\SHORT_TITLE_LEN ?>" class="wide-field" >
            </div>
            </div><?php
            $shortened_url = '';
            if (!empty($data['url_shortener'])) {
                $shortened_url = C\baseUrl() . "group/" .
                    $_REQUEST['group_id'] . "/" .
                    $_REQUEST['page_name'];
            }?>
            <div id='shortener-container'>
            <div class="top-margin">
            <label for="url-shortener"><b><?=tl('wiki_element_url_to_shorten')
            ?></b></label>
            <input type="text" id='url-shortener' name="url_shortener"
                value="<?= $data['url_shortener']?>" class="wide-field" >
            </div>
            </div>
            <div id='short-url-label'>
            <div class="top-margin">
            <b><?=tl('wiki_element_short_url') ?></b> <span class="gray"><?=
                $shortened_url?></span>
            </div>
            <div id="day-chart" ></div>
            <div id="month-chart" ></div>
            <div id="year-chart" ></div>
            </div>
            <div id='share-container'>
            <div class="top-margin">
            <label for='share-expires'><b><?=tl('wiki_element_share_expires')
            ?></b></label><?php
            $this->view->helper("options")->render(
                "share-expires", "share_expires",
                $data["share_page_expires"], ($data['share_expires'] ??
                C\FOREVER));
            ?>
            </div>
            </div>
            <div id='non-alias-type'>
            <div class="top-margin">
            <label for="page-border"><b><?=tl('wiki_element_page_border')
            ?></b></label><?php
            $this->view->helper("options")->render("page-border","page_border",
                $data['page_borders'], $data['page_border']);
            ?>
            </div>
            <div class="top-margin">
            <label for="page-theme"><b><?=tl('wiki_element_page_theme')
            ?></b></label><?php
            $this->view->helper("options")->render("page-theme","page_theme",
                $data['page_themes'], $data['page_theme']);
            ?>
            </div>
            <div class="top-margin" id="page-toc-setting">
            <label for="page-toc"><b><?=tl('wiki_element_table_of_contents')
            ?></b></label><input type="checkbox" name="toc" value="true"
                <?php
                    $checked = (isset($data['toc']) && $data['toc']) ?
                    'checked="checked"' : '';
                    e( $checked );
                ?> id='page-toc' >
            </div>
            <?php if (!empty($data['can_set_static_html_folder'])) {
                $is_static_folder = !empty($data['static_html_folder']);
            ?>
            <div class="top-margin" id="static-html-folder-setting">
            <label for="static-html-folder"><b><?=
                tl('wiki_element_static_html_folder')
            ?></b></label><input type="checkbox" name="static_html_folder"
                value="true"
                <?php
                    $checked = ($is_static_folder) ?
                        'checked="checked"' : '';
                    e($checked);
                ?> id='static-html-folder'
                onclick="setDisplay('static-html-folder-options',
                    this.checked)" >
            <?php /* Directory Indexes and Index File only say how a
                     folder of files is served, so they are shown only
                     when the page is one. */ ?>
            <div id="static-html-folder-options"<?php
                    e(($is_static_folder) ? '' : ' style="display:none"');
                ?>>
            <div class="top-margin" id="directory-indexes-setting">
            <label for="directory-indexes"><b><?=
                tl('wiki_element_directory_indexes')
            ?></b></label><input type="checkbox" name="directory_indexes"
                value="true"
                <?php
                    $checked = (!empty($data['directory_indexes'])) ?
                        'checked="checked"' : '';
                    e($checked);
                ?> id='directory-indexes' >
            </div>
            <div class="top-margin" id="index-file-setting">
            <label for="index-file"><b><?=
                tl('wiki_element_index_files')
            ?></b></label><br /><?= tl('wiki_element_comma_separated')
            ?><input type="text" id='index-file'
                name="index_file"
                value="<?= $data['index_file'] ?>"
                maxlength="<?= C\SHORT_TITLE_LEN ?>" class="wide-field" >
            </div>
            </div>
            </div>
            <?php } ?>
            <div class="top-margin">
            <label for="public-source"><b><?=tl('wiki_element_public_source')
            ?></b></label><input type="checkbox" name="public_source"
                value="true"
                <?php
                    $checked = (isset($data['public_source'])
                        && $data['public_source']) ?
                    'checked="checked"' : '';
                    e( $checked );
                    $group_source_off =
                        isset($data['GROUP']['PAGE_SOURCE_ALLOWED']) &&
                        !$data['GROUP']['PAGE_SOURCE_ALLOWED'];
                    if ($group_source_off) {
                        e(' disabled="disabled"');
                    }
                ?> id='public-source' ><?php
                if ($group_source_off) { ?>
                <span class="red"><?=
                    tl('wiki_element_group_source_off') ?></span><?php
                } ?>
            </div>
            <div class="top-margin">
            <label for="page-title"><b><?=tl('wiki_element_title')
            ?></b></label><input type="text" id='page-title'
                name="title" value="<?=$data['title'] ?>"
                maxlength="<?= C\SHORT_TITLE_LEN ?>" class="wide-field" >
            </div>
            <div class="top-margin">
            <label for="meta-author"><b><?=tl('wiki_element_meta_author')
            ?></b></label><input type="text" id='meta-author'
                name="author" value="<?= $data['author']?>"
                maxlength="<?= C\LONG_NAME_LEN ?>" class="wide-field" >
            </div>
            <div class="top-margin">
            <label for="page-icon"><b><?=tl('wiki_element_page_icon')
            ?></b></label><div class="inline" style="width:1.5in;">
            <img class='user-icon' id='current-icon' src="<?=
                $data['page_icon'] ?>" alt="<?=
                tl('wiki_element_page_icon_preview') ?>" ><?php
            $this->view->helper("fileupload")->render('current-icon',
                'page_icon', 'page-icon',  C\THUMB_SIZE, 'image',
                ['image/png', 'image/gif', 'image/jpeg', 'image/webp',
                'image/x-icon']); ?>
            </div>
            </div>
            <div class="top-margin">
            <label for="meta-robots"><b><?=tl('wiki_element_meta_robots')
            ?></b></label><input type="text" id='meta-robots'
                name="robots" value="<?= $data['robots'] ?>"
                maxlength="<?=C\LONG_NAME_LEN ?>" class="wide-field" >
            </div>
            <div class="top-margin">
            <label for="meta-description"><b><?=
                tl('wiki_element_meta_description')
            ?></b></label>
            </div>
            <textarea id="meta-description" class="short-text-area"
                name="description" data-buttons='none'><?=$data['description']
            ?></textarea>
            <div class="top-margin">
            <label for="meta-properties"><b><?=
                tl('wiki_element_meta_properties')
            ?></b></label>
            </div>
            <div><?=tl('wiki_element_meta_property_format')?></div>
            <textarea id="meta-properties" class="short-text-area"
                name="properties" data-buttons='none'><?=$data['properties']
            ?></textarea>
            <?php
            if (!empty($_SESSION['USER_ID']) &&
                $_SESSION['USER_ID'] == C\ROOT_ID) { ?>
                <div class="top-margin">
                <label for="alt-path"><b><?=tl('wiki_element_alternative_path')
                ?></b></label><input type="text" id='alt-path'
                    placeholder="<?=tl('wiki_element_empty_use_default')
                    ?>" name="alternative_path" value="<?=
                    $data['alternative_path'] ?>"
                    maxlength="<?=C\LONG_NAME_LEN ?>" class="wide-field" >
                </div>
                <?php
            }
            ?>
            <div class="top-margin">
            <label for="update-description"><b><?=
            tl('wiki_element_resource_lookup') ?></b></label><?php
            $this->view->helper("options")->render("update-description",
                "update_description", $data['update_descriptions'],
                $data['update_description'] ?? "no-lookup");
            ?></div>
            <div class="top-margin">
            <label for="page-header"><b><?=tl('wiki_element_page_header')
            ?></b></label><input type="text" id='page-header'
                name="page_header" value="<?=$data['page_header']?>"
                maxlength="<?=C\SHORT_TITLE_LEN ?>" class="wide-field" >
            </div>
            <div class="top-margin">
            <label for="page-footer"><b><?=tl('wiki_element_page_footer')
            ?></b></label><input type="text" id='page-footer'
                name="page_footer" value="<?=$data['page_footer'] ?>"
                maxlength="<?= C\SHORT_TITLE_LEN ?>" class="wide-field" >
            </div>
            </div>
            </div>
            <?php } ?>
            <?php
            if (!$is_settings_view && !$is_resources_view &&
                $is_git_repository) { ?>
                <div id='page-container'>
                <h2><?= tl("wiki_element_git_access") ?></h2>
                <?php
                $push_url = !empty($data['GIT_AUTH_CLONE_URL']) ?
                    $data['GIT_AUTH_CLONE_URL'] :
                    ($data['GIT_CLONE_URL'] ?? "");
                if (!empty($data['GIT_APP_CODE_BAD_PASSWORD'])) { ?>
                    <div class="red git-app-message"><?=
                        tl("wiki_element_git_bad_password") ?></div>
                <?php } ?>
                <div class="git-clone-line git-open">
                <code class="git-clone-cmd">git clone <?= $push_url ?></code>
                <?php
                $icon_helper->renderFormButton("button", "clone_copy", false,
                    "git-icon-control", 'onclick="gitCopyClone(this)" ' .
                    'data-clone="' . $push_url . '"');
                $icon_helper->renderFormButton("button", "refresh", false,
                    "git-icon-control",
                    'onclick="gitToggleAppCode()"');
                ?>
                </div>
                <?php if (empty($data['GIT_AUTH_CLONE_URL'])) {
                    $prompt = (!empty($data['GIT_APP_CODE_EXPIRED'])) ?
                        tl("wiki_element_git_code_expired") :
                        tl("wiki_element_git_clone_only"); ?>
                    <div id="git-app-prompt" class="git-app-message"><?=
                        $prompt ?></div>
                <?php } ?>
                <div id="git-app-refresh" class="git-panel">
                <div class="git-app-row">
                <input type="password" id="git-app-password"
                    placeholder="<?= tl("wiki_element_password") ?>">
                <select id="git-app-expiry"><?php
                $expiry_labels = [
                    "month" => tl("wiki_element_git_expiry_month"),
                    "three_months" =>
                        tl("wiki_element_git_expiry_three_months"),
                    "six_months" => tl("wiki_element_git_expiry_six_months"),
                    "year" => tl("wiki_element_git_expiry_year"),
                    "never" => tl("wiki_element_git_expiry_never")];
                foreach ($data['GIT_APP_CODE_DURATIONS'] as $duration) {
                    $shown = $expiry_labels[$duration] ?? $duration;
                    e('<option value="' . $duration . '">' . $shown .
                        '</option>');
                }
                ?></select>
                <?php
                $icon_helper->renderFormButton("button", "add", false,
                    "git-icon-control", 'onclick="gitCreateAppCode(this)" ' .
                    'data-controller="' . $data['CONTROLLER'] . '" ' .
                    'data-group="' . $data['GROUP']['GROUP_ID'] . '" ' .
                    'data-page="' . $data['PAGE_NAME'] . '" ' .
                    'data-token-name="' . C\p('CSRF_TOKEN') . '" ' .
                    'data-token-value="' . $data[C\p('CSRF_TOKEN')] . '"');
                ?>
                </div>
                </div>
                <?php $this->renderGitStatistics($data); ?>
                </div>
            <?php
            } else if (!$is_settings_view && !$is_resources_view) { ?>
                <div id='page-container'><?php
                if (!str_starts_with($data['current_page_type'], 't') ||
                    !is_numeric(substr($data['current_page_type'], 1))) {
                    if (empty($data['SPREADSHEET'])) {?>
                        <textarea id="wiki-page"
                            class="tall-text-area" name="page"
                            <?php
                            if ((!isset($data['page_type']) ||
                                    $data['page_type'] != 'presentation')) {
                                $data_buttons = 'all,!wikibtn-slide';
                            } else {
                                $data_buttons = 'all';
                            }?>
                            data-buttons='<?=$data_buttons ?>' ><?=
                            $data['PAGE'] ?></textarea><?php
                    } else {
                        e("<div><a href='javascript:".
                            "spreadsheet.toggleHistograms({".
                            "has_column_headers:true, ignore_columns:[".
                            "\"user_captcha_text\"]})'>".
                            tl('wiki_element_toggle_histograms') .
                            "</a></div>");
                        e("<div id='spreadsheet'>&nbsp;</div>");
                    }
                } else {
                    e($data['PAGE']);
                }
                if (!isset($data['RESOURCE_NAME'])) {
                    ?>
                    <div class="green center"><?php
                    $this->view->helper("fileupload")->render(
                        'wiki-page', 'page_resource', 'wiki-page-resource',
                        min(L\metricToInt(ini_get('upload_max_filesize')),
                        L\metricToInt(ini_get('post_max_size'))), 'textarea',
                        null, true);
                    e(tl('wiki_element_archive_info'));
                    ?></div>
                    <div class="top-margin">
                    <label for="edit-reason"><b><?=
                        tl('wiki_element_edit_reason')
                        ?></b></label><input type="text" id='edit-reason'
                        name="edit_reason" value="" maxlength="<?=
                        C\SHORT_TITLE_LEN ?>"
                        class="wide-field" >
                    </div><?php
                }
                ?></div><?php
            }?>
            <div id="save-container" class="top-margin center"><?php
            $resource_param = "";
            if (!$is_resources_view && isset($data['RESOURCE_NAME'])) { ?>
                <button class="button-box"
                    onclick="window.location='<?=$current_url .
                    "&amp;resources=true";
                    ?>'; return false;" ><?=
                    tl('wiki_element_closebutton') ?></button><?php
                $resource_param = "&amp;arg=media&amp;n=" .
                    $data['RESOURCE_NAME'] . "&amp;page_id=" . $data['PAGE_ID'];
            }
            if (!$is_resources_view &&
                !($is_git_repository && !$is_settings_view)) { ?>
                <button class="button-box" type="submit"><?=
                    tl('wiki_element_savebutton') ?></button>
                <a class="source-anchor-button" target="show"
                    style="position:relative; left:-.04in; right:-.04in;"
                    href='<?=$simple_view_url . $resource_param ?>'><?=
                    tl('wiki_element_show');?></a><?php
            }?>
            </div>
        </form>
        <?php
        if ($is_resources_view || ($is_media_list && !$is_settings_view)) {
            ?><div id="page-resources">
            <h3><?= tl('wiki_element_page_resources')?></h3>
            <p><?= tl('wiki_element_resources_info') ?></p>
            <form id="resource-upload-form" method="post"
                enctype="multipart/form-data">
            <input type="hidden" name="c" value="<?= $data['CONTROLLER']
                ?>" >
            <input type="hidden" name="<?= C\p('CSRF_TOKEN') ?>" value="<?=
                $data[C\p('CSRF_TOKEN')] ?>" >
            <input type="hidden" name="a" value="wiki" >
            <input type="hidden" name="arg" value="edit" >
            <?php
                if (isset($data['BACK_PARAMS'])) {
                    foreach ($data["BACK_PARAMS"] as
                             $back_param_key => $back_param_value) {
                        e('<input type="hidden" '
                            . 'name="' . $back_param_key .
                            '" value="' .
                            $back_param_value
                            . '" >');
                    }
                }
            ?>
            <input type="hidden" name="group_id" value="<?=
                $data['GROUP']['GROUP_ID'] ?>" >
            <input type="hidden" name="page_name" value="<?=
                $data['PAGE_NAME'] ?>" >
            <input type="hidden" id="r-settings" name="settings" value="<?=
                $data['settings'] ?>" >
            <input type="hidden" id="r-resources" name="resources" value="<?=
                $data['resources'] ?>" >
            <div class="center">
            <?php
            $this->view->helper("fileupload")->render(
                'page-resources', 'page_resource',
                'media-page-resource',
                min(L\metricToInt(ini_get('upload_max_filesize')),
                L\metricToInt(ini_get('post_max_size'))),
                'immediate', null, true);
            ?>
            </div>
            </form>
            <?php
                $this->renderResources($data, "", true,
                    'media-page-resource');
                $detail_id = $data['PAGE_ID'];
                $detail_id .= (empty($data['SUB_PATH'])) ?
                    "" : base64_encode($data['SUB_PATH']);
            ?>
            <script>
            function renameResource(old_name, id)
            {
                let name_elt;
                if (typeof id === 'object') {
                    name_elt = id;
                } else {
                    name_elt = elt("resource-"+id);
                }
                let new_name = "";
                if (name_elt) {
                    new_name = name_elt.value;
                }
                if (!name_elt || !new_name) {
                    doMessage('<h1 class=\"red\" ><?=
                        tl("wiki_element_rename_failed") ?></h1>');
                    return;
                }
                if (localStorage) {
                    let detail_name = 'detail<?=$detail_id ?>';
                    let url = localStorage.getItem(detail_name);
                    if (url) {
                        localStorage.setItem(detail_name, url.replace(old_name,
                            new_name));
                    }
                }
                let location = "<?=$simple_current_url?>" +
                    "&new_resource_name=" + encodeURIComponent(new_name) +
                    "&old_resource_name=" +
                        old_name.replace('&quot;','"');
                window.location = location;
            }
            function deleteResource(name, url)
            {
                let confirmed = confirm("<?=
                    tl('wiki_element_delete_operation') ?>");
                if (confirmed) {
                    window.location = url;
                }
            }
            function toggleResourceMenu(button)
            {
                let menu = button.nextElementSibling;
                if (!menu) {
                    return;
                }
                let opening = menu.classList.contains("none");
                let all_menus =
                    document.querySelectorAll(".resource-more-options");
                for (let i = 0; i < all_menus.length; i++) {
                    all_menus[i].classList.add("none");
                }
                if (opening) {
                    menu.classList.remove("none");
                }
            }
            document.addEventListener("click", function(event) {
                if (!event.target.closest(".resource-more-menu")) {
                    let open_menus = document.querySelectorAll(
                        ".resource-more-options");
                    for (let i = 0; i < open_menus.length; i++) {
                        open_menus[i].classList.add("none");
                    }
                }
            });
            function emptyConfirm()
            {
                let really_meant_it =
                    confirm("<?= tl('wiki_element_delete_operation'); ?>");
                if (really_meant_it && localStorage) {
                    let detail_name = 'detail<?=$detail_id ?>';
                    localStorage.removeItem(detail_name);
                }
                return really_meant_it;
            }
            function initializeResourceContainer()
            {
                let container_id = '<?=$data['SCROLL_CONTAINER_ID']?>';
                let popup_id = '<?=$data['SCROLL_CONTAINER_ID']?>-popup'
                if (container_id) {
                    let container =  document.getElementById(container_id);
                    let popup =  document.getElementById(popup_id);
                    container.addEventListener('contextmenu', (event) => {
                        actions_list = document.getElementById(
                            'resource-actions-list');
                        popup.innerHTML = '<ul class="popup-dropdown">' +
                            '<li tabindex="0"><ul>' + actions_list.innerHTML +
                            '</ul></li></ul>';
                        popup.style.position = 'fixed';
                        popup.style.top = (event.clientY - 20) + "px";
                        popup.style.left = (event.clientX - 40) + "px";
                        event.preventDefault();
                    });
                    document.addEventListener('click', (event) => {
                        let popup =  document.getElementById(popup_id);
                        popup.innerHTML = "";
                    });
                }
            }
            initializeResourceContainer();
            </script>
            </div><?php
        }
    }
    /**
     * Draws a list of media resources associated with a wiki page
     *
     * @param array $data fields RESOURCES_INFO contains info on resources
     * @param string $read_mode whether the rendering should be for a media
     *      list in read mode (value "read"), a source mode (value "source"),
     *      or for use on the edit task of any wiki page (value "")
     * @param bool $logged_in whether the user is currently logged in or not
     * @param string $file_upload_id of form element to be set if can upload
     *      files
     */
    public function renderResources($data, $read_mode, $logged_in = true,
        $file_upload_id = '')
    {
        $have_resources = !empty($data['RESOURCES_INFO']['resources']) &&
            count($data['RESOURCES_INFO']['resources']) > 0;
        if (isset($data['RESOURCES_INFO']) && $data['RESOURCES_INFO']) {
            $is_static = ($data['CONTROLLER'] == 'static') ? true : false;
            $token_string = (empty($data[C\p('CSRF_TOKEN')])) ? "" :
                C\p('CSRF_TOKEN').'='.$data[C\p('CSRF_TOKEN')];
            $base_url = htmlentities(B\wikiUrl($data['PAGE_NAME'] , true,
                $data['CONTROLLER'], $data['GROUP']['GROUP_ID']));
            $raw_base_url = B\wikiUrl($data['PAGE_NAME'], true,
                $data['CONTROLLER'], $data['GROUP']['GROUP_ID']);
            if ($logged_in && !empty($token_string)) {
                $raw_base_url .= $token_string;
            }
            $data['RAW_BASE_URL'] = $raw_base_url;
            $url_prefix = $data['RESOURCES_INFO']['url_prefix'];
            if ($logged_in) {
                $base_url .= $token_string;
                if (C\REDIRECTS_ON) {
                    $url_prefix =  preg_replace("/\/-\//u",
                        "/$token_string/", $url_prefix);
                } else {
                    $url_prefix .= "&amp;". $token_string;
                }
            }
            $folder_prefix = ($is_static) ? $base_url : $base_url . "&amp;";
            $folder_prefix .= "page_id=". $data["PAGE_ID"];
            $url_is_folder_prefix = false;
            $settings_and_resources = "";
            if (!empty($data['settings'])) {
                $settings_and_resources =  "&amp;settings={$data['settings']}" .
                "&amp;resources={$data['resources']}";
            }
            if (!empty($read_mode)) {
                $read_arg = ($read_mode == "source") ? "source" : "media";
                $folder_prefix .= "&amp;arg=$read_arg";
                $url_prefix = $folder_prefix;
                $url_is_folder_prefix = true;
            } else if ($data['CURRENT_LAYOUT'] == 'detail') {
                $folder_prefix .= "&amp;arg=media";
                $url_prefix = $folder_prefix;
                $folder_prefix = $base_url . "&amp;arg=edit";
                $base_url .= $settings_and_resources;
                $url_is_folder_prefix = true;
            } else {
                $folder_prefix = $base_url . "&amp;arg=edit";
                $base_url .= $settings_and_resources;
            }
            if ($read_mode != "media") {
                $data['resources'] ??= 'false';
                $folder_prefix .= "&amp;resources={$data['resources']}";
            }
            ?>
            <form class="podcast-controls-row">
            <?php
            if (!empty($data['PODCAST_FOLDER']) &&
                !empty($data['CAN_EDIT'])) {
                $this->renderPodcastStatus($data);
            }
            ?>
            <div class="align-opposite media-controls"><?php
            $sub_path = $this->renderPath('resource-path',
                $data, [$folder_prefix => ""], "", "", "paths", false,
                "hover-selector");
            $sub_path_folder_prefix = (empty($data['RESOURCE_FILTER'])) ?
                $folder_prefix : $folder_prefix . "&amp;resource_filter=".
                urlencode($data['RESOURCE_FILTER']);
            $sub_path_var = "";
            if (!empty($data['SUB_PATH'])) {
                $sub_path_var = "&amp;sf=". urlencode($data['SUB_PATH']);
                $sub_path_folder_prefix .= $sub_path_var;
            }
            $layouts = ["list" => ["&#8803;", tl('wiki_element_list_view')],
                "grid" => ["&boxplus;", tl('wiki_element_grid_view')],
                "detail" => ["<span class='dotted-underline'>&#9634;</span>",
                    tl('wiki_element_detail_view')]
                ];
            ?>
            <div class="icon-button-group"><?php
                foreach ($layouts as $layout => $layout_data) {
                    list($icon, $label) = $layout_data;
                    $is_current_layout = (($data['CURRENT_LAYOUT'] ?? "")
                        == $layout);
                    $layout_style = ($is_current_layout) ?
                        "class='selected' " : "";
                    ?><a class="media-anchor-button" href="<?=
                        $sub_path_folder_prefix . "&amp;layout=$layout"
                        ?>"><span <?=$layout_style ?> role="img" aria-label='<?=
                        $label ?>'><?=$icon ?></span></a><?php
                }
                ?>
            </div><?php
            $this->view->helper("options")->renderLinkDropDown(
                "sort-fields", $data['sort_fields'] ?? "",
                $data['CURRENT_SORT']?? "", "$sub_path_folder_prefix&amp;sort=",
                false, "",
                "<span class='hover-lightgray' role='img' aria-label='" .
                tl('wiki_element_sort_order') . "'>&#8645;</span>");
            $arg = ($data['CONTROLLER'] == 'static') ? 'read' :
                (($data['MODE'] == 'media-detail-edit') ? 'edit' :
                $data['MODE']);
            $a = ($data['CONTROLLER'] == 'static') ? 'showPage' : 'wiki';
            $page_name = ($data['CONTROLLER'] == 'static') ? 'p' : 'page_name';
            ?>
            <input type="hidden" name='<?=C\p('CSRF_TOKEN')?>'
                value='<?=$data[C\p('CSRF_TOKEN')]?>' >
            <input type="hidden" name='arg' value='<?=$arg ?>' >
            <input type="hidden" name='a' value='<?=$a ?>' >
            <input type="hidden" name='c' value='<?=$data['CONTROLLER'] ?>' >
            <input type="hidden" name='resources' value='<?=$data["resources"]
                ?? "false" ?>' >
            <input type="hidden" name='<?= $page_name?>'
                value='<?=$data['PAGE_NAME']?>' >
            <?php
            if (!empty($data['SUB_PATH'])) { ?>
                <input type="hidden" name='sf'
                    value='<?=$data['SUB_PATH']; ?>' >
                <?php
            }
            ?>
            <div class="filter-container">
            <input type="search" class="narrow-field" name='resource_filter'
                placeholder="<?=tl('wiki_element_resource_filter')?>"
                id='resource-filter' value="<?=$data['RESOURCE_FILTER']?>" >
            </div>
            <button type='submit' name='filter_resources'
                class="button-box media-filter-button"><?=
                tl('wiki_element_go') ?></button><?php
            if (empty($read_mode)) {
                ?><div class="media-buttons-container" id='clipboard-toggle'><a
                    class="media-anchor-button"
                    href="javascript:toggleDisplay('clipboard');<?php
                    ?>toggleClass('clipboard-toggle','back-gray');<?php
                    ?>this.preventDefault;"><span
                    role="img" aria-label='<?=
                    tl('wiki_element_clipboard')
                    ?>'>&#128203;</span></a></div><?php
                if ($file_upload_id != "") {
                    $data['resource_actions'][
                        "javascript:elt('$file_upload_id').click()"] =
                        tl('wiki_element_upload_file');
                }
                $data['resource_actions']['zip'] =
                    tl('wiki_element_page_resources_zip');
                if (!empty($data['CAN_FIX_RESOURCE_VERSIONS'])) {
                    $data['resource_actions']['clear-lock'] =
                        tl('social_component_clear_resource_lock');
                    $data['resource_actions']['version'] =
                        tl('social_component_version_resource');
                }
                $this->view->helper("options")->renderLinkDropDown(
                    "resource-actions", $data['resource_actions'],
                    -1, "$sub_path_folder_prefix&amp;resource_actions=",
                    false, "inward-align",
                    "<span class='hover-lightgray' role='img' aria-label='" .
                    tl('wiki_element_actions') . "'>&#8943;</span>");
            }
            ?>
            </div></form>
            <div id="clipboard" class="none">
            <h3><?=tl('wiki_element_clipboard') ?></h3>
            <div>
            [<a href="<?=$base_url . $sub_path_var .
                '&amp;arg=edit&amp;paste_all=true'
                ?>"><?=tl('wiki_element_paste_all')?></a>]
            [<a onclick='return emptyConfirm();'
                href="<?=$base_url . $sub_path_var .
                '&amp;arg=edit&amp;empty_clip=true'
                ?>"><?=tl('wiki_element_empty_clipboard')?></a>]
            </div>
            <hr ><?php
            if (empty($data['CLIPBOARD_INFO'])) {?>
                <div class='red'><?=tl('wiki_element_no_clipboard_items')
                ?></div><?php
            } else {
                foreach ($data['CLIPBOARD_INFO'] as $clipboard_name) { ?>
                    [<a href="<?=$base_url . $sub_path_var .
                        '&amp;arg=edit&amp;paste='.urlencode($clipboard_name)
                        ?>"><?=tl('wiki_element_paste',
                        $clipboard_name)?></a>]
                    <?php
                }
            }
            ?></div>
            <?php
            if ($have_resources ||
                empty($read_mode)) {
                $time = time();
                $container_id =  $data['SCROLL_CONTAINER_ID'] ??
                    L\crawlHash($time);
                ?><div id="<?= $container_id ?>" class="wiki-resources" >
                    <div id="<?= $container_id ?>-popup"></div><?php
                $is_list = false;
                $is_grid = false;
                $is_detail = false;
                if (empty($data['CURRENT_LAYOUT']) ||
                    $data['CURRENT_LAYOUT'] == 'list') {
                    $is_list = true;
                    ?><div id='info-item'>
                        <div class="float-opposite" ><?=
                        $this->view->helper('close')->render(
                            "javascript:setDisplay('info-item', false);"
                        );
                        ?></div>
                        <iframe id="info-frame" src='about:blank'></iframe>
                    </div><table><thead><?php
                } else if ($data['CURRENT_LAYOUT'] == 'grid') {
                    $is_grid = true;
                    ?>
                    <div id='info-item'>
                    <div class="float-opposite" ><?=
                    $this->view->helper('close')->render(
                        "javascript:setDisplay('info-item', false);"
                    );
                    ?></div>
                    <iframe id="info-frame" src='about:blank'></iframe>
                    </div>
                    <div class="grid-container"><?php
                } else {
                    $is_detail = true;
                    $read_mode = empty($read_mode) ? "media" : $read_mode;
                    ?>
                    <div id='detail-item'>&nbsp;</div>
                    <div id='<?=$data['DETAIL_SCROLL_ID'];
                        ?>' class="detail-container"><?php
                }
                if (empty($read_mode) && $data['MODE'] != 'source' &&
                    $data['CURRENT_LAYOUT'] == 'list') {
                    ?><tr><th colspan="2"></th>
                        <th style='width:2.6in;'>
                        <div class='resizable' ><a href='<?=
                        $sub_path_folder_prefix?>&amp;default_sort=name'><?=
                        tl('wiki_element_name')
                    ?></a></div></th><?php
                    if (!$_SERVER["MOBILE"]) {
                        e("<th><div class='resizable'
                            ><a href='$sub_path_folder_prefix&amp;".
                            "default_sort=size'>" . tl('wiki_element_size').
                            '</a></div>');
                        e("</th><th ><a href='".
                            "$sub_path_folder_prefix&amp;".
                            "default_sort=modified'>" .
                            tl('wiki_element_modified').'</a></th>');
                    } ?>
                </tr></thead><tbody><?php
                }
                $seen_resources = []; /* these are video and audio which
                   appear in different version such as mp4 and mov
                   This array keeps track if have seen an alternative version
                   earlier while processing resource list.
                   */
                $i = 0;
                $is_first = true;
                if (empty($read_mode) && (($data['MODE'] ?? "") == 'edit'
                    || ($data['MODE'] ?? "") == 'media-detail-edit')
                    && !empty($data['SUB_PATH'])) {
                    $this->renderParentFolderRow($data, $folder_prefix);
                }
                if ($have_resources) {
                    foreach ($data['RESOURCES_INFO']['resources'] as $resource){
                        $this->renderResource($i, $is_first, $resource,
                            $base_url, $token_string, $url_prefix,
                            $url_is_folder_prefix, $folder_prefix,
                            $seen_resources, $read_mode, $logged_in, $data);
                        $i++;
                    }
                }
                if ($is_list) {
                    ?></tbody></table><?php
                } else if ($is_grid) {
                    ?></div><?php
                } else {
                    ?></div><?php
                }
                ?></div><?php
                if (empty($read_mode)) {
                    $move_base_url = B\wikiUrl($data['PAGE_NAME'], true,
                        $data['CONTROLLER'], $data['GROUP']['GROUP_ID']);
                    if ($logged_in && !empty($token_string)) {
                        $move_base_url .= $token_string;
                    }
                    $move_base_url .= "&arg=edit";
                    if (!empty($data['SUB_PATH'])) {
                        $move_base_url .= "&sf=" .
                            urlencode($data['SUB_PATH']);
                    }
                    ?><script>
                    window.addEventListener("load", function() {
                        initResourceDragDrop("<?=$move_base_url ?>");
                    });
                    </script><?php
                }
                if (!empty($read_mode)) {
                    $scroll_id_path = $data['PAGE_ID'];
                    $scroll_id_path .= (empty($data['SUB_PATH'])) ?
                        "" : base64_encode($data['SUB_PATH']);
                    ?>
                    <script>
                    function clearResource(id, url)
                    {
                        if (localStorage && localStorage.getItem(
                            "current_time" + id)) {
                            localStorage.removeItem("current_time" + id);
                        }
                        window.location = url;
                    }
                    function recordScrollTop()
                    {
                        let root_elt = document.documentElement;
                        localStorage.setItem("scroll_top<?=
                            $scroll_id_path ?>", root_elt.scrollTop);
                    }
                    function initializeResourceList()
                    {
                        let marked_elts = document.getElementsByClassName(
                            'marked');
                        for (let i = 0; i < marked_elts.length; i++) {
                            let cur_id = marked_elts.item(i).id;
                            if (localStorage && localStorage.getItem(
                                "current_time" + cur_id)) {
                                marked_elts.item(i).innerHTML = "&loz;";
                            }
                        }
                        let scroll_top;
                        if (localStorage) {
                            document.body.addEventListener("click",
                                recordScrollTop, true);
                            if (scroll_top = localStorage.getItem(
                                "scroll_top<?= $scroll_id_path ?>")) {
                                document.documentElement.scrollTop = scroll_top;
                            }
                        }
                    }
                    initializeResourceList();
                    </script>
                    <?php
                }
            }
        }
        if (!$have_resources) {?>
            <h2 class="wiki-resource-noresource"><?=
                tl('wiki_element_no_resources')?></h2><?php
        }
    }
    /**
     * Renders a single resource description from the list of resources for
     * a given wiki page
     * @param int $i index in list of resources the current resource is
     *    (used when writing out element id's of tags to make unique)
     * @param bool &$is_first is this the first resource to be drawn
     * @param array $resource assiative array of information about the
     *     resource to be rendered
     * @param string $base_url controller URL (with trailing "?") that
     *      resource action links should be built off
     * @param string $token_string token_name=CRSF_TOKEN string for URL
     *      to stay logged in
     * @param string $url_prefix URL prefix used to compose links that
     *      target the resource itself (typically a thumbnail/redirect
     *      URL when REDIRECTS_ON, otherwise a controller URL)
     * @param string $url_is_folder_prefix URL prefix used when the
     *      resource is a sub-folder rather than a file; clicking
     *      navigates into the folder
     * @param string $folder_prefix relative-path prefix prepended to
     *      the resource name to form its full sub_path within the
     *      page's resource tree
     * @param array &$seen_resources accumulator of resource names
     *      already rendered on this page (used to suppress duplicates
     *      and to detect non-empty resource lists)
     * @param string $read_mode whether the readering should be for a media
     *      list in read mode (valye "read"), a source mode (value "source"),
     *      or for use on the edit task of any wiki page (value "")
     * @param bool $logged_in is the context for rendering the resource of
     *      of someone who is looged in or not
     * @param array $data associate array of values to be sent to and used by
     *      view for rendering
     */
    public function renderResource($i, &$is_first, $resource, $base_url,
        $token_string, $url_prefix, $url_is_folder_prefix, $folder_prefix,
        &$seen_resources, $read_mode, $logged_in, $data)
    {
        $name = $resource['name'];
        $hash_id = L\crawlHash($data['PAGE_ID'] . urlencode($name) .
            ($data['ORIGINAL_SUB_PATH'] ?? ""));
        $hash2_id = L\crawlHash($data['PAGE_ID'] . $name .
            ($data['ORIGINAL_SUB_PATH'] ?? ""));
        if (C\REDIRECTS_ON) {
            $thumb_prefix =  preg_replace("/\/-\//u", "/$token_string/",
                $data['RESOURCES_INFO']['thumb_prefix']);
            $athumb_prefix =  preg_replace("/\/-\//u", "/$token_string/",
                $data['RESOURCES_INFO']['athumb_prefix']);
        } else {
            $thumb_prefix = $data['RESOURCES_INFO']['thumb_prefix'] .
                "&amp;$token_string";
            $athumb_prefix = $data['RESOURCES_INFO']['athumb_prefix'] .
                "&amp;$token_string";
        }
        $default_thumb = C\SHORT_BASE_URL .
            $data['RESOURCES_INFO']['default_thumb'];
        $default_editable_thumb = C\SHORT_BASE_URL .
            $data['RESOURCES_INFO']['default_editable_thumb'];
        $default_folder_thumb = C\SHORT_BASE_URL .
            $data['RESOURCES_INFO']['default_folder_thumb'];
        if (!empty($data['RESOURCE_FILTER'])&&
            mb_stripos($name, $data['RESOURCE_FILTER']) === false) {
            return;
        }
        $name_parts = pathinfo($name);
        $written_name = $name;
        $use_editable_thumb = false;
        if (!empty($name_parts['extension'])) {
            if (in_array($name_parts['extension'], ['txt', 'css',
                'csv', 'tex', 'php', 'sql', 'html', 'java', 'js', 'py',
                'pl', 'P', 'srt'])) {
                $use_editable_thumb = true;
            } elseif (in_array($name_parts['extension'], ['mov',
                'mp4', 'm4v', 'webm', 'mkv', 'm2ts'])) {
                $written_name = $name_parts['filename'] .
                    "[".tl('wiki_element_video')."]";
            } elseif (in_array($name_parts['extension'], ['wav',
                'mp3','aac', 'aif', 'aiff', 'oga', 'ogg', "m4a"])) {
                $written_name = $name_parts['filename'] .
                    "[".tl('wiki_element_audio')."]";
            } else if ($name_parts['extension'] == "vtt") {
                if (!empty($read_mode)) {
                    return;
                }
                $use_editable_thumb = true;
            }
        }
        if (!empty($read_mode) && isset($seen_resources[$written_name])) {
            return;
        }
        $is_list = false;
        $is_grid = false;
        $is_detail = false;
        if (empty($data['CURRENT_LAYOUT']) ||
            $data['CURRENT_LAYOUT'] == 'list') {
            $is_list = true;
        } else if ($data['CURRENT_LAYOUT'] == 'grid') {
            $is_grid = true;
        } else {
            $is_detail = true;
        }
        $seen_resources[$written_name] = true;
        $disabled = " disabled='disabled' ";
        if (!empty($data['CAN_EDIT']) && !empty($resource['is_writable'])) {
            $disabled = "";
        }
        if (empty($read_mode)) {
            $written_name = $name;
        }
        $thumb_connect = (C\REDIRECTS_ON)
            ? "/" : "&amp;n=";
        $name_connect = (C\REDIRECTS_ON &&
            !$url_is_folder_prefix)
            ? "/" : "&amp;n=";
        $encode_name = urlencode($name);
        if ($read_mode == "source"){
            $url_prefix = preg_replace("/arg=source/", "arg=media",
                $url_prefix);
        }
        $current_url = "$url_prefix$name_connect$encode_name";
        $clear_url = "$folder_prefix&amp;clear=" .
            $encode_name;
        $current_thumb = "$thumb_prefix$thumb_connect" .
            $encode_name;
        $current_animated_thumb = "$athumb_prefix$thumb_connect" .
            $encode_name;
        $is_a_default_thumb = false;
        $alt_current_url = $current_url;
        if (!empty($data['SUB_PATH'])) {
            if (!str_contains($current_url, "sf=") &&
                $url_is_folder_prefix) {
                $encode_sub_path = urlencode($data['SUB_PATH']);
                $add_sub_path = "&amp;sf=$encode_sub_path";
                $current_url .= $add_sub_path;
                $clear_url .= $add_sub_path;
            }
            $alt_current_url = "$folder_prefix&amp;sf=".
                urlencode($data['SUB_PATH']) . "&amp;n=".
                $encode_name;
            $alt_current_url = preg_replace("/resources=true/u",
                "resources=false", $alt_current_url);
            if (!$resource['has_thumb'] ||
                ($use_editable_thumb && empty($read_mode))) {
                $current_thumb = $default_thumb;
                $is_a_default_thumb = true;
                if (empty($read_mode) &&!$disabled &&
                    $use_editable_thumb) {
                    $current_thumb = $default_editable_thumb;
                    $current_url = $alt_current_url;
                }
            }
            if (!empty($resource['is_dir'])) {
                $current_url = "$folder_prefix&amp;sf=".
                    urlencode($data['SUB_PATH'])."/$name";
                $current_thumb = $default_folder_thumb;
                $is_a_default_thumb = true;
            }
        } else {
            $alt_current_url = "$folder_prefix&amp;n=".
                urlencode($name);
            $alt_current_url = preg_replace("/resources=true/u",
                "resources=false", $alt_current_url);
            if (!$resource['has_thumb'] ||
                ($use_editable_thumb && empty($read_mode))) {
                $current_thumb = $default_thumb;
                $is_a_default_thumb = true;
                if (empty($read_mode) &&!$disabled &&
                    $use_editable_thumb) {
                    $current_thumb = $default_editable_thumb;
                    $current_url = $alt_current_url;
                }
            }
            if (!empty($resource['is_dir'])) {
                $current_url = "$folder_prefix&amp;sf=".
                    urlencode($name);
                $current_thumb = $default_folder_thumb;
                $is_a_default_thumb = true;
            }
        }
        $is_seen = !empty($_SESSION['seen_media']) &&
            in_array($hash_id, $_SESSION['seen_media']);
        $media_detail = ($data['MODE'] == 'edit') ?
            "arg=media-detail-edit&amp;page_id=" . $data["PAGE_ID"]:
            "arg=media-detail-read";
        $current_media_url = preg_replace('/arg=(media|edit|source)/',
            $media_detail, $current_url);
        $alt_current_media_url = preg_replace('/arg=(media|edit|source)/',
            $media_detail, $alt_current_url);
        $first_resource = "";
        $drag_attributes = "";
        if (empty($read_mode) && !$disabled) {
            $drag_attributes = " draggable='true' data-resource-name='" .
                str_replace("'", "&#39;", $name) . "' ";
            if (!empty($resource['is_dir'])) {
                $drag_attributes .= " data-folder-target='" .
                    str_replace("'", "&#39;", $name) . "' ";
            }
        }
        if ($is_list) {
            $tr = "tr";
            $td = "td";
            $td_img = "td";
            ?><tr class='list-resource' <?=$drag_attributes ?>><?php
        } else if ($is_grid) {
            $tr = "div";
            $td = "div";
            $td_img = "div";
            $seen = ($is_seen) ? " seen-resource " : "";
            $click = " onclick='" .
                "clearResource(this.id, \"". $clear_url . "\")' ";
            ?><div class='grid-resource <?=$seen ?>' <?=$click;
                ?> <?=$drag_attributes ?>><?php
        } else {
            $tr = "div";
            $td = "div";
            $td_img = "div";
            $data['original_MODE'] = $data['MODE'];
            $data['MODE'] == 'read';
            $double_click_url = $current_url;
            if ($is_first) {
                $is_first = false;
                $detail_id = $data['PAGE_ID'];
                $detail_id .= (empty($data['SUB_PATH'])) ?
                    "" : base64_encode($data['SUB_PATH']);
                $reset_detail = (empty($data['RESET_DETAIL'])) ? "" :
                    "if (localStorage) {" .
                        "localStorage.removeItem('detail$detail_id');" .
                        "localStorage.removeItem('selected$detail_id');" .
                    "}";
                $first_resource = "<script>" .
                    "function changeDetail(id, url)".
                    "{".
                        "let detail = document.getElementById('detail-item');".
                        "let mode = '{$data['original_MODE']}';" .
                        "if (mode == 'edit') { url = url.replace(".
                            "'arg=media-detail-read','arg=media-detail-edit');".
                        "} else { url = url.replace(".
                            "'arg=media-detail-edit','arg=media-detail-read');".
                        "}".
                        "detail.innerHTML = '<iframe src=\"'+ url +'\" >' +" .
                            "'</iframe>';" .
                        "if (localStorage) {".
                            "localStorage.setItem('detail$detail_id', url);" .
                            "let old_selected_id = " .
                                "localStorage.getItem('selected$detail_id');" .
                            "if (old_selected_id) {" .
                                "let old_selected =".
                                    "document.getElementById(old_selected_id);".
                                "old_selected.classList.remove(" .
                                    "'back-aliceblue')" .
                            "}" .
                            "localStorage.setItem('selected$detail_id', id);" .
                        "}".
                        "let selected_detail = document.getElementById(".
                            "id);".
                        "selected_detail.classList.add('back-aliceblue');".
                    "}" .
                    $reset_detail .
                    "let detail_url;" .
                    "let selected_detail_id;" .
                    "if (!localStorage || !(detail_url = ".
                        "localStorage.getItem('detail$detail_id')) ||".
                            "!(selected_detail_id = ".
                            "localStorage.getItem('selected$detail_id'))) {".
                        "detail_url = '$current_media_url';".
                        "selected_detail_id = '$hash2_id';" .
                    "};" .
                    "window.addEventListener('load', (event) => {".
                        "changeDetail(selected_detail_id, detail_url); });" .
                    "</script>";
            }
            ?>
            <div id='<?= $hash2_id ?>' class='detail-resource'
                <?=$drag_attributes ?>><?php
        }
        $info_link_class = ($read_mode) ? "info-link" :
            "float-opposite info-link";
        $info_link = ($is_detail) ? "" : "&nbsp;<a class='$info_link_class' ".
            "onclick='toggleDisplay(\"info-item\");elt(\"info-frame\").src=".
            "\"$alt_current_media_url&target=child\";" .
            "window.event.stopPropagation();" .
            "return false;' href='$alt_current_media_url'>&#9432;</a>";
        if ($is_seen && $is_list) {
            e("<$td class='view-indicator huge-font'><span " .
                "id='$hash2_id' class='marked' onclick='" .
                "clearResource(this.id, \"". $clear_url . "\")' ".
                ">&diams;</span></$td>");
        } else {
            e("<$td class='view-indicator'></$td>");
        }
        $animated_thumb_info = "";
        if ($resource['has_animated_thumb']) {
            $animated_thumb_info = "class='animated' " .
                "data-src='$current_animated_thumb' ".
                "onmouseover= 'let src = this.src; ".
                "this.src = this.dataset.src; ".
                "this.dataset.src =src' " .
                "onmouseout= 'let src = this.src; ".
                "this.src = this.dataset.src; ".
                "this.dataset.src =src' ";
        }
        $dbl_click = "";
        $style = ($is_detail || $is_grid) ? " style='min-width:120px;' " : "";
        if ($is_detail) {
            $current_url = "javascript:changeDetail(\"$hash2_id\",".
                "\"$current_media_url\");";
            $dbl_click = "ondblclick='location = \"$double_click_url\";'";
        }
        $num_files_indicator = "";
        if (!empty($resource['is_dir'])) {
            if (isset($resource['num_files'])) {
                $num_files_indicator = "<div class='white back-blue badge'>".
                    $resource['num_files'] ."</div>";
            }
        }
        $thumb_class = ($is_a_default_thumb) ? ' class="default-thumb" ' : "";
        e("<$td_img class='resource-thumb'>".
            "$num_files_indicator<a href='$current_url'  $dbl_click >");
        e("<img $style loading='lazy' $thumb_class src='" . $current_thumb .
            "'  alt='$written_name'  $animated_thumb_info >");
        e("</a>$first_resource</$td>");
        if (!empty($read_mode)) {
            e("<$td><a href='$current_url'>".
                "$written_name</a>$info_link</$td>");
        } else {
            // need tabindex to allow focus-within to work Safari
            e("<$td class='resource-field' tabindex='0'
                ><input type='text' ".
                "id='resource-$i' aria-label='".tl('wiki_element_name')."' ".
                "value='".str_replace("'", "&#39;", $name).
                "' $disabled >");
            if (!$disabled) {
                ?><script>
                res = document.getElementById('resource-<?=$i?>');
                res.addEventListener("keyup", function (event) {
                    if (event.keyCode == 13) {
                        renameResource("<?= urlencode($name);?>", <?= $i ?>);
                    }
                });
                </script>
                <span class="user-accounts-edit-toolbar"><?php
                $clean_name = urlencode($name);
                ?><button type='button' class='user-accounts-edit-btn'
                    title='<?=tl('wiki_element_rename') ?>'
                    aria-label='<?=tl('wiki_element_rename') ?>'
                    onclick='renameResource("<?=$clean_name ?>",
                    <?=$i ?>)'>&#10003;</button><?php
                $raw_base_url = $data['RAW_BASE_URL'];
                /* the base url names the page but not the resource
                   listing it is being viewed through, and what comes
                   back after the delete is worked out from the fields
                   this url carries, so without this the reader is
                   returned to the page's edit form rather than to the
                   folder they deleted from */
                $delete_url = $raw_base_url .
                    "&arg=edit&resources=true&delete_resource=" .
                    $clean_name;
                if (!empty($data['SUB_PATH'])) {
                    $delete_url .= "&sf=". urlencode($data['SUB_PATH']);
                }
                ?><button type='button' class='user-accounts-edit-btn'
                    title='<?=tl('wiki_element_delete') ?>'
                    aria-label='<?=tl('wiki_element_delete') ?>'
                    onclick='deleteResource("<?=$clean_name ?>",
                    "<?=$delete_url
                    ?>")'>&#x2715;</button><?php
            }
            if (!$disabled && empty($resource['is_dir'])
                && (!isset($data['page_type']) ||
                $data['page_type'] != 'media_list')) {
                $resource_sub_path = empty($data['SUB_PATH']) ?
                    "" : "|" . $data['SUB_PATH'] ;
                ?>
                <script>
                document.write(
                "<button type='button' class='user-accounts-edit-btn' "+
                "title='<?=tl('wiki_element_wiki_code') ?>' "+
                "onclick='alert(\"((resource:<?=
                    urlencode($name). $resource_sub_path . "|" .
                tl('wiki_element_resource_description', $name) ."))" ?>\")' >"+
                "&#9881;</button>");
                </script><?php
            }
            $append = "";
            if (isset($data['OTHER_BACK_URL'])) {
                $append .= $data['OTHER_BACK_URL'];
            }
            if (!$disabled && !empty($resource['is_compressed'])) {
                $extract_url = $data['RAW_BASE_URL'] .
                    "&arg=edit&resources=true&extract=" . urlencode($name) .
                    $append;
                if (!empty($data['SUB_PATH'])) {
                    $extract_url .= "&sf=". urlencode($data['SUB_PATH']);
                }
                ?><button type='button' class='user-accounts-edit-btn'
                    onclick='window.location="<?=$extract_url ?>"'><?=
                    tl('wiki_element_extract') ?></button><?php
            }
            $copy_url = $base_url .
                "&amp;arg=edit&amp;clip_copy=". urlencode($name) . $append;
            if (!empty($data['SUB_PATH'])) {
                $copy_url .= "&amp;sf=". urlencode($data['SUB_PATH']);
            }
            $cut_url = $base_url .
                "&amp;arg=edit&amp;clip_cut=". urlencode($name) . $append;
            if (!empty($data['SUB_PATH'])) {
                $cut_url .= "&amp;sf=" . urlencode($data['SUB_PATH']);
            }
            if (!$disabled) {
                ?><span class="resource-more-menu position-context"
                    ><button type='button'
                    class='user-accounts-edit-btn resource-more-btn'
                    title='<?=tl('wiki_element_more_actions') ?>'
                    aria-label='<?=tl('wiki_element_more_actions') ?>'
                    onclick='toggleResourceMenu(this)'
                    ><span class="resource-more-glyph"
                    >&#8230;</span></button><span
                    class="resource-more-options none"
                    ><a href='<?=$copy_url ?>'><?=
                    tl('wiki_element_clip_copy') ?></a><a href='<?=
                    $cut_url ?>'><?=
                    tl('wiki_element_clip_cut') ?></a></span></span><?php
            }
            if (!$disabled) {
                ?></span><?php
            }
            e($info_link);
            e("</$td>");
            if (!$_SERVER["MOBILE"]) {
                e("<$td>" .
                    L\intToMetric($resource['size']) .
                    "B</$td>");
                e("<$td>" .
                    date("r", $resource['modified']) .
                    "</$td>");
            }
        }
        e("</$tr>");
    }
    /**
     * Renders a row at the top of the resource list that stands for the
     * parent folder when the user is inside a subfolder. Clicking it
     * goes up to the parent folder, and dragging a resource onto it
     * moves that resource up into the parent. It is not shown at the
     * top resource folder, which has no parent.
     *
     * @param array $data fields for the current page, including the
     *      current SUB_PATH and resource thumbnail info
     * @param string $folder_prefix url prefix that a folder's
     *      navigation link is built on
     */
    public function renderParentFolderRow($data, $folder_prefix)
    {
        $sub_path = $data['SUB_PATH'];
        $path_parts = array_filter(explode("/", $sub_path));
        array_pop($path_parts);
        $parent_sub_path = implode("/", $path_parts);
        $up_url = $folder_prefix . "&amp;sf=" .
            urlencode($parent_sub_path);
        $folder_thumb = C\SHORT_BASE_URL .
            $data['RESOURCES_INFO']['default_folder_thumb'];
        $parent_label = tl('wiki_element_parent_folder');
        $drop_attributes = " data-folder-path='" .
            str_replace("'", "&#39;", $parent_sub_path) . "' ";
        $layout = $data['CURRENT_LAYOUT'] ?? 'list';
        $is_list = (empty($layout) || $layout == 'list');
        $name_link = "<a class='parent-folder-link' href='$up_url'>" .
            $parent_label . "</a>";
        $up_arrow = "<div class='white back-blue badge'>&#8593;</div>";
        $thumb_cell = "<a href='$up_url'>$up_arrow" .
            "<img class='default-thumb' loading='lazy' src='" .
            $folder_thumb . "' alt='$parent_label' ></a>";
        if ($layout == 'grid') {
            e("<div class='grid-resource parent-folder-row' " .
                "$drop_attributes>");
            e("<div class='resource-thumb'>$thumb_cell</div>");
            e("<div class='resource-field'>$name_link</div>");
            e("</div>");
        } else if ($layout == 'detail') {
            e("<div class='parent-folder-row parent-folder-detail' " .
                "$drop_attributes>");
            e("<div class='resource-thumb'>$thumb_cell</div>");
            e("<div class='resource-field'>$name_link</div>");
            e("</div>");
        } else {
            e("<tr class='list-resource parent-folder-row' " .
                "$drop_attributes>");
            e("<td class='view-indicator'></td>");
            e("<td class='resource-thumb'>$thumb_cell</td>");
            e("<td class='resource-field'>$name_link</td>");
            if ($is_list && !$_SERVER["MOBILE"]) {
                e("<td></td><td></td>");
            }
            e("</tr>");
        }
    }
    /**
     * Used to render the dropdown that lists paths within media lists folders,
     * recent wiki pages, and groups a user has been to
     *
     * @param string $dropdown_id element id of select tag to be used for
     *      dropdown
     * @param array $data set up in controller and SocialComponent with
     *      data fields view and this element are supposed to render
     * @param array $options if nonempty, then this should be items, key-values
     *      in the form (url => label), to list first in dropdown
     * @param string $selected_url url which is selected by default in dropdown.
     * @param string $top_name name of root media list folder (defaults
     *      to something like "Root Folder" in the language of current locale)
     * @param string $render_type can be: "paths" if just listing folder path
     *      in wiki page resource folder, "just_groups_and_pages" if want a list
     *      of recent groups and wiki pages viewed, or "all" if want both
     * @param boolean $as_list whether to output the result as a dropdown
     *      or as an unordered list.
     * @param string $class_list list of css classes to add to top div in
     *      what is rendered
     * @return string the final accumulated $sub_path string used by
     *      the caller to deep-link into the same folder after a form
     *      submission
     */
    public function renderPath($dropdown_id, $data, $options,
        $selected_url = "", $top_name = "", $render_type = "paths",
        $as_list = false, $class_list = "")
    {
        $folder_prefix = "";
        if (empty($options)) {
            $options = [];
        } else if ($render_type != "just_groups_and_pages") {
            $folder_prefix = key($options);
            $root_name = $options[$folder_prefix];
            if (empty($root_name)) {
                $root_name = tl('wiki_element_root_folder');
            }
            $options[$folder_prefix] = $root_name;
        }
        $path_parts = (empty($data['SUB_PATH'])) ?
            [] : array_filter(explode("/", $data['SUB_PATH']));
        $sub_path = "";
        $selected_set = ($selected_url) ? true : false;
        if ($render_type == "just_groups_and_pages" && empty($options)) {
            $options = array_merge([-1 => tl('wiki_element_recent_places')],
                $options);
        } else if (in_array($render_type, ['all', 'paths'])) {
            $num_parts = count($path_parts);
            $i = 1;
            foreach ($path_parts as $part) {
                $sub_path .= $part;
                if ($i == $num_parts) {
                    if ($top_name == "") {
                        $options = array_merge([-1 => $part], $options);
                    } else {
                        $options = array_merge([-1 => $top_name,
                           "&amp;sf=$sub_path" => $part], $options);
                    }
                    break;
                } else {
                    $options = array_merge(
                        ["&amp;sf=$sub_path" => $part], $options);
                }
                $i++;
                $sub_path .= '/';
            }
            if ($num_parts == 0) {
                if ($top_name == "" && !empty($part)) {
                    $options = array_merge([-1 => $part], $options);
                } else if (!empty($top_name)){
                    $options = array_merge([-1 => $top_name], $options);
                }
                $selected_set = true;
            }
            $options = array_merge([tl('wiki_element_paths') => ""],
                $options);
        }
        if (in_array($render_type, ['all', 'just_groups_and_pages'])) {
            if (!empty($data['RECENT_PAGES'])) {
                $token_string = C\p('CSRF_TOKEN') .
                    "=". $data[C\p('CSRF_TOKEN')];
                $add_options = [tl('wiki_element_recent_pages') => ""];
                $found_new = false;
                foreach ($data['RECENT_PAGES'] as $page_name => $url) {
                    $out_token = (strstr($url, C\p('CSRF_TOKEN')) === false) ?
                        $token_string : "";
                    if (empty($options[$url . $out_token])) {
                        $add_options[$url . $out_token] = $page_name;
                        $found_new = true;
                    }
                }
                if ($found_new) {
                    $options = array_merge($options, $add_options);
                }
            }
            if (!empty($data['RECENT_GROUPS'])) {
                $token_string = C\p('CSRF_TOKEN') .
                    "=". $data[C\p('CSRF_TOKEN')];
                $add_options = [tl('wiki_element_recent_groups') => ""];
                $found_new = false;
                foreach ($data['RECENT_GROUPS'] as $group_name => $url) {
                    $out_token = (strstr($url, C\p('CSRF_TOKEN')) === false) ?
                        $token_string : "";
                    if (!empty($out_token) && (strstr($url, "?") === false)) {
                        $url .= "?";
                    }
                    if (empty($options[$url . $out_token])) {
                        $add_options[$url . $out_token] =
                            $group_name ;
                        $found_new = true;
                    }
                }
                if ($found_new) {
                    $options = array_merge($options, $add_options);
                }
            }
        }
        if (count($options) <= 1) {
            $options[tl('wiki_element_login_for_recent')] = "";
        }
        $this->view->helper('options')->renderLinkDropDown($dropdown_id,
            $options, $selected_url, $folder_prefix, $as_list,
            $class_list);
        return $sub_path;
    }
    /**
     * Used to draw a list of Wiki Pages for the current group. It also
     * draws a search form and can be used to create pages
     *
     * @param array $data fields for the current controller, CSRF_TOKEN
     *     etc needed to render the search for and paging queries
     * @param bool $can_edit whether the current user has permissions to
     *     edit or create this page
     * @param bool $logged_in whether current user is logged in or not
     */
    public function renderPages($data, $can_edit, $logged_in)
    {
        $icon_helper = $this->view->helper('iconlink');
        $token_string = ($logged_in) ? C\p('CSRF_TOKEN') ."=".
            $data[C\p('CSRF_TOKEN')] :
            "";
        $group_id = $data["GROUP"]["GROUP_ID"];
        $controller = $data['CONTROLLER'];
        $create_query = htmlentities(B\wikiUrl(preg_replace("/\s+/u", "_",
            $data["FILTER"]), true, $controller, $group_id)) . $token_string .
            "&amp;arg=edit";
        $paging_query = htmlentities(B\wikiUrl("pages", true, $controller,
            $group_id)) . $token_string;
        if (!empty($data['FILTER'])) {
            $paging_query .= "&amp;filter=" . $data['FILTER'];
        }
        ?><h2 class="page-list-header-footer"><?=
            tl("wiki_element_wiki_create_find", $data["GROUP"]["GROUP_NAME"])
        ?></h2><?php
        ?>
        <form id="editpageForm" method="post" class="page-list-header-footer">
        <input type="hidden" name="c" value="<?=$data['CONTROLLER'] ?>" >
        <input type="hidden" name="<?= C\p('CSRF_TOKEN') ?>" value="<?=
            $data[C\p('CSRF_TOKEN')] ?>" >
        <input type="hidden" name="a" value="wiki" >
        <input type="hidden" name="arg" value="pages" >
        <input type="hidden" name="group_id" value="<?=
            $data['GROUP']['GROUP_ID'] ?>" >
        <?php
        $this->view->helper("options")->renderLinkDropDown(
        "page-sort-fields", $data['sort_fields'] ?? "",
        $data['CURRENT_SORT']?? "", "$paging_query&amp;sort=",
        false, "",
        "<span class='hover-lightgray' role='img' aria-label='" .
        tl('wiki_element_sort_order') . "'>&#8645;</span>"); ?>
        <div class="search-filter-container">
        <input type="search" name="filter" class="extra-wide-field"
            maxlength="<?= C\SHORT_TITLE_LEN ?>"
            placeholder="<?= tl("wiki_element_filter_or_create")
            ?>" value="<?= $data['FILTER'] ?>" >
        </div>
        <button class="button-box"
            type="submit"><?=tl('wiki_element_go') ?></button>
        </form>
        <?php
        if ($data["FILTER"] != "") {
            ?><div class='page-list-header-footer'>
            <a href='<?= $create_query ?>'><?=tl("wiki_element_create_page",
                $data['FILTER']) ?></a></div><?php
        }
        ?>
        <div>&nbsp;</div>
        <?php
        if ($data['PAGES'] != []) {
            $time = time();
            foreach ($data['PAGES'] as $page) {
                if ($page['TYPE'] == 'page_alias' && isset($page['ALIAS'])) {
                    $page["SHOW_DESCRIPTION"] = tl('wiki_element_redirect_to').
                        " <a href='".htmlentities(B\wikiUrl($page['ALIAS'],
                        true, $controller, $group_id)) . $token_string .
                        "'>".  urldecode($page['ALIAS']) . "</a>";
                } else {
                    $page["SHOW_DESCRIPTION"] = strip_tags(
                        $page["SHOW_DESCRIPTION"]);
                }
                ?>
                <div class='wiki-page-list-result'>
                <a href="<?= htmlentities(B\wikiUrl($page['PAGE_NAME'],
                    true, $controller, $group_id)) . $token_string
                    ?>&amp;noredirect=true" <?php
                    if ($data["OPEN_IN_TABS"]) { ?>
                        target="_blank" rel="noopener"<?php
                    }?>><?=urldecode($page["SHOW_PAGE_NAME"]) ?></a> <?php
                if ($can_edit) {
                    $tab_target = (empty($data['OPEN_IN_TABS'])) ?
                        "" : "_blank";
                    if ($data['CAN_DELETE']) {
                        $icon_helper->renderButton(htmlentities(
                            B\wikiUrl($page['PAGE_NAME'], true,
                            $controller, $group_id)). $token_string .
                            "&amp;noredirect=true&amp;arg=deletepage",
                            'delete', "", false, "float-opposite ".
                            "small-margin no-padding", "", false, $tab_target);
                    }
                    $icon_helper->renderButton(htmlentities(
                        B\wikiUrl($page['PAGE_NAME'], true, $controller,
                        $group_id)).
                        $token_string . "&amp;noredirect=true&amp;arg=edit",
                        'edit', "", false, "float-opposite ".
                        "small-margin no-padding", "", false, $tab_target);
                } else if (empty($data['GROUP']) ||
                    !isset($data['GROUP']['PAGE_SOURCE_ALLOWED']) ||
                    !empty($data['GROUP']['PAGE_SOURCE_ALLOWED'])) {
                    $tab_target = (empty($data['OPEN_IN_TABS'])) ?
                        "" : "_blank";
                    $icon_helper->renderButton(htmlentities(
                        B\wikiUrl($page['PAGE_NAME'], true, $controller,
                        $group_id)).
                        $token_string . "&amp;noredirect=true&amp;arg=source",
                        'view_source', "", false, "float-opposite ".
                        "small-margin no-padding", "", false, $tab_target);
                }
                ?></br >
                <?= $page["SHOW_DESCRIPTION"] ?><br>
                <span class="float-opposite gray"><?=
                    $this->view->helper("feeds")->getPubdateString(
                    $time, $page['LAST_MODIFIED']); ?></span>
                </div>
                <?php
            }?>
            <div class="page-list-header-footer"><?php
            $this->view->helper("pagination")->render(
                $paging_query, $data['LIMIT'], $data['RESULTS_PER_PAGE'],
                $data['TOTAL_ROWS']);?>
            </div><?php
        }
        if (empty($data['PAGES'])) {
            ?><div class='page-list-header-footer'><?=
            tl('wiki_element_no_pages', "<b>" . L\getLocaleTag() .
            "</b>")?></div><?php
        }
    }
    /**
     * Used to draw the page which displays all wiki pages
     *    that link a wiki page with a particular relationship type
     *
     * @param array $data fields contain info about all such wiki pages
     */
    public function renderRelationships($data)
    {
        $logged_in = !empty($data["ADMIN"]);
        if ($logged_in) {
            $csrf_token = C\p('CSRF_TOKEN')."=".$data[C\p('CSRF_TOKEN')];
        }
        $page_name = $data['PAGE_NAME'];
        $page_id = $data['PAGE_ID'];
        $url_main = htmlentities(B\wikiUrl($page_name, true,
            $data['CONTROLLER'], $data['GROUP_ID'])) . $csrf_token;
        //display pages that link to the particular page
        ?><table><tr><td><?php
        if (!empty($data["PAGES_THAT_LINK_TO"])) {
            foreach ($data["PAGES_THAT_LINK_TO"] as $key => $value) {
                $var = $data["PAGES_THAT_LINK_TO"][$key]["PAGES_LINKING_TO"];
                $url = htmlentities(B\wikiUrl($var, true, $data['CONTROLLER'] ,
                    $data['GROUP_ID'])).$csrf_token."&amp;arg=relationships" .
                    "&amp;page_id=" .
                    $data["PAGES_THAT_LINK_TO"][$key]["PAGE_ID"] .
                    "&amp;reltype=" . $data["REL-TYPE"];?>
                <div class="center"><a href='<?=$url?>' ><?=$var?>
                    </a></div><?php
            }
        }
        ?></td><tr><?php
        $rel_top = (!empty($data["TOTAL_TO_PAGES"])) ? "rel-type-top" : "";
        ?>
        <tr><td class="<?=$rel_top ?> center">|</td></tr><?php
        //displaying the middle table - Current Page Name
        ?><tr><td><div class="rel-type-current"><b><a
            href='<?=$url_main ?>' ><?=$page_name?></a></b><br><?php
            if (empty($data["RELATIONSHIPS"])) {
                e("<b class='small-font'>".tl('wiki_element_no_cur_rel').
                    "</b>");
            } else {
                foreach ($data["RELATIONSHIPS"] as $key => $value) {
                    $url = $url_main . "&amp;arg=relationships" .
                        "&amp;page_id=" . $page_id;
                    $relationship_type =
                        $data["RELATIONSHIPS"][$key]["RELATIONSHIP_TYPE"];
                    $url .= "&amp;reltype=" . $relationship_type;
                    if (!empty($data["REL-TYPE"]) &&
                        $relationship_type == $data["REL-TYPE"]) {
                        $relationship_type = "<b>$relationship_type</b>";
                    }
                    e("<a class='small-font' href='$url'>".
                        "$relationship_type</a><br>");
                }
            }
           ?></div></td></tr><?php
        $rel_bottom = (!empty($data["TOTAL_FROM_PAGES"])) ?
            "rel-type-bottom" : "";
        ?>
        <tr><td class="<?=$rel_bottom ?> center">|</td></tr><?php
        //display pages that link from the particular page
        ?><tr><td><?php
        if (!empty($data["PAGES_THAT_LINK_FROM"])) {
            foreach ($data["PAGES_THAT_LINK_FROM"] as $key => $value) {
                $var =
                    $data["PAGES_THAT_LINK_FROM"][$key]["PAGES_LINKING_FROM"];
                $url = htmlentities(B\wikiUrl($var, true,
                    $data['CONTROLLER'], $data['GROUP_ID'])).$csrf_token.
                    "&amp;arg=relationships"."&amp;page_id=".
                    $data["PAGES_THAT_LINK_FROM"][$key]["PAGE_ID"].
                    "&amp;reltype=".$data["REL-TYPE"];
                ?><div class="center"><a href='<?=$url?>'><?=$var
                ?></a></div><?php
            }
        }
        ?></td></tr></table><?php
    }
    /**
     * Used to draw the revision history page for a wiki document
     * Has a form that can be used to draw the diff of two revisions
     *
     * @param array $data fields contain info about revisions of a Wiki page
     */
    public function renderHistory($data)
    {
        $base_query = htmlentities(B\wikiUrl("", true, $data['CONTROLLER'],
            $data["GROUP"]["GROUP_ID"]) .C\p('CSRF_TOKEN')."=".
            $data[C\p('CSRF_TOKEN')]);
        $append = "";
        if (isset($data['OTHER_BACK_URL']) && $data['OTHER_BACK_URL'] != '') {
            $append = $data['OTHER_BACK_URL'];
        }
        $edit_or_source = (!empty($data['CAN_EDIT'])) ? 'edit' : 'source';
        if (count($data['HISTORY']) > 1) { ?>
            <div>
            <form id="differenceForm" method="get">
            <input type="hidden" name="c" value="<?=$data['CONTROLLER']
             ?>" >
            <input type="hidden" name="<?=C\p('CSRF_TOKEN') ?>" value="<?=
                $data[C\p('CSRF_TOKEN')] ?>" >
            <input type="hidden" name="a" value="wiki" >
            <input type="hidden" name="arg" value="history" >
            <input type="hidden" name="group_id" value="<?=
                $data['GROUP']['GROUP_ID'] ?>" >
            <input type="hidden" name="page_id" value="<?=
                $data["PAGE_ID"] ?>" >
            <input type="hidden" name="diff" value="1" >
            <b><?=tl('wiki_element_difference') ?></b>
            <input type="text" id="diff-1" name="diff1"
                value="<?=$data['diff1'] ?>" > -
            <input type="text" id="diff-2" name="diff2"
                value="<?= $data['diff2'] ?>" >
            <button class="button-box" type="submit"><?=
                tl('wiki_element_go') ?></button>
            </form>
            </div>
            <?php
        }
        ?>
        <div>&nbsp;</div>
        <?php
        $time = time();
        $feed_helper = $this->view->helper("feeds");
        $base_query .= "&amp;arg=history&amp;page_id=".$data["PAGE_ID"];
        $first = true;
        foreach ($data['HISTORY'] as $item) {
            ?>
            <div class='group-result'>
            <?php
            if (count($data['HISTORY']) > 1) { ?>
                (<a href="javascript:updateFirst('<?=$item['PUBDATE']
                    ?>');" ><?= tl("wiki_element_diff_first")
                    ?></a> | <a href="javascript:updateSecond('<?=
                    $item['PUBDATE']?>');" ><?= tl("wiki_element_diff_second")
                    ?></a>)
                <?php
            } else { ?>
                (<b><?= tl("wiki_element_diff_first")
                    ?></b> | <b><?= tl("wiki_element_diff_second")
                    ?></b>)
                <?php
            }
            e("<a href='$base_query&show={$item['PUBDATE']}'>" .
                date("c",$item["PUBDATE"])."</a>. <b>{$item['PUBDATE']}</b>. ");
            e(tl("wiki_element_edited_by", $item["USER_NAME"]));
            if (strlen($item["EDIT_REASON"] ?? "") > 0) {
                e("<i>{$item["EDIT_REASON"]}</i>. ");
            }
            e(tl("wiki_element_page_len", $item["PAGE_LEN"])." ");
            if (!empty($data['CAN_EDIT'])) {
                if ($first && $data['LIMIT'] == 0) {
                    e("[<b>".tl("wiki_element_revert")."</b>].");
                } else {
                    e("[<a href='$base_query&amp;revert=".$item['PUBDATE'].
                    "'>".tl("wiki_element_revert")."</a>].");
                }
            }
            $first = false;
            $next = $item['PUBDATE'];
            ?>
            </div>
            <div>&nbsp;</div>
            <?php
        }?>
        <div class="page-list-header-footer"><?php
        $this->view->helper("pagination")->render(
            $base_query,
            $data['LIMIT'], $data['RESULTS_PER_PAGE'], $data['TOTAL_ROWS']);
        ?></div>
        <script>
        function updateFirst(val)
        {
            elt('diff-1').value=val;
        }
        function updateSecond(val)
        {
            elt('diff-2').value=val;
        }
        </script>
        <?php
    }
    /**
     * Used to drawn the form that let's someone see the source of a wiki page
     *
     * @param array $data fields contain data about the page being
     *      edited. In particular, PAGE contains the raw page data
     */
    public function renderSourcePage($data)
    {
        $icon_helper = $this->view->helper('iconlink');
        $simple_base_url = B\wikiUrl("", true,
            $data['CONTROLLER'], $data['GROUP']['GROUP_ID']) .
            C\p('CSRF_TOKEN') . '=' . $data[C\p('CSRF_TOKEN')];
        $base_url = htmlentities($simple_base_url);
        $simple_view_url = B\wikiUrl($data['PAGE_NAME'], true,
           $data['CONTROLLER'], $data['GROUP']['GROUP_ID']) .
           C\p('CSRF_TOKEN') . '=' . $data[C\p('CSRF_TOKEN')];
        $simple_current_url = $simple_view_url . "&arg=source";
        $is_settings_view = !empty($data['settings']) &&
            $data['settings'] != "false";
        $is_resources_view = !empty($data['resources']) &&
            $data['resources'] != "false";
        $is_media_list =
            !empty($data['page_type']) && $data['page_type'] == 'media_list';
        $is_page_alias =
            !empty($data['page_type']) && $data['page_type'] == 'page_alias';
        $is_shortener =
            !empty($data['page_type']) && $data['page_type'] == 'url_shortener';
        $is_share =
            !empty($data['page_type']) && $data['page_type'] == 'share';
        $is_git_repository = !empty($data['page_type']) &&
            $data['page_type'] == 'git_repository';
        if (!empty($data['SUB_PATH'])) {
            $simple_view_url .= "&sf=".urlencode($data['SUB_PATH']);
            $simple_current_url .= "&sf=".urlencode($data['SUB_PATH']);
        }
        $current_url = htmlentities($simple_current_url);
        $new_settings = $is_settings_view ? "&amp;settings=false" :
            "&amp;settings=true";
        $new_resources = $is_resources_view ? "&amp;resources=false" :
            "&amp;resources=true";
        $is_settings_view = !empty($data['settings']) &&
            $data['settings'] != "false";
        $is_resources_view = !empty($data['resources']) &&
            $data['resources'] != "false";
        $settings_toggle_url = $current_url . $new_settings;
        $resources_toggle_url = $current_url . $new_resources;
        $append = "";
        if (isset($data['OTHER_BACK_URL'])) {
            $append = $data['OTHER_BACK_URL'];
        }
        $icon_helper = $this->view->helper('iconlink');
        if ($is_settings_view || $is_resources_view) {
            $toggle_url = ($is_settings_view) ? $settings_toggle_url :
                $resources_toggle_url;
            ?>
            <div class="float-opposite" ><?=
            $this->view->helper('close')->render($toggle_url);
            ?></div>
            <?php
        } else if (!$is_git_repository) { ?>
            <div class="float-opposite wiki-history-discuss" >
            <?=
            $icon_helper->renderButton( $base_url . $append .
                '&amp;arg=history&amp;page_id='.$data['PAGE_ID'], "history");?>
            </div><?php
        } ?>
        <form id="editpageForm" onsubmit="
            var caret_pos = elt('caret-pos');
            var scroll_top = elt('scroll-top');
            var wiki_page = elt('wiki-page');
            if (caret_pos && scroll_top && wiki_page) {
                caret_pos.value =
                (wiki_page.selectionStart) ?
                wiki_page.selectionStart : 0;
                scroll_top.value= (wiki_page.scrollTop) ?
                wiki_page.scrollTop : 0;
            }" >
            <input type="hidden" name="c" value="<?=$data['CONTROLLER']
            ?>" >
            <input type="hidden" name="<?=C\p('CSRF_TOKEN') ?>" value="<?=
                $data[C\p('CSRF_TOKEN')] ?>" >
            <input type="hidden" name="a" value="wiki" >
            <input type="hidden" name="arg" value="source" >
            <?php
            if (isset($data['BACK_PARAMS'])) {
                foreach ($data["BACK_PARAMS"] as
                         $back_param_key => $back_param_value) {
                    e('<input type="hidden" '
                        . 'name="' . $back_param_key .
                        '" value="' .
                        $back_param_value
                        . '" >');
                }
            }
            ?>
            <input type="hidden" name="group_id" value="<?=
                $data['GROUP']['GROUP_ID'] ?>" >
            <input type="hidden" name="page_name" value="<?=
                $data['PAGE_NAME'] ?>" >
            <input type="hidden" name="caret" id="caret-pos" >
            <input type="hidden" name="scroll_top" id="scroll-top" >
            <input type="hidden" id="p-settings" name="settings" value="<?=
                $data['settings'] ?>" >
            <div class="top-margin">
                <b><?=tl('wiki_element_locale_name',
                    $data['CURRENT_LOCALE_TAG']) ?></b><br>
                <b><label for="wiki-page"><?php
                $human_page_name = preg_replace("/\_/", " ",
                    urldecode($data['PAGE_NAME']));
                e(tl('wiki_element_page', $human_page_name));
                ?></label></b><?php
                if (!$is_settings_view && !$is_resources_view) { ?>
                    <div id="toggle-settings">
                    <?php
                    $icon_helper->renderButton(['settings-toggle-button',
                        $settings_toggle_url], 'settings', "");
                    if (!$is_media_list && !$is_page_alias && !$is_share &&
                        !$is_shortener && !$is_git_repository) {
                        $icon_helper->renderButton(
                            ['resources-toggle-button',
                            $resources_toggle_url], 'resources', "");
                    }
                    if ($is_git_repository) {
                        $icon_helper->renderButton(htmlentities(B\wikiUrl(
                            "pages", true, "group",
                            $data['GROUP']['GROUP_ID'])) .
                            C\p('CSRF_TOKEN') .'=' . $data[C\p('CSRF_TOKEN')],
                            'pages', "", false,
                            "media-buttons-container small-margin",
                            "media-anchor-button small-font", true);
                    }?>
                    </div><?php
                } ?>
            </div>
            <?php if (!($is_git_repository && !$is_settings_view)) { ?>
            <div id='page-settings'>
            <div class="top-margin">
            <label for="page-type"><b><?=tl('wiki_element_page_type')
            ?></b></label><?php
            $this->view->helper("options")->render("page-type", "page_type",
                $data['page_types'], $data['current_page_type'], true,
                ['disabled' => 'disabled']);
            ?>
            </div>
            <div id='alias-type'>
            <div class="top-margin">
            <label for="page-alias"><b><?=tl('wiki_element_page_alias')
            ?></b></label><input type="text" id='page-alias' disabled="disabled"
                name="page_alias" value="<?= $data['page_alias']?>"
                maxlength="<?= C\SHORT_TITLE_LEN ?>" class="wide-field" >
            </div>
            </div>
            <div id='non-alias-type'>
            <div class="top-margin">
            <label for="page-border"><b><?=tl('wiki_element_page_border')
            ?></b></label><?php
            $this->view->helper("options")->render("page-border","page_border",
                $data['page_borders'], $data['page_border'], false,
                ['disabled' => 'disabled']);
            ?>
            </div>
            <div class="top-margin">
            <label for="page-toc"><b><?=tl('wiki_element_table_of_contents')
            ?></b></label><input type="checkbox" name="toc" value="true"
                <?php
                    $checked = (isset($data['toc']) && $data['toc']) ?
                    'checked="checked"' : '';
                    e( $checked );
                ?> id='page-toc' disabled="disabled" >
            </div>
            <div class="top-margin">
            <label for="page-title"><b><?=tl('wiki_element_title')
            ?></b></label><input type="text" id='page-title'
                disabled="disabled"
                name="title" value="<?=$data['title'] ?>"
                maxlength="<?= C\SHORT_TITLE_LEN ?>" class="wide-field" >
            </div>
            <div class="top-margin">
            <label for="meta-author"><b><?=tl('wiki_element_meta_author')
            ?></b></label><input type="text" id='meta-author'
                name="author" value="<?= $data['author']?>"
                disabled="disabled"
                maxlength="<?= C\LONG_NAME_LEN ?>" class="wide-field" >
            </div>
            <div class="top-margin">
            <label for="meta-robots"><b><?=tl('wiki_element_meta_robots')
            ?></b></label><input type="text" id='meta-robots'
                disabled="disabled"
                name="robots" value="<?= $data['robots'] ?>"
                maxlength="<?=C\LONG_NAME_LEN ?>" class="wide-field" >
            </div>
            <div class="top-margin">
            <label for="meta-description"><b><?=
                tl('wiki_element_meta_description')
            ?></b></label>
            </div>
            <textarea id="meta-description" class="short-text-area"
                disabled="disabled"
                name="description" data-buttons='none'><?=$data['description']
            ?></textarea>
            <div class="top-margin">
            <label for="meta-properties"><b><?=
                tl('wiki_element_meta_properties')
            ?></b></label>
            </div>
            <div><?=tl('wiki_element_meta_property_format')?></div>
            <textarea id="meta-properties" class="short-text-area"
                disabled="disabled"
                name="properties" data-buttons='none'><?=$data['properties']
            ?></textarea><?php
            if (!empty($_SESSION['USER_ID']) &&
                $_SESSION['USER_ID'] == C\ROOT_ID) { ?>
                <div class="top-margin">
                <label for="alt-path"><b><?=tl('wiki_element_alternative_path')
                ?></b></label><input type="text" id='alt-path'
                    placeholder="<?=tl('wiki_element_empty_use_default')
                    ?>" name="alternative_path" value="<?=
                    $data['alternative_path'] ?>"
                    disabled="disabled"
                    maxlength="<?=C\LONG_NAME_LEN ?>" class="wide-field" >
                </div>
                <?php
            }
            ?>
            <div class="top-margin">
            <label for="page-header"><b><?=tl('wiki_element_page_header')
            ?></b></label><input type="text" id='page-header'
                name="page_header" value="<?=$data['page_header']?>"
                disabled="disabled"
                maxlength="<?=C\SHORT_TITLE_LEN ?>" class="wide-field" >
            </div>
            <div class="top-margin">
            <label for="page-footer"><b><?=tl('wiki_element_page_footer')
            ?></b></label><input type="text" id='page-footer'
                name="page_footer" value="<?=$data['page_footer'] ?>"
                disabled="disabled"
                maxlength="<?= C\SHORT_TITLE_LEN ?>" class="wide-field" >
            </div>
            </div>
        </div>
        <?php } ?><?php
        if (!$is_media_list && !$is_resources_view && !$is_settings_view &&
            $is_git_repository) { ?>
            <div id='page-container'>
            <h2><?= tl("wiki_element_git_access") ?></h2>
            <div class="git-clone-line git-open">
            <code class="git-clone-cmd">git clone <?=
                $data["GIT_CLONE_URL"] ?></code>
            <?php $icon_helper->renderFormButton("button", "clone_copy",
                false, "git-icon-control", 'onclick="gitCopyClone(this)" ' .
                'data-clone="' . $data["GIT_CLONE_URL"] . '"'); ?>
            </div>
            <?php $this->renderGitStatistics($data); ?>
            </div><?php
        } else if (!$is_media_list && !$is_resources_view &&
            !$is_settings_view) {?>
            <div id='page-container'><textarea id="wiki-page"
                class="tall-text-area" name="page" data-buttons='none'
                disabled="disabled"><?= $data['PAGE']
                ?></textarea>
            </div><?php
        } ?>
        </form>
        <?php
        if (($is_media_list && !$is_settings_view )|| $is_resources_view) {?>
            <div id="page-resources">
            <h3><?= tl('wiki_element_page_resources')?></h3>
            <p><?= tl('wiki_element_resources_info') ?></p>
            </div><?php
            $this->renderResources($data, "source");
            ?></div>
            <?php
        }
    }
    /**
     * Renders the ballot count authorization view for a wiki poll page.
     *
     * If the current user has permission to update the poll, displays a heading
     * and instructions followed by the witness credential form to authorize
     * tallying the votes. Otherwise, displays a "not authorized" heading.
     *
     * @param array $data  View data array. Expected keys:
     *   - 'CAN_UPDATE_POLL' (bool) Whether the current user can authorize the
     *          vote count.
     *   - 'PAGE_NAME' (string) The name of the wiki page hosting the poll.
     *   - 'WITNESSES' (array)  List of witness identifiers (passed through to
     *          renderWitnessForm).
     *   - C\p('CSRF_TOKEN') (string) CSRF token value for the hidden input.
     * @return void
     */
    public function renderBallotCount($data)
    {
        if (!empty($data['CAN_UPDATE_POLL'])) {
            ?><h1><?=tl('wiki_element_authorize_vote_count',
                $data['PAGE_NAME']) ?></h1>
            <p><?=tl('wiki_element_vote_count_instructions') ?></p>
            <form method="post">
            <input type="hidden" name="<?=C\p('CSRF_TOKEN')?>" value="<?=
                $data[C\p('CSRF_TOKEN')] ?>" >
            <?php
            $this->renderWitnessForm($data);
        } else {?>
            <h1><?=tl('wiki_element_not_authorized_to_end_poll') ?></h1><?php
        }
    }
    /**
     * Renders the ballot initialization element for a wiki poll page.
     *
     * If the current user has permission to update the poll, displays a heading
     * and instructions followed by the witness credential form. Otherwise,
     * displays a "not initialized" heading.
     *
     * @param array $data  View data array. Expected keys:
     *  - 'CAN_UPDATE_POLL' (bool) whether the current user can initialize
     *       the poll.
     *  - 'PAGE_NAME' (string) the name of the wiki page hosting the poll.
     *  - 'WITNESSES' (array) list of witness identifiers
     *    (passed through to renderWitnessForm).
     */
    public function renderBallotInit($data)
    {
        if (!empty($data['CAN_UPDATE_POLL'])) {
            ?><h1><?=tl('wiki_element_vote_initialization',
                $data['PAGE_NAME']) ?></h1>
            <p><?=tl('wiki_element_vote_init_instructions') ?></p>
            <?php
            $this->renderWitnessForm($data);
        } else {?>
            <h1><?=tl('wiki_element_vote_not_initialized') ?></h1><?php
        }
    }
    /**
     * Renders the witness credential form used to initialize or authorize a
     * ballot.
     *
     * Outputs an HTML form with a CSRF token and one password field per
     * witness.  When LDAP is enabled, an additional visible text field is shown
     * for the witness login name; otherwise the witness value is submitted as
     * a hidden field.
     *
     * @param array $data  View data array. Expected keys:
     *  - 'WITNESSES'  (array)  Ordered list of witness identifier strings.
     *  - C\p('CSRF_TOKEN') (string) CSRF token value for the hidden input.
     * @return void
     */
    public function renderWitnessForm($data)
    {?>
        <form method="post">
        <input type="hidden" name="<?=C\p('CSRF_TOKEN')?>" value="<?=
            $data[C\p('CSRF_TOKEN')] ?>" >
        <?php
        $i = 0;
        foreach ($data['WITNESSES'] as $witness) {
            if (C\nsdefined("USE_LDAP") && C\USE_LDAP) {?>
                <div class='csv-form-field'><label
                    for="witness-<?=$i ?>"><?=
                    tl('wiki_element_witness_login', $witness)
                ?></label>: <input id="witness-<?=$i ?>" type='text' <?php
                    ?> name='WITNESS[<?=$i ?>]' ></div><?php
            } else {?>
                <input type='hidden' name='WITNESS[<?=$i ?>]' <?php
                    ?> value='<?=$witness ?>' ><?php
            } ?>
            <div class='csv-form-field'><label for="password-<?=$i ?>"><?=
                tl('wiki_element_witness_password', $witness) ?></label>:
            <input id="password-<?=$i ?>" type='password' <?php
                ?> name='PASSWORD[<?=$i ?>]' ></div><?php
                $i++;
        }
        ?>
        <div class='csv-form-field'><input
            type='submit' name='WITNESS_SUBMIT' value='<?=
                tl('wiki_element_witness_submit') ?>' ></div>
        </form><?php
    }
    /**
     * Renders the concluded ballot results view.
     *
     * Outputs a results heading with a placeholder container for vote
     * histograms  and a raw data heading with a placeholder container for a
     * spreadsheet view. Both containers are expected to be populated
     * client-side via JavaScript.
     *
     * @param array $data - view data array. Not currently used by this method,
     *  but accepted for interface consistency.
     * @return void
     */
    public function renderBallotConcluded($data)
    {
        ?>
        <h1><?= tl('wiki_element_vote_results'); ?></h1>
        <?php
        if (!empty($_REQUEST['ballot_message']) &&
            $_REQUEST['ballot_message'] == 'FORM_MISMATCH') { ?>
        <h2 class="red"><?=tl('wiki_element_form_mismatch'); ?></h2>
        <?php
        }
        ?>
        <div id='histograms'>&nbsp;</div>
        <h1><?= tl('wiki_element_raw_data'); ?></h1>
        <p><?=tl('wiki_element_check_form_hashes'); ?></p>
        <div id='spreadsheet'>&nbsp;</div><?php
    }
    /**
     * The controller used to display a wiki page might vary (could be
     * group or static). Links within a wiki page need to be updated
     * to reflect which controller is being used. This method does the
     * update.
     *
     * @param int $group_id id of wiki page the passed page belongs to
     * @param array $data fields etc which will be sent to the view
     * @param string $pre_page a wiki page where links,etc have not yet
     *      had dynamic substitutions applied
     * @return string page after subustitutions
     */
    public function dynamicSubstitutions($group_id, $data, $pre_page)
    {
        $csrf_token = "";
        $no_amp_csrf_token = "";
        $no_right_amp_csrf_token = "";
        $resource_csrf_token = "";
        $resource_no_amp_csrf_token = "";
        $resource_no_right_amp_csrf_token = "";
        if (!empty($data['ADMIN'])) {
            $resource_token = $data[C\p('CSRF_TOKEN')];
            if ($group_id == C\PUBLIC_GROUP_ID ||
                (!empty($data["GROUP"]["GROUP_ID"]) &&
                !empty($data["GROUP"]["REGISTER_TYPE"]) &&
                $group_id == $data["GROUP"]["GROUP_ID"]) &&
                $data["GROUP"]["REGISTER_TYPE"] == C\PUBLIC_JOIN) {
                $resource_token = str_pad("", 11,  "0");
            }
            $no_amp_csrf_token = C\p('CSRF_TOKEN') . "=" .
                $data[C\p('CSRF_TOKEN')];
            $resource_no_amp_csrf_token = C\p('CSRF_TOKEN') . "=" .
                $resource_token;
            $no_right_amp_csrf_token =
                "&amp;". $no_amp_csrf_token;
            $resource_no_right_amp_csrf_token = "&amp;".
                $resource_no_amp_csrf_token;
            $csrf_token = $no_right_amp_csrf_token . "&amp;";
            $resource_csrf_token = $resource_no_right_amp_csrf_token . "&amp;";
        }
        if ($data['CONTROLLER'] == 'static') {
            $address = C\SHORT_BASE_URL . "p/";
            $pre_address = B\controllerUrl("group", true) .
                "{$csrf_token}a=wiki&amp;arg=read&amp;";
        } else {
            $pre_address = B\controllerUrl($data['CONTROLLER'], true) .
                "{$csrf_token}a=wiki&amp;arg=read&amp;";
            $address = $pre_address .
                "group_id=$group_id&amp;page_name=";
        }
        $pre_page = preg_replace('/@@(.*)@(.*)@@/',
            $pre_address . "group_name=$1&amp;page_name=$2", $pre_page);
        $pre_page = preg_replace('/\[{controller_and_page}\]/', $address,
            $pre_page);
        $pre_page = preg_replace('/\[{controller}\]/', $data['CONTROLLER'],
            $pre_page);
        $pre_page = preg_replace('/\/\[{token}\]\//',
            (empty($no_amp_csrf_token)) ? "/-/" : "/$no_amp_csrf_token/" ,
            $pre_page);
        $pre_page = preg_replace('/\[{token}\]/', $csrf_token,
            $pre_page);
        $pre_page = preg_replace('/\/\[{rtoken}\]\//',
            (empty($no_amp_csrf_token)) ? "/-/" :
            "/$resource_no_amp_csrf_token/" , $pre_page);
        $pre_page = preg_replace('/\[{rtoken}\]/', $resource_csrf_token,
            $pre_page);
        $pre_page = preg_replace('/\[{just\-token}\]/',
            $data[C\p('CSRF_TOKEN')],
            $pre_page);
        if (preg_match('/\[\{keyword-captcha\|(.+?)\}\]/si',
            $pre_page, $matches)) {
            if (empty($_SESSION)) {
                $_SESSION = [];
            }
            unset($_SESSION["captcha_text"]);
            $_SESSION["captcha_text"] = $matches[1];
            $_SESSION["captcha_keyword_required"] = true;
            $pre_page =
                preg_replace('/\[\{keyword-captcha\|(.+?)\}\]/si',
                "" , $pre_page);
        }
        $proof_random = $_SESSION["random_string"] ?? "";
        $proof_time = $_SESSION["request_time"] ?? "";
        $proof_level = $_SESSION["level"] ?? "";
        $proof_markup = "<input type='hidden' name='nonce_for_string' " .
            "id='nonce_for_string' >" .
            "<input type='hidden' name='random_string' id='random_string' " .
            "value='" . $proof_random . "' >" .
            "<input type='hidden' name='time' id='time' value='" .
            $proof_time . "' >" .
            "<input type='hidden' name='level' id='level' value='" .
            $proof_level . "' >";
        $pre_page = preg_replace('/\[{proof-of-work}\]/', $proof_markup,
            $pre_page);
        $username = $_SESSION['USER_NAME'] ?? "PUBLIC_USER";
        $pre_page = preg_replace('/\[{username}\]/', $username, $pre_page);
        if (preg_match('/\[{form\-hash([^}]+)}\]/', $pre_page,
            $hash_matches) != false) {
            $page_hash = L\crawlAuthHash($data[C\p('CSRF_TOKEN')] .
                $hash_matches[1]);
            $pre_page = preg_replace('/\[{form-hash([^}]+)}\]/', $page_hash,
                $pre_page);
            $vote_receipt = $_REQUEST['VOTE_RECEIPT'] ?? "";
            if (!empty($vote_receipt)) {
                $pre_page = "<div class='vote-receipt' ><h1>".
                    tl('wiki_element_vote_cast') ."</h1>".
                    "<p>".  tl('wiki_element_vote_receipt'). "<br>".
                    $vote_receipt . "</p></div>";
            }
            if (empty($_REQUEST['SUBMIT_SUCCESSFUL'])) {
                $pre_page = preg_replace('/\[{onsubmit\|([^}]+)}\]/', '',
                    $pre_page);
                $pre_page = preg_replace('/\[{presubmit\|([^}]+)}\]/', '$1',
                    $pre_page);
            } else {
                $pre_page = preg_replace('/\[{onsubmit\|([^}]+)}\]/', '$1',
                    $pre_page);
                $pre_page = preg_replace('/\[{presubmit\|([^}]+)}\]/', '',
                    $pre_page);
            }
            if (preg_match_all("/\[{a(?:lias)?-def\|([^\|}\n]+?)\|".
                "([^\|}\n]+?)}\]/si", $pre_page,
                $option_matches, PREG_SET_ORDER) > 0) {
                foreach ($option_matches as $option_match) {
                    list($replace, $alias, $substitution) = $option_match;
                    $pre_page = str_replace($replace, "", $pre_page);
                    $pre_page = str_replace("[{a~$alias}]", $substitution,
                        $pre_page);
                }
            }
            if (preg_match_all("/\[{(?:r|s)-ck!!([^\!\n]+?)!!([^\!\n]+?)!!".
                "([^\!\n]+?)!!([^\!\n]+?)}\]/si", $pre_page,
                $option_matches, PREG_SET_ORDER) > 0) {
                foreach ($option_matches as $option_match) {
                    list($replace, $field, $list, $if_true, $if_false) =
                        $option_match;
                    $list = preg_split("/(\s+,?\s*)|(\s*,\s*)/",
                        $list);
                    $assoc = (str_starts_with($replace, "[{s")) ?
                        $_SESSION : $_REQUEST;
                    if ((!empty($assoc[$field]) &&
                        in_array($assoc[$field], $list)) ||
                        (!empty($assoc['WIKI_FORM'][$field]) &&
                        in_array($assoc['WIKI_FORM'][$field], $list))) {
                        $pre_page = str_replace($replace, $if_true,
                            $pre_page);
                    } else {
                        $pre_page = str_replace($replace, $if_false,
                            $pre_page);
                    }
                }
            }
            if (preg_match_all("/\[{s-set\|([^\|}\n]+?)\|([^\|}\n]+?)}\]/si",
                $pre_page, $option_matches, PREG_SET_ORDER) > 0) {
                foreach ($option_matches as $option_match) {
                    list($replace, $session_field, $session_value) =
                        $option_match;
                    $pre_page = str_replace($replace, "", $pre_page);
                    $_SESSION['WIKI_FORM'][$session_field] = $session_value;
                }
            }
            if (preg_match_all("/\[{s-unset\|([^\|}\n]+?)}\]/si",
                $pre_page, $option_matches, PREG_SET_ORDER) > 0) {
                foreach ($option_matches as $option_match) {
                    list($replace, $session_field) =
                        $option_match;
                    $pre_page = str_replace($replace, "", $pre_page);
                    unset($_SESSION['WIKI_FORM'][$session_field]);
                }
            }
            if (preg_match_all('/\[{csv\-(.+)}\]/', $pre_page,
                $csv_field_matches, PREG_SET_ORDER) > 0) {
                foreach ($csv_field_matches as $csv_field_match) {
                    if (empty($csv_field_match[1])) {
                        continue;
                    } else if (str_starts_with($csv_field_match[1],
                        "checked-")) {
                        $csv_field_name = substr($csv_field_match[1], 8);
                        $replace = (empty($_REQUEST[$csv_field_name])) ? "" :
                            ' checked="checked" ';
                    } else {
                        $csv_field_name = $csv_field_match[1];
                        $replace = $_REQUEST[$csv_field_name] ?? "";
                    }
                    $pre_page = str_replace($csv_field_match[0],
                        $replace, $pre_page);
                }
            }
        }
        /* A block widget written on its own line comes out wrapped in a
           paragraph; unwrap it so the form or dropdown that replaces it is
           not left inside a <p>, which is not valid html. */
        $pre_page = preg_replace(
            '/<p>\s*(\[{(?:search\|[^}]*|recent_places)}\])\s*<\/p>/',
            '$1', $pre_page);
        if (stripos($pre_page, "[{recent_places}]") !== false) {
            ob_start();
            $this->renderPath("", $data, [], "", "",
                "just_groups_and_pages");
            $recent_dropdown = ob_get_clean();
            $pre_page = preg_replace('/\[{recent_places}\]/', $recent_dropdown,
                $pre_page);
        }
        if(preg_match('/\[\{date-string\|([^\}]+)\}\]/', $pre_page,
            $matches) && !empty($matches[1])) {
            $date_string = date($matches[1]);
            $pre_page = preg_replace('/\[\{date-string\|([^\}]+)\}\]/',
                $date_string, $pre_page);
        }
        if (strpos($pre_page, "[{search|") !== false) {
            $pre_page = $this->substituteWikiSearchForm($pre_page);
        }
        return $pre_page;
    }
    /**
     * Turns a <code>[{search|its|size|placeholder}]</code> token left by the
     * parser into a working site search box. The form is built here, at
     * display time, because it needs the site's search address and the
     * icon-link helper's buttons, which a wiki page's parser does not have.
     *
     * @param string $pre_page the page html that may hold a search token
     * @return string the page html with any search token turned into a form
     */
    protected function substituteWikiSearchForm($pre_page)
    {
        $search_url = C\SHORT_BASE_URL;
        $icon = $this->view->helper("iconlink");
        $search_form = <<<EOD
<form method="get" action='$search_url' class="search-box $2-search-box" >
<div class="search-box-inner"  tabindex="0">
<input type='hidden' name="its" value='$1' >
<input type='text'  name='q'  value="" placeholder='$3'
    title='$3' class='search-input' >
EOD;
        $search_form .= $icon->renderFormButton("reset", "reset", true,
            "search-reset-button") . $icon->renderFormButton("submit",
            "search", true, "search-button") . <<<EOD
</div>
</form>
EOD;
        return preg_replace('/\[{search\|(.+?)\|(.+?)\|(.+?)}\]/',
            $search_form, $pre_page);
    }
    /**
     * Renders the podcast download status for a Media List page whose
     * resource folder is fed by one or more podcast search sources.
     * When a download is in progress it shows a live indicator; when
     * idle it shows a button that requests an immediate update. A
     * small poller flips between the two by checking the podcast
     * status endpoint, so the page reflects the current state without
     * a manual reload. The relevant fields (PODCAST_FOLDER,
     * PODCAST_DOWNLOADING, PAGE_NAME, GROUP, SUB_PATH, token) come
     * from $data.
     *
     * @param array $data view data prepared by the wiki controller
     */
    public function renderPodcastStatus($data)
    {
        $controller = $data['CONTROLLER'];
        $group_id = $data['GROUP']['GROUP_ID'];
        $token = C\p('CSRF_TOKEN') . '=' . $data[C\p('CSRF_TOKEN')];
        $sub_path_query = "";
        if (!empty($data['SUB_PATH'])) {
            $sub_path_query = "&amp;sf=" . urlencode($data['SUB_PATH']);
        }
        $status_url = htmlentities(B\wikiUrl($data['PAGE_NAME'], true,
            $controller, $group_id)) . $token .
            "&amp;arg=podcast_status" . $sub_path_query;
        $update_url = htmlentities(B\wikiUrl($data['PAGE_NAME'], true,
            $controller, $group_id)) . $token .
            "&amp;arg=podcast_update" . $sub_path_query;
        $idle_html = "<a class='media-anchor-button' href='#' " .
            "onclick='requestPodcastUpdate(); return false;' " .
            "title='" . tl('wiki_element_podcast_update') . "'>" .
            "<span class='hover-lightgray' role='img' aria-label='" .
            tl('wiki_element_podcast_update') .
            "'>&#8635;</span></a>";
        $queued_html = "<span class='gray' title='" .
            tl('wiki_element_podcast_queued') . "' role='img' " .
            "aria-label='" . tl('wiki_element_podcast_queued') .
            "'>&#8987;</span>";
        $downloading_html = "<span class='green' title='" .
            tl('wiki_element_podcast_downloading') . "' role='img' " .
            "aria-label='" . tl('wiki_element_podcast_downloading') .
            "'><svg class='podcast-spin' aria-hidden='true' " .
            "viewBox='0 0 16 16' width='14' height='14'>" .
            "<path fill='none' stroke='currentColor' " .
            "stroke-width='2' stroke-linecap='round' " .
            "d='M13.5 8a5.5 5.5 0 1 1-1.6-3.9'></path>" .
            "<path fill='currentColor' " .
            "d='M11 1.5 L13 4.5 L9.6 4.8 Z'></path></svg></span>";
        $not_running_html = "<span class='red' title='" .
            tl('wiki_element_podcast_not_running') . "' role='img' " .
            "aria-label='" . tl('wiki_element_podcast_not_running') .
            "'>&#9888;</span>";
        $state = $data['PODCAST_STATE'] ?? 'idle';
        $initial = $idle_html;
        if ($state == 'downloading') {
            $initial = $downloading_html;
        } else if ($state == 'queued') {
            $initial = $queued_html;
        } else if ($state == 'not_running') {
            $initial = $not_running_html;
        }
        ?><span id="podcast-status-body"
            class="media-buttons-container"
            ><?= $initial ?></span><?php
        ?>
        <script>
        (function () {
            var poll_time = <?= C\PODCAST_STATUS_POLL_TIME ?>;
            var status_url = "<?= html_entity_decode($status_url) ?>";
            var update_url = "<?= html_entity_decode($update_url) ?>";
            var state_html = {
                idle: <?= json_encode($idle_html) ?>,
                queued: <?= json_encode($queued_html) ?>,
                downloading: <?= json_encode($downloading_html) ?>,
                not_running: <?= json_encode($not_running_html) ?>
            };
            var was_active = <?= ($state == 'downloading' ||
                $state == 'queued') ? 'true' : 'false' ?>;
            function applyPodcastStatus(info)
            {
                var body =
                    document.getElementById("podcast-status-body");
                if (!body) {
                    return;
                }
                var html = state_html[info.state];
                if (!html) {
                    html = state_html.idle;
                }
                body.innerHTML = html;
                var active = (info.state == "downloading" ||
                    info.state == "queued");
                if (was_active && info.state == "idle") {
                    window.location.reload();
                    return;
                }
                was_active = active;
            }
            window.requestPodcastUpdate = function ()
            {
                applyPodcastStatus({state: "queued"});
                fetch(update_url).then(function (response) {
                    return response.json();
                }).then(applyPodcastStatus).catch(function () {
                });
            };
            function refreshPodcastStatus()
            {
                fetch(status_url).then(function (response) {
                    return response.json();
                }).then(applyPodcastStatus).catch(function () {
                });
            }
            setInterval(refreshPodcastStatus, poll_time);
        })();
        </script>
        <?php
    }
}
