/ src / views / elements / WikiElement.php
<?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\controllers\components\SocialComponent;
use seekquarry\yioop\models\Model;
use seekquarry\yioop\views\layouts\WebLayout;
use seekquarry\yioop\library\wiki as LW;

/** For tl, getLocaleTag and Yioop constants */
require_once __DIR__.'/../../library/Utility.php';
/**
 * WikiElement 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
{
    /**
     * $is_settings_view is true where the writer is looking
     * at a page's settings rather than its text.
     * renderEditPageForm works it out, and the methods drawing
     * parts of that screen read it.
     * @var bool
     */
    public $is_settings_view;
    /**
     * $is_resources_view is true where the writer is looking
     * at the files kept with a page rather than its text.
     * renderEditPageForm works it out, and the methods drawing
     * parts of that screen read it.
     * @var bool
     */
    public $is_resources_view;
    /**
     * $is_one_resource is true where the writer is looking
     * at one file kept with a page, opened on its own.
     * renderEditPageForm works it out, and the methods drawing
     * parts of that screen read it.
     * @var bool
     */
    public $is_one_resource;
    /**
     * $shows_spreadsheet is true where the one file being edited holds
     * figures separated by commas, which can be shown as rows or as
     * bars. renderEditPageForm works it out, and the controls above the
     * editor read it to decide whether to offer the swap between the
     * two.
     * @var bool
     */
    public $shows_spreadsheet;
    /**
     * $shows_image is true where the one file being edited is a picture,
     * which is drawn on a canvas the writer may change rather than in
     * the writing box. renderEditPageForm works it out from the file's
     * ending, and renderEditBody reads it to draw the canvas and its
     * controls in place of the text area.
     * @var bool
     */
    public $shows_image;
    /**
     * $shows_media_page is true where the writer is looking
     * at a page whose text is a list of the files kept with it.
     * renderEditPageForm works it out, and the methods drawing
     * parts of that screen read it.
     * @var bool
     */
    public $shows_media_page;
    /**
     * $is_git_repository is true where the writer is looking
     * at a page standing for a git repository.
     * renderEditPageForm works it out, and the methods drawing
     * parts of that screen read it.
     * @var bool
     */
    public $is_git_repository;
    /**
     * $is_page_alias is true where the writer is looking
     * at a page that is another name for a second page.
     * renderEditPageForm works it out, and the methods drawing
     * parts of that screen read it.
     * @var bool
     */
    public $is_page_alias;
    /**
     * $is_share is true where the writer is looking
     * at a page shared by a link that expires.
     * renderEditPageForm works it out, and the methods drawing
     * parts of that screen read it.
     * @var bool
     */
    public $is_share;
    /**
     * $is_shortener is true where the writer is looking
     * at a page standing for a shortened address.
     * renderEditPageForm works it out, and the methods drawing
     * parts of that screen read it.
     * @var bool
     */
    public $is_shortener;
    /**
     * $icon_helper holds the helper that draws a small picture standing for
     * a link.
     * renderEditPageForm works it out, and the methods drawing
     * parts of that screen read it.
     * @var object
     */
    public $icon_helper;
    /**
     * $resources_toggle_url holds the address that turns the list of files
     * kept with a page on or off.
     * renderEditPageForm works it out, and the methods drawing
     * parts of that screen read it.
     * @var string
     */
    public $resources_toggle_url;
    /**
     * $settings_toggle_url holds the address that turns a page's settings
     * on or off.
     * renderEditPageForm works it out, and the methods drawing
     * parts of that screen read it.
     * @var string
     */
    public $settings_toggle_url;
    /**
     * $simple_view_url holds the address of the page as a reader sees it.
     * renderEditPageForm works it out, and the methods drawing
     * parts of that screen read it.
     * @var string
     */
    public $simple_view_url;
    /**
     * $simple_current_url holds the address of the screen the writer is on.
     * renderEditPageForm works it out, and the methods drawing
     * parts of that screen read it.
     * @var string
     */
    public $simple_current_url;
    /**
     * render 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)
    {
        /* A further page of a scrolled list is asked for on its own and
           the answer is just its rows, which the browser adds to the table
           already on the page; none of the rest of the page is drawn. */
        if (!empty($data["GIT_ISSUE_ROWS_ONLY"])) {
            $this->view->element("git")->renderGitIssueRowsOnly($data);
            return;
        }
        /* One place of a front page is asked for on its own, and the
           answer is just that place drawn, which the browser puts where
           the place stands; none of the rest of the page is drawn. */
        if (isset($data["FRONT_PAGE_PLACE"])) {
            e($this->dynamicSubstitutions($data['GROUP_ID_FOR_PLACE'] ?? 0,
                $data, $data["FRONT_PAGE_PLACE"]));
            return;
        }
        $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";
            }
            /* A wiki page is mostly working area whether it is being
               written or read, so it takes the whole of the content
               column rather than a reading width. */
            $wide = in_array($data["MODE"], ["edit", "read", "source"]) ?
                " wide-activity" : "";
            e('<div class="small-margin-current-activity '.
                $page_border. $mode . $wide . '">');
        }
        if (isset($data['BACK_URL'])) {
            $this->view->helper("close")->render(C\SHORT_BASE_URL . "?" .
                $data['BACK_URL'] . "&" . C\p('CSRF_TOKEN') . "=" .
                $data[C\p('CSRF_TOKEN')]);
        }
        $folder_prefix = $base_query . '&arg=read&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']);
                /* moveResourceSwapIntoPlaceRow in spreadsheet.js adds
                   the histogram mark to this span on load. */
                ?><span class="wiki-resource-marks"
                    id="resource-marks-here"><?php
                if (!empty($data['RESOURCE_DOWNLOAD_URL'])) {
                    $this->view->helper('iconlink')->renderButton(
                        $data['RESOURCE_DOWNLOAD_URL'], 'download');
                }
                ?></span><?php
                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 "ballot-part":
                $this->renderBallotPart($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>');
    }
    /**
     * renderReadPage 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->view->element("git")->renderGitRepository($data);
        } else if (!empty($data["PAGE"]) || !empty($data["PAGE_ID"])) {
            $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);
            }
            /* A news article is read from the top down: what it is
               called, the picture that opens it with what it shows and
               who holds it, who wrote it, and then the story. The page's
               own words hold only the story, so the rest is drawn
               here. */
            if (($data["HEAD"]['page_type'] ?? "") == "news_article") {
                $this->renderArticleOpening($data, $group_id);
            }
            ?><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&".
                "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')] .
                    '&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>");
        }
    }
    /**
     * renderShareWall draws the notice a reader sees where a page is
     * shared with them by a link rather than by belonging to a group,
     * and asks them to say who they are before the page is shown.
     *
     * @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
    }
    /**
     * renderWikiPreview draws the pane that shows what the page being edited
     * will look like, beside the source it is written in. Nothing is drawn into
     * it here: the browser renders the source as it is typed, so the pane
     * starts empty and the first render fills it.
     * @param array $data fields set up by the controller, saying whether this
     *     page can be previewed at all and carrying what the renderer needs to
     *     resolve links
     */
    private function renderWikiPreview($data)
    {
        if (empty($data["WIKI_PREVIEW"])) {
            return;
        }
        ?>
        <div class="split-adjuster wiki-preview-bar"></div>
        <div class="wiki-preview">
        <?php
        /* The preview carries the same classes a page being read
           carries, so a domain's own stylesheet dresses what the writer
           sees the way it will dress what a reader sees. */
        $reading_classes = WebLayout::wikiReadingClasses(
            array_merge($data, ['MODE' => 'read']));
        ?>
        <div id="wiki-preview-body" class="wiki-preview-body<?=
            $reading_classes ?>"></div>
        </div><?php
    }
    /**
     * renderWikiPreviewStart starts the browser rendering that keeps the
     * preview pane in step with the source. It sits outside the pair the two
     * columns make so that it is not itself laid out as one of them.
     * @param array $data fields set up by the controller, carrying what the
     *     renderer needs to resolve links
     */
    private function renderWikiPreviewStart($data)
    {
        if (empty($data["WIKI_PREVIEW"])) {
            return;
        }
        ?>
        <script>
        window.addEventListener("load", function() {
            /* The preview's own script is not loaded on every screen that
               reaches here, and calling into it when it is absent stops
               the rest of this page's start-up, which is what sets the
               editor's toolbar up. */
            if (typeof initWikiPreview !== "function") {
                return;
            }
            initWikiPreview("wiki-page", "wiki-preview-body",
                "<?= $data['GROUP_ID'] ?? 0 ?>",
                "<?= $data['WIKI_PREVIEW_PAGE_ID'] ?? 0 ?>",
                "<?= $data['CONTROLLER'] ?? 'group' ?>",
                "<?= C\p('CSRF_TOKEN') ?>",
                "<?= $data[C\p('CSRF_TOKEN')] ?? '' ?>");
        });
        </script><?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
     */
    /**
     * renderCategoryItems draws the pages of one category the way a category's
     * list draws them anywhere else: a picture beside each title, with the
     * article's abstract beneath. A reader meeting a category from a front page
     * and a reader meeting it from an article should see the same thing.
     * @param array $data fields from the controller, holding the pages
     * @param string $controller which controller the links read under
     * @param int $group_id which group the pages belong to
     * @param string $token_string the reader's token, as a query field
     */
    public function renderCategoryItems($data, $controller, $group_id,
        $token_string)
    {
        if (empty($data['PAGES'])) {
            ?><p class="category-names-empty"><?=
                tl('social_component_no_article_in_category')
                ?></p><?php
            return;
        }
        ?><div class="category-list category-list-standard">
        <ul class="category-list-items"><?php
        foreach ($data['PAGES'] as $page) {
            $page_url = htmlentities(B\wikiUrl($page['PAGE_NAME'], true,
                $controller, $group_id)) . $token_string;
            ?><li class="category-list-item">
            <a class="category-list-icon" href="<?= $page_url ?>"><img
                src="<?= $page['ICON_URL'] ?? "" ?>" alt="<?=
                $page['PAGE_NAME'] ?>" ></a>
            <a class="category-list-title" href="<?= $page_url ?>"><?=
                $page['PAGE_NAME'] ?></a>
            <p class="category-list-description"><?=
                strip_tags($page["SHOW_DESCRIPTION"] ?? "") ?></p>
            </li><?php
        }
        ?></ul></div><?php
    }
    /**
     * renderPageListSearch draws the search form that stands over a list of
     * pages    /** Draws the search form that stands over a list of pages: a
     * box to narrow the list by name and a chooser for the order it is in. The
     * whole list of a group's pages and the list of one category both draw this
     * same form, so the two look and behave alike.
     * @param array $data fields from the controller, holding what the reader
     *     last asked for
     * @param string $paging_query the address the order chooser adds to
     * @param string $arg which listing the form asks for again, "pages" for the
     *     whole list or "category" for one category's
     * @param string $category which category, where it is one
     */
    public function renderPageListSearch($data, $paging_query, $arg,
        $category = "")
    {
        ?>
        <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="<?= $arg ?>" ><?php
        if ($category !== "") {
            ?><input type="hidden" name="sf" value="<?= $category
                ?>" ><?php
        }
        ?>
        <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&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
    }
    /**
     * renderArticleOpening draws what stands above a news article's story
     * /** Draws what stands above a news article's story    /** Draws what
     * stands above a news article's story: its title, the banner that opens it
     * with what the picture shows and who holds the rights to it, and who wrote
     * it. A reader should meet these in that order, and the page's own words
     * hold only the story.
     * @param array $data fields from the controller
     * @param int $group_id which group the article belongs to
     */
    public function renderArticleOpening($data, $group_id)
    {
        $head = $data["HEAD"] ?? [];
        $title = trim($head['title'] ?? "");
        if ($title !== "") {
            ?><h2 class="article-title"><?= $title ?></h2><?php
        }
        if (!empty($data['page_icon'])) {
            ?><div class="article-banner"><img class="article-banner-image"
                src="<?= $data['page_icon'] ?>" alt="<?= $title ?>" ><?php
            if (trim($head['banner_caption'] ?? "") !== "") {
                ?><span class="article-banner-caption"><?=
                    $head['banner_caption'] ?></span><?php
            }
            if (trim($head['banner_rights'] ?? "") !== "") {
                ?><span class="article-banner-rights"><?=
                    $head['banner_rights'] ?></span><?php
            }
            ?></div><?php
        }
        if (trim($head['author'] ?? "") !== "") {
            ?><p class="article-author"><b><?= $head['author']
                ?></b></p><?php
        }
        /* What an article is filed under belongs between who wrote it
           and when, not at the foot of the page: a reader deciding
           whether to read it wants to know what it is about first. */
        if (!empty($data['PAGE_CATEGORIES'])) {
            /* A category a page is filed under leads to the group's
               pages narrowed to that category, which is that address with
               the name on the end. */
            $token_string = C\p('CSRF_TOKEN') . "=" .
                ($data[C\p('CSRF_TOKEN')] ?? "");
            ?><div class="article-categories"><span
                class="article-categories-label"><?=
                tl('wiki_element_filed_under') ?></span><?php
            foreach ($data['PAGE_CATEGORIES'] as $one_category) {
                ?> <a class="article-category" href="<?=
                    htmlentities(B\wikiUrl("pages/" . $one_category, true,
                    $data['CONTROLLER'], $group_id)) . $token_string
                    ?>"><?= $one_category ?></a><?php
            }
            ?></div><?php
        }
        /* When the article was written, or when it was last changed,
           whichever the writer chose to say. */
        if (trim($head['article_date'] ?? "") !== "") {
            $said = (($head['article_date_kind'] ?? "date") == "updated") ?
                tl('wiki_element_article_updated') :
                tl('wiki_element_article_date');
            /* The word already carries its colon, so none is added. */
            $when_line = $said . " " . $head['article_date'];
            ?><p class="article-date"><?= $when_line ?></p><?php
        }
    }
    /**
     * renderFrontPageSettings draws what a front page needs: a choice of how
     * its places are laid out, drawn small so a writer picks by the look of it,
     * and for each place a say in what fills it. A place holds either one
     * article, named as a page, or a whole category of them. The places for
     * every layout are drawn, and the ones belonging to the layout chosen are
     * the ones on show.
     * @param array $data fields from the controller, holding the layouts on
     *     offer and what this page was last saved with
     */
    public function renderFrontPageSettings($data)
    {
        $layouts = $data['front_page_layouts'] ?? [];
        if (empty($layouts)) {
            return;
        }
        $chosen = $data['front_page_layout'] ?? "lead-and-list";
        $showing = (($data['current_page_type'] ?? "") == "front_page") ?
            "" : "display:none";
        ?>
        <div class="top-margin" id="front-page-settings" style="<?=
            $showing ?>">
        <b><?= tl('wiki_element_front_page_layout') ?></b>
        <div class="front-page-layouts"><?php
        foreach ($layouts as $name => $says) {
            $picked = ($name == $chosen) ? " checked='checked'" : "";
            ?>
            <label class="front-page-layout" for="layout-<?= $name ?>">
            <input type="radio" id="layout-<?= $name ?>"
                name="front_page_layout" value="<?= $name ?>"<?=
                $picked ?> >
            <?= $this->frontPageLayoutDrawing($name) ?>
            <span class="front-page-layout-name"><?= $says ?></span>
            </label>
            <?php
        }
        ?></div><?php
        foreach ($layouts as $name => $says) {
            $this->renderFrontPageSlots($data, $name, $name == $chosen);
        }
        ?>
        </div><?php
    }
    /**
     * renderFrontPageSlots draws the places one layout holds. A place that can
     * only hold one article takes the name of a page, with the group's pages
     * offered as suggestions; a place that can only hold a category takes one
     * from the group's own list; a place that can hold either says which first,
     * and the control for the other kind waits out of sight.
     * @param array $data fields from the controller
     * @param string $layout which layout these places belong to
     * @param bool $showing whether this layout is the one chosen
     */
    public function renderFrontPageSlots($data, $layout, $showing)
    {
        $kinds = $data['front_page_kinds'] ?? [];
        $slots = $data['front_page_slots'] ?? [];
        $categories = $data['front_page_categories'] ?? [];
        ?>
        <div class="front-page-slots" data-layout="<?= $layout ?>"
            style="<?= $showing ? "" : "display:none" ?>"><?php
        foreach ($this->frontPageSlotNames($data, $layout)
            as $slot => $says) {
            $named = $layout . "-" . $slot;
            $held = $slots[$named] ?? [];
            $only = $this->frontPageSlotHolds($slot);
            $kind = $held['kind'] ?? ($only == "" ? "category" : $only);
            /* What a place holds is kept under the name of the kind it
               holds, so a story's name and a category's name are told
               apart. Reading one settled key found neither. */
            $value = ($kind == "article") ? ($held['page'] ?? "") :
                ($held['category'] ?? "");
            ?>
            <div class="top-margin front-page-slot">
            <div class="front-page-slot-holds">
            <label for="front-slot-<?= $named ?>"><b><?= $says
                ?></b></label><?php
            if ($only == "") {
                $this->view->helper("options")->render(
                    "front-kind-" . $named,
                    "front_page_slot[" . $named . "][kind]", $kinds,
                    $kind, false, ['class' => 'front-page-kind',
                    'data-slot' => $named]);
            } else {
                ?><input type="hidden" name="front_page_slot[<?= $named
                    ?>][kind]" value="<?= $only ?>" ><?php
            }
            $wants_page = ($kind == "article");
            /* A place holding the names of a group's categories names
               nothing itself, so neither the page box nor the category
               chooser belongs beside it. */
            $wants_names = ($kind == "category_names");
            if ($only != "category") {
                ?>
                <input type="text" id="front-slot-<?= $named ?>"
                    name="front_page_slot[<?= $named ?>][page]" value="<?=
                    $wants_page ? $value : "" ?>" maxlength="<?=
                    C\LONG_NAME_LEN ?>" class="wide-field front-page-page"
                    data-slot="<?= $named ?>" list="front-page-pages"
                    style="<?= $wants_page ? "" : "display:none" ?>" >
                <?php
                /* A name that leads nowhere is kept and said so, since a
                   writer may plan a front page before writing the pages
                   it points at. */
                if ($wants_page && !empty($held['missing'])) {
                    ?><span class="front-page-missing"><?=
                        tl('wiki_element_front_page_missing') ?></span><?php
                }
            }
            if ($only != "article" && !$wants_names) {
                $this->view->helper("options")->render(
                    "front-category-" . $named,
                    "front_page_slot[" . $named . "][category]",
                    $categories, $wants_page ? "" : $value, false,
                    ['class' => 'front-page-category',
                    'data-slot' => $named, 'style' =>
                    $wants_page ? "display:none" : ""]);
            }
            ?></div><?php
            /* How much of what a place holds to show: paragraphs of a
               story, or items of a category's list with the choice of
               letting it run on as a reader reaches its end. These sit
               indented beneath what the place holds, one to a line, so
               the row a writer reads first says only what is there. */
            ?><div class="front-page-slot-how-much"><?php
            if ($only != "category") {
                $counts = [];
                foreach (range(1, 10) as $step) {
                    $counts[$step] = tl(
                        'wiki_element_front_page_paragraph_count', $step);
                }
                ?><div class="front-page-how-much" data-slot="<?=
                    $named ?>" data-for="article" style="<?= $wants_page ?
                    "" : "display:none" ?>"><label
                    for="front-paragraphs-<?= $named ?>"><?=
                    tl('wiki_element_front_page_paragraphs') ?></label><?php
                $this->view->helper("options")->render(
                    "front-paragraphs-" . $named,
                    "front_page_slot[" . $named . "][paragraphs]",
                    $counts, $held['paragraphs'] ?? 2, false,
                    ['class' => 'front-page-paragraphs',
                    'data-slot' => $named]);
                ?></div><?php
            }
            if ($only != "article") {
                $items = [];
                foreach (range(3, 10) as $step) {
                    $items[$step] = tl(
                        'wiki_element_front_page_item_count', $step);
                }
                $items["scroll"] =
                    tl('wiki_element_front_page_scroll_on');
                /* The box follows the words that name it, as a reader
                   meets the question before the answer. */
                ?><label for="front-heading-<?= $named ?>"
                    class="front-page-how-much" data-slot="<?= $named ?>"
                    data-for="category" style="<?= $wants_page ?
                    "display:none" : "" ?>"><?=
                    tl('wiki_element_front_page_name_as_heading')
                    ?><input type="checkbox"
                    id="front-heading-<?= $named ?>"
                    name="front_page_slot[<?= $named ?>][heading]"
                    value="true" class="front-page-heading"
                    data-slot="<?= $named ?>"<?=
                    empty($held['heading']) ? "" : " checked"
                    ?> ></label><?php
                ?><div class="front-page-how-much" data-slot="<?=
                    $named ?>" data-for="category" style="<?= $wants_page ?
                    "display:none" : "" ?>"><label
                    for="front-items-<?= $named ?>"><?=
                    tl('wiki_element_front_page_items') ?></label><?php
                $this->view->helper("options")->render(
                    "front-items-" . $named,
                    "front_page_slot[" . $named . "][items]",
                    $items, $held['items'] ?? 5, false,
                    ['class' => 'front-page-items',
                    'data-slot' => $named]);
                ?></div><?php
                $shapes = [
                    "standard" => tl('wiki_element_list_standard'),
                    "compact" => tl('wiki_element_list_compact'),
                    "minimal" => tl('wiki_element_list_minimal')];
                ?><div class="front-page-how-much" data-slot="<?=
                    $named ?>" data-for="category" style="<?= $wants_page ?
                    "display:none" : "" ?>"><label
                    for="front-shape-<?= $named ?>"><?=
                    tl('wiki_element_list_shape') ?></label><?php
                $this->view->helper("options")->render(
                    "front-shape-" . $named,
                    "front_page_slot[" . $named . "][shape]",
                    $shapes, $held['shape'] ?? "standard", false,
                    ['class' => 'front-page-shape',
                    'data-slot' => $named]);
                ?></div><?php
            }
            ?></div>
            </div>
            <?php
        }
        ?></div><?php
        /* Every arrangement offers the same pages to choose from, and a
           writer meets them as they type a name. The list was drawn for
           one arrangement only, so the places of the others suggested
           nothing. */
        if ($layout == $data['front_page_layout_first']) {
            ?>
            <datalist id="front-page-pages"><?php
            foreach ($data['front_page_articles'] ?? [] as $one) {
                if (trim($one) !== "") {
                    ?><option value="<?= $one ?>" ><?php
                }
            }
            ?></datalist><?php
        }
    }
    /**
     * frontPageSlotHolds says whether a place can hold only one sort of thing.
     * A lead holds an article and the headlines under it hold a category, so
     * neither asks which; anywhere else may hold either.
     * @param string $slot which place within its layout
     * @return string "article", "category", or an empty answer where the place
     *     may hold either
     */
    public function frontPageSlotHolds($slot)
    {
        if ($slot == "lead") {
            return "article";
        }
        if ($slot == "below") {
            return "category";
        }
        return "";
    }
    /**
     * frontPageSlotNames gives the places one layout holds, by the name each is
     * saved under and the words a writer sees beside it. The words come from
     * the controller so that the form and the placeholder a reader sees for an
     * empty place are named from one list rather than two.
     * @param array $data fields from the controller, holding the names
     * @param string $layout which layout was picked
     * @return array place name to the words describing it
     */
    public function frontPageSlotNames($data, $layout)
    {
        $names = $data['front_page_slot_names'] ?? [];
        return $names[$layout] ?? ($names["grid"] ?? []);
    }
    /**
     * frontPageLayoutDrawing draws a small picture of what one layout looks
     * like, so a writer chooses by the shape rather than by the name alone.
     * @param string $layout which layout to draw
     * @return string the drawing
     */
    public function frontPageLayoutDrawing($layout)
    {
        $shapes = ["lead-and-list" =>
            "<rect x='2' y='2' width='44' height='18' />" .
            "<rect x='2' y='24' width='44' height='4' />" .
            "<rect x='2' y='31' width='44' height='4' />",
            "cards" => "<rect x='2' y='6' width='13' height='24' />" .
            "<rect x='18' y='6' width='13' height='24' />" .
            "<rect x='34' y='6' width='13' height='24' />",
            "grid" => "<rect x='2' y='4' width='21' height='13' />" .
            "<rect x='26' y='4' width='21' height='13' />" .
            "<rect x='2' y='20' width='21' height='13' />" .
            "<rect x='26' y='20' width='21' height='13' />",
            "sections" => self::sectionsDrawing()];
        $drawing = $shapes[$layout] ?? "";
        return "<svg class='front-page-drawing' viewBox='0 0 49 37' " .
            "aria-hidden='true' >$drawing</svg>";
    }
    /**
     * sectionsDrawing draws the little picture standing for a column of cards:
     * the row of cards stood on its end, three cards one under another, each
     * the full width. A card holds one story or one category's list.
     * @return string the shapes making up the picture
     */
    public static function sectionsDrawing()
    {
        $drawing = "";
        $cards = 3;
        $card_height = 10;
        $gap = 2;
        for ($card = 0; $card < $cards; $card++) {
            $top = 3 + $card * ($card_height + $gap);
            $drawing .= "<rect x='2' y='" . $top .
                "' width='44' height='" . $card_height . "' />";
        }
        return $drawing;
    }
    /**
     * renderEditPageForm draws the form somebody edits a wiki page
     * with.
     * @param array $data fields contain data about the page being edited. In
     *     particular, PAGE contains the raw page data
     */
    public function renderEditPageForm($data)
    {
        $this->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')];
        /* Which locale of the page is being looked at travels with
           every way of looking at it, so going to its history or its
           source stays in the same language rather than falling back to
           the one the reader has the site in. */
        $locale_part = empty($data['CURRENT_LOCALE_TAG']) ? "" :
            "&page_locale=" . urlencode($data['CURRENT_LOCALE_TAG']);
        $simple_base_url .= $locale_part;
        $simple_view_url_locale = $locale_part;
        $base_url = htmlentities($simple_base_url);
        $this->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_view_url_locale;
        $this->simple_current_url = $this->simple_view_url . "&arg=edit";
        $this->is_settings_view = !empty($data['settings']) &&
            $data['settings'] != "false";
        $this->is_resources_view = !empty($data['resources']) &&
            $data['resources'] != "false";
        /* Clicking a resource opens that one file to look at or edit. It
           is not the list of resources, so the check above does not catch
           it, and the page's own text is not what is on screen either.
           Controls belonging to the page rather than to the file have no
           place on it. */
        $this->is_one_resource = !$this->is_resources_view &&
            !empty($data['RESOURCE_NAME']);
        $this->shows_spreadsheet = $this->is_one_resource &&
            strtolower(pathinfo($data['RESOURCE_NAME'] ?? "",
            PATHINFO_EXTENSION)) == "csv";
        $this->shows_image = $this->is_one_resource &&
            in_array(strtolower(pathinfo($data['RESOURCE_NAME'] ?? "",
            PATHINFO_EXTENSION)), C\EDITABLE_IMAGE_EXTENSIONS);
        $this->shows_media_page = ($data["HEAD"]['page_type'] ??
            ($data['page_type'] ?? "")) == 'media_list';
        $this->is_page_alias =
            !empty($data['page_type']) && $data['page_type'] == 'page_alias';
        $this->is_share =
            !empty($data['page_type']) && $data['page_type'] == 'share';
        $this->is_shortener =
            !empty($data['page_type']) && $data['page_type'] == 'url_shortener';
        $this->is_git_repository = !empty($data['page_type']) &&
            $data['page_type'] == 'git_repository';
        if (!empty($data['SUB_PATH'])) {
            $this->simple_view_url .= "&sf=".urlencode($data['SUB_PATH']);
            $this->simple_current_url .= "&sf=".urlencode($data['SUB_PATH']);
        }
        $current_url = htmlentities($this->simple_current_url);
        $new_settings = $this->is_settings_view ? "&settings=false" :
            "&settings=true";
        $new_resources = $this->is_resources_view ? "&resources=false" :
            "&resources=true";
        if ($this->is_resources_view) {
            $this->simple_current_url .= "&resources=true";
        }
        $this->settings_toggle_url = $current_url . $new_settings;
        $this->resources_toggle_url = $current_url . $new_resources;
        if (isset($data['OTHER_BACK_URL'])) {
            $append = $data['OTHER_BACK_URL'];
        }
        $this->icon_helper = $this->view->helper('iconlink');
        ?>
        <form id="editpageForm" method="post"
            enctype="multipart/form-data"
            onsubmit="noteWikiPlace();" >
            <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'] ?>" >
            <?php
            /* target=_blank so a reader can look at the file without
               losing what is typed in the editor. */
            $resource_read_url = ($this->is_one_resource) ?
                $this->simple_view_url . "&arg=media&n=" .
                urlencode($data['RESOURCE_NAME']) .
                "&page_id=" . $data['PAGE_ID'] : "";
            if ($this->is_one_resource) {
                ?><div class="top-margin"><b><?=
                tl('wiki_element_resource_name',
                "<a href='" . $resource_read_url . "' target='_blank'>" .
                $data['RESOURCE_NAME'] . "</a>") ?></b></div><?php
            }
            ?>
            <?php
            $lists_files = $this->is_resources_view;
            if (!$lists_files && $this->is_one_resource) {
                ?><div class="top-margin wiki-edit-controls"><?php
                $this->renderLocaleChooser($data);
                if ($this->shows_spreadsheet) {
                    $this->icon_helper->renderButton(
                        ['histogram-toggle',
                        "javascript:toggleSpreadsheetView()"],
                        'histograms', "");
                    ?><script>
                    window.spreadsheet_marks =
                        {bars: "\u{1F4CA}", rows: "\u{25A6}"};
                    window.spreadsheet_says =
                        {bars: "<?= tl('iconlink_helper_histograms') ?>",
                        rows: "<?= tl('iconlink_helper_spreadsheet') ?>"};
                    </script><?php
                }
                $this->icon_helper->renderButton(
                    $data['RESOURCE_DOWNLOAD_URL'] ?? "", 'download');
                ?><span id="resource-save-controls"
                    class="wiki-buttons"><?php
                $this->icon_helper->renderFormButton("submit", "save", false,
                    "icon-anchor-button", "id='wiki-save-button'");
                ?><input type="hidden" id="save-as-name"
                    name="save_as_name" value="<?=
                    $data['RESOURCE_NAME'] ?? '' ?>" ><?php
                $this->icon_helper->renderFormButton("button", "save_as",
                    false, "icon-anchor-button",
                    "id='wiki-save-as-button' " .
                    "onclick='saveWikiResourceAs()'");
                ?></span></div><?php
            } else if (!$lists_files && !$this->shows_media_page) {
                $this->renderWikiNavRow($data, $base_url);
            }
            ?>
<?php
        $this->renderEditSettings($data);
        $this->renderEditBody($data);
        $this->renderEditButtons($data);
?>
            </div>
        </form>
        <?php
        if ($this->is_resources_view ||
            ($this->shows_media_page && !$this->is_settings_view)) {
            ?><div id="page-resources"><?php
            if (!$this->shows_media_page) {
                ?><h3><?= tl('wiki_element_page_resources')?></h3>
                <div class="wiki-edit-controls"><b><label
                    for="page-locale"><?=
                    tl('wiki_element_locale') ?></label></b><?php
                $this->renderLocaleChooser($data);
                e($this->view->helper("helpbutton")->render(
                    "Page_Setting_and_Resources",
                    $data[C\p('CSRF_TOKEN')]));
                ?></div>
                <p><?= tl('wiki_element_resources_info') ?></p><?php
            }
            ?>
            <div id="resource-wiki-code" class="wiki-popup-prompt"
                style="display:none">
            <div class="wiki-popup-content">
            <h2 class="center"><?= tl('wiki_element_wiki_code') ?></h2>
            <div class="center">
            <label for="resource-wiki-code-form"><?=
                tl('wiki_element_wiki_code_form') ?></label>
            <select id="resource-wiki-code-form">
            <option value="resource"><?=
                tl('wiki_element_plain_resource') ?></option>
            <option value="resource-link"><?=
                tl('wiki_element_resource_link') ?></option>
            <option value="resource-thumb"><?=
                tl('wiki_element_thumbnail_link') ?></option>
            </select>
            </div>
            <div class="center"><input type="text"
                id="resource-wiki-code-text" readonly
                class="resource-wiki-code-field" ><?php
                /* The helper draws a link, and this one goes nowhere:
                   it puts the words on the clipboard instead, which is
                   wired below. */
                $this->icon_helper->renderButton(
                    ['resource-wiki-code-copy',
                    "javascript:copyResourceWikiCode();"], 'clip_copy', "");
                ?></div>
            <div class="center"><button type="button" class="button-box"
                onclick="hideResourceWikiCode();"><?=
                tl('wiki_element_closebutton') ?></button></div>
            </div>
            </div>
            <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 = "<?=$this->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 showResourceWikiCode(name, description)
            {
                let holder = document.getElementById('resource-wiki-code');
                if (!holder) {
                    return;
                }
                let chooser = document.getElementById(
                    'resource-wiki-code-form');
                let shown = document.getElementById(
                    'resource-wiki-code-text');
                let write = function () {
                    shown.value = "((" + chooser.value + ":" + name + "|" +
                        description + "))";
                    shown.select();
                };
                chooser.onchange = write;
                write();
                holder.style.display = "block";
            }
            function copyResourceWikiCode()
            {
                let shown = document.getElementById(
                    'resource-wiki-code-text');
                if (!shown) {
                    return;
                }
                shown.select();
                if (navigator.clipboard) {
                    navigator.clipboard.writeText(shown.value);
                }
            }
            function hideResourceWikiCode()
            {
                let holder = document.getElementById('resource-wiki-code');
                if (holder) {
                    holder.style.display = "none";
                }
            }
            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'
                /* A page with no resources yet, or one not saved, has no
                   list for these to act on, and reaching into one that is
                   not there stopped the rest of this page's start-up: the
                   area a writer drags a file into is set up further down,
                   so it was the missing list that took the dragging with
                   it. */
                let container = (container_id) ?
                    document.getElementById(container_id) : null;
                let popup = (container_id) ?
                    document.getElementById(popup_id) : null;
                if (container && popup) {
                    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";
                        popup.style.backgroundColor = 'var(--gray-100)';
                        popup.style.border = '1px solid var(--gray-50)';
                        popup.style.borderRadius = '5px';
                        popup.style.boxShadow = '0 2px 8px rgba(0,0,0,0.3)';
                        popup.style.zIndex = '100';
                        event.preventDefault();
                    });
                    document.addEventListener('click', (event) => {
                        let popup =  document.getElementById(popup_id);
                        popup.innerHTML = "";
                        popup.style.backgroundColor = '';
                        popup.style.border = '';
                        popup.style.boxShadow = '';
                    });
                }
            }
            initializeResourceContainer();
            </script>
            </div><?php
        }
    }
    /**
     * renderResources 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;
            }
            $url_prefix = $data['RESOURCES_INFO']['url_prefix'];
            /* Every link out of this page carries the language being
               looked at. A page has a copy in each language, and a link
               without one lands on whichever the site picks, so a paste
               put the files in the wrong copy of the page. */
            $page_locale = $data['CURRENT_LOCALE_TAG'] ?? "";
            $locale_var = ($page_locale === "") ? "" :
                "&page_locale=" . urlencode($page_locale);
            $raw_base_url .= $locale_var;
            $data['RAW_BASE_URL'] = $raw_base_url;
            if ($logged_in) {
                $base_url .= $token_string . str_replace("&", "&",
                    $locale_var);
                if (C\REDIRECTS_ON) {
                    $url_prefix =  preg_replace("/\/-\//u",
                        "/$token_string/", $url_prefix);
                } else {
                    $url_prefix .= "&". $token_string;
                }
            }
            $folder_prefix = ($is_static) ? $base_url : $base_url . "&";
            $folder_prefix .= "page_id=". $data["PAGE_ID"];
            $url_is_folder_prefix = false;
            $settings_and_resources = "";
            if (!empty($data['settings'])) {
                $settings_and_resources =  "&settings={$data['settings']}" .
                "&resources={$data['resources']}";
            }
            if (!empty($read_mode)) {
                $read_arg = ($read_mode == "source") ? "source" : "media";
                $folder_prefix .= "&arg=$read_arg";
                $url_prefix = $folder_prefix;
                $url_is_folder_prefix = true;
            } else if ($data['CURRENT_LAYOUT'] == 'detail') {
                $folder_prefix .= "&arg=media";
                $url_prefix = $folder_prefix;
                $folder_prefix = $base_url . "&arg=edit";
                $base_url .= $settings_and_resources;
                $url_is_folder_prefix = true;
            } else {
                $folder_prefix = $base_url . "&arg=edit";
                $base_url .= $settings_and_resources;
            }
            if ($read_mode != "media") {
                $data['resources'] ??= 'false';
                $folder_prefix .= "&resources={$data['resources']}";
            }
            ?>
            <form class="podcast-controls-row">
            <?php
            if ($this->shows_media_page && !$this->is_settings_view) {
                $this->renderWikiNavRow($data, $base_url);
            }
            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 . "&resource_filter=".
                urlencode($data['RESOURCE_FILTER']);
            $sub_path_var = "";
            if (!empty($data['SUB_PATH'])) {
                $sub_path_var = "&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 . "&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&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']);
            $activity = ($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='<?=$activity ?>' >
            <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&resource_actions=",
                    false, "inward-align",
                    "<span class='hover-lightgray' role='img' aria-label='" .
                    tl('wiki_element_actions') . "'>&#8943;</span>");
            }
            ?>
            </div></form>
            <div id="new-image-dialog" class="wiki-popup-prompt"
                style="display:none">
            <div class="wiki-popup-content">
            <h2 class="center"><?=
                tl('wiki_element_new_image_size') ?></h2>
            <div class="center">
            <label for="new-image-wide"><?=
                tl('wiki_element_image_width') ?></label>
            <input type="number" id="new-image-wide" min="1"
                max="<?= C\MAX_NEW_IMAGE_SIDE ?>" value="400"
                class="image-editor-size" >
            <span class="image-editor-by">&#215;</span>
            <label for="new-image-tall"><?=
                tl('wiki_element_image_height') ?></label>
            <input type="number" id="new-image-tall" min="1"
                max="<?= C\MAX_NEW_IMAGE_SIDE ?>" value="400"
                class="image-editor-size" >
            </div>
            <div class="center">
            <button type="button" class="button-box"
                id="new-image-make"><?=
                tl('wiki_element_new_image_make') ?></button>
            <button type="button" class="button-box"
                id="new-image-drop"><?=
                tl('wiki_element_new_image_cancel') ?></button>
            </div>
            </div>
            </div>
            <div id="clipboard" class="none">
            <h3><?=tl('wiki_element_clipboard') ?></h3>
            <div>
            [<a href="<?=$base_url . $sub_path_var .
                '&arg=edit&paste_all=true'
                ?>"><?=tl('wiki_element_paste_all')?></a>]
            [<a onclick='return emptyConfirm();'
                href="<?=$base_url . $sub_path_var .
                '&arg=edit&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 .
                        '&arg=edit&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
                $inside_bullets = false;
                $is_grid = false;
                $is_detail = false;
                if (empty($data['CURRENT_LAYOUT']) ||
                    $data['CURRENT_LAYOUT'] == 'list') {
                    $inside_bullets = 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
                }
                /* The head of the list is closed and its body opened
                   whether or not a heading row is written. Where no
                   heading was written the head was left open, so every
                   resource row landed in the head; each of those rows
                   is told to stay put against the top of the list as it
                   scrolls, so they piled one on top of another. */
                if (empty($read_mode) && $data['MODE'] != 'source' &&
                    $data['CURRENT_LAYOUT'] == 'list') {
                    ?><tr><th colspan="2"></th>
                        <th>
                        <div class='resizable' ><a href='<?=
                        $sub_path_folder_prefix?>&default_sort=name'><?=
                        tl('wiki_element_name')
                    ?></a></div></th><?php
                    e("<th class='hide-narrow'><div class='resizable'
                            ><a href='$sub_path_folder_prefix&".
                            "default_sort=size'>" . tl('wiki_element_size').
                            '</a></div>');
                        e("</th><th class='resource-modified-column ".
                            "hide-narrow'><a href='".
                            "$sub_path_folder_prefix&".
                            "default_sort=modified'>" .
                            tl('wiki_element_modified').'</a></th>');
                    ?>
                </tr><?php
                }
                if ($inside_bullets) {
                    ?></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 ($inside_bullets) {
                    ?></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";
                    /* what comes back after the move is worked out from
                       the fields this url carries, so without naming the
                       resource listing here the reader is returned to the
                       page itself rather than to the folder they were
                       just arranging. */
                    $move_base_url .= "&resources=true";
                    if (!empty($data['SUB_PATH'])) {
                        $move_base_url .= "&sf=" .
                            urlencode($data['SUB_PATH']);
                    }
                    /* the selection shortcuts act through the same
                       requests the row buttons use, so they are given the
                       same urls with the resource name left off the end */
                    $select_base = $data['RAW_BASE_URL'] .
                        "&arg=edit&resources=true";
                    if (!empty($data['SUB_PATH'])) {
                        $select_base .= "&sf=" .
                            urlencode($data['SUB_PATH']);
                    }
                    $select_urls = json_encode([
                        "delete_url" => $select_base . "&delete_resources=",
                        "clip_copy_url" => $select_base .
                            "&clip_copy_resources=",
                        "clip_cut_url" => $select_base .
                            "&clip_cut_resources=",
                        "paste_all_url" => $select_base . "&paste_all=true"]);
                    $delete_confirm = tl('wiki_element_delete_operation');
                    ?><script>
                    window.addEventListener("load", function() {
                        initResourceDragDrop("<?=$move_base_url ?>",
                            "<?=tl('wiki_element_items_dragged') ?>");
                        initResourceSelection(<?=$select_urls ?>,
                            "<?=$delete_confirm ?>");
                    });
                    </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
        }
    }
    /**
     * renderResource 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'] .
                "&$token_string";
            $athumb_prefix = $data['RESOURCES_INFO']['athumb_prefix'] .
                "&$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'],
                C\EDITABLE_RESOURCE_EXTENSIONS) ||
                in_array(strtolower($name_parts['extension']),
                C\EDITABLE_IMAGE_EXTENSIONS)) {
                $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;
        }
        $inside_bullets = false;
        $is_grid = false;
        $is_detail = false;
        if (empty($data['CURRENT_LAYOUT']) ||
            $data['CURRENT_LAYOUT'] == 'list') {
            $inside_bullets = 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)
            ? "/" : "&n=";
        $name_connect = (C\REDIRECTS_ON &&
            !$url_is_folder_prefix)
            ? "/" : "&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&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 = "&sf=$encode_sub_path";
                $current_url .= $add_sub_path;
                $clear_url .= $add_sub_path;
            }
            $alt_current_url = "$folder_prefix&sf=".
                urlencode($data['SUB_PATH']) . "&n=".
                $encode_name;
            $alt_current_url = preg_replace("/resources=true/u",
                "resources=false", $alt_current_url);
            if (!$resource['has_thumb']) {
                $current_thumb = $default_thumb;
                $is_a_default_thumb = true;
            }
            if (empty($read_mode) && !$disabled && $use_editable_thumb) {
                $current_url = $alt_current_url;
                $marks_as_editable = true;
            }
            if (!empty($resource['is_dir'])) {
                $current_url = "$folder_prefix&sf=".
                    urlencode($data['SUB_PATH'])."/$name";
                $current_thumb = $default_folder_thumb;
                $is_a_default_thumb = true;
            }
        } else {
            $alt_current_url = "$folder_prefix&n=".
                urlencode($name);
            $alt_current_url = preg_replace("/resources=true/u",
                "resources=false", $alt_current_url);
            if (!$resource['has_thumb']) {
                $current_thumb = $default_thumb;
                $is_a_default_thumb = true;
            }
            if (empty($read_mode) && !$disabled && $use_editable_thumb) {
                $current_url = $alt_current_url;
                $marks_as_editable = true;
            }
            if (!empty($resource['is_dir'])) {
                $current_url = "$folder_prefix&sf=".
                    urlencode($name);
                $current_thumb = $default_folder_thumb;
                $is_a_default_thumb = true;
            }
        }
        /* On a page whose resources are served as a static HTML folder,
           every entry in the listing links to its own static path under the
           page (page URL, then the sub-folder walked into so far, then the
           entry name), so a click fetches the file or sub-folder directly
           the way a plain web-server directory index does, rather than
           opening it through the wiki's resource viewer. Files and
           sub-folders are treated alike, so following the links walks the
           tree as static paths the whole way down. */
        if (!empty($data['static_html_folder'])) {
            $static_path = $base_url;
            if (!empty($data['SUB_PATH'])) {
                $static_path .= "/" . $data['SUB_PATH'];
            }
            $current_url = $static_path . "/" . $encode_name;
        }
        $is_seen = !empty($_SESSION['seen_media']) &&
            in_array($hash_id, $_SESSION['seen_media']);
        $media_detail = ($data['MODE'] == 'edit') ?
            "arg=media-detail-edit&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 ($inside_bullets) {
            $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 && $inside_bullets) {
            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" ' : "";
        $editable_pen = "";
        if (!empty($marks_as_editable)) {
            $editable_pen = "<svg class='resource-edit-pen' " .
                "viewBox='0 0 16 16' aria-hidden='true'>" .
                "<path d='M11.2,1.6 L14.4,4.8 L6.4,12.8 L2.4,13.6 " .
                "L3.2,9.6 Z' fill='#f5c542' stroke='#7a5c00' " .
                "stroke-width='1' stroke-linejoin='round' />" .
                "<path d='M10,2.8 L13.2,6' fill='none' stroke='#7a5c00' " .
                "stroke-width='1' /></svg>";
        }
        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("$editable_pen</a>$first_resource</$td>");
        if (!empty($read_mode)) {
            e("<$td><a href='$current_url'>".
                "$written_name</a>$info_link</$td>");
        } else {
            /* tabindex is needed for focus-within to work in 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
            }
            /* What to write on a page to show this file is offered from
               the menu with the rest of what can be done to it, rather
               than from a mark of its own that did nothing else. */
            $shows_wiki_code = (!$disabled && empty($resource['is_dir'])
                && (!isset($data['page_type']) ||
                $data['page_type'] != 'media_list'));
            $resource_sub_path = empty($data['SUB_PATH']) ?
                "" : "|" . $data['SUB_PATH'];
            $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 .
                "&arg=edit&clip_copy=". urlencode($name) . $append;
            if (!empty($data['SUB_PATH'])) {
                $copy_url .= "&sf=". urlencode($data['SUB_PATH']);
            }
            $cut_url = $base_url .
                "&arg=edit&clip_cut=". urlencode($name) . $append;
            if (!empty($data['SUB_PATH'])) {
                $cut_url .= "&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"
                    ><?php
                    if ($shows_wiki_code) {
                        ?><a href='#' onclick='showResourceWikiCode(
                            "<?= urlencode($name) . $resource_sub_path
                            ?>", "<?= tl('wiki_element_resource_description',
                            $name) ?>"); return false;'><?=
                            tl('wiki_element_wiki_code') ?></a><?php
                    }
                    ?><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>");
            e("<$td class='hide-narrow'>" .
                L\intToMetric($resource['size']) .
                "B</$td>");
            e("<$td class='resource-modified-column hide-narrow'>" .
                date(C\RESOURCE_MODIFIED_DATE_FORMAT,
                $resource['modified']) .
                "</$td>");
        }
        e("</$tr>");
    }
    /**
     * renderParentFolderRow 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 . "&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';
        $inside_bullets = (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 ($inside_bullets) {
                e("<td class='hide-narrow'></td>" .
                    "<td class='hide-narrow'></td>");
            }
            e("</tr>");
        }
    }
    /**
     * renderPath 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_bullets whether to output the result as a dropdown
     * or as
     *     an unordered list.
     * @param string $class_names 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_bullets = false, $class_names = "")
    {
        $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,
                           "&sf=$sub_path" => $part], $options);
                    }
                    break;
                } else {
                    $options = array_merge(
                        ["&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_bullets,
            $class_names);
        return $sub_path;
    }
    /**
     * renderPages 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 .
            "&arg=edit";
        /* Where the list has been narrowed to one category, every way on
           from it keeps that name, so paging and reordering stay inside
           the category rather than falling back to the whole list. */
        $narrowed = empty($data["LIST_CATEGORY"]) ? "pages" :
            "pages/" . $data["LIST_CATEGORY"];
        $paging_query = htmlentities(B\wikiUrl($narrowed, true, $controller,
            $group_id)) . $token_string;
        if (!empty($data['FILTER'])) {
            $paging_query .= "&filter=" . $data['FILTER'];
        }
        /* Where the list is narrowed to one category the bar above says
           so, and nothing needs saying twice. */
        $narrowed_to = $data["LIST_CATEGORY"] ?? "";
        if ($narrowed_to === "") {
            ?><h2 class="page-list-header-footer"><?=
                tl("wiki_element_wiki_create_find",
                $data["GROUP"]["GROUP_NAME"])
            ?></h2><?php
        }
        /* A group's pages are kept in each language on their own, so the
           list is of one language's pages. Choosing another shows that
           language's list rather than this one's. */
        $locale_options = [];
        foreach ($data['LOCALE_LIST'] ?? [] as $known) {
            $locale_options[$known['LOCALE_TAG']] = $known['LOCALE_NAME'];
        }
        if (empty($locale_options)) {
            $locale_options[$data['CURRENT_LOCALE_TAG']] =
                $data['CURRENT_LOCALE_TAG'];
        }
        if ($narrowed_to !== "") {
            $locale_options = [];
        }
        if (!empty($locale_options)) {
        ?><div class="page-list-header-footer top-margin">
        <label for="page-list-locale"><b><?=
            tl('wiki_element_locale_name', "") ?></b></label><?php
        $this->view->helper("options")->render("page-list-locale",
            "page_locale", $locale_options, $data['CURRENT_LOCALE_TAG'],
            "window.location = \"" . $paging_query .
            "&arg=pages&page_locale=\" + this.value;");
        ?></div><?php
        }
        $this->renderPageListSearch($data, $paging_query, "pages",
            $data["LIST_CATEGORY"] ?? "");
        /* Making a page is offered where a reader is looking through the
           whole of a group's pages and has named one that is not there.
           A category's page holds what is filed under it, and a page made
           from here would not be filed under it, so nothing is offered
           there. */
        if ($data["FILTER"] != "" && $narrowed_to === "") {
            ?><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 ($narrowed_to !== "") {
            /* A category's page shows what is filed under it the way a
               category's list shows it anywhere else: a picture beside
               each title with its abstract beneath. */
            $this->renderCategoryItems($data, $controller, $group_id,
                $token_string);
        } else 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
                    ?>&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 .
                            "&noredirect=true&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 . "&noredirect=true&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 . "&noredirect=true&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'])) {
            /* Named as the rest of the site names a locale: the
               language rather than the tag that stands for it. */
            $shown_locale = $data['CURRENT_LOCALE_TAG'] ??
                L\getLocaleTag();
            foreach ($data['LOCALE_LIST'] ?? [] as $known) {
                if ($known['LOCALE_TAG'] == $shown_locale) {
                    $shown_locale = $known['LOCALE_NAME'];
                    break;
                }
            }
            ?><div class='page-list-header-footer'><?=
            tl('wiki_element_no_pages', "<b>" . $shown_locale .
            "</b>")?></div><?php
        }
    }
    /**
     * renderRelationships 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."&arg=relationships" .
                    "&page_id=" .
                    $data["PAGES_THAT_LINK_TO"][$key]["PAGE_ID"] .
                    "&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 . "&arg=relationships" .
                        "&page_id=" . $page_id;
                    $relationship_type =
                        $data["RELATIONSHIPS"][$key]["RELATIONSHIP_TYPE"];
                    $url .= "&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.
                    "&arg=relationships"."&page_id=".
                    $data["PAGES_THAT_LINK_FROM"][$key]["PAGE_ID"].
                    "&reltype=".$data["REL-TYPE"];
                ?><div class="center"><a href='<?=$url?>'><?=$var
                ?></a></div><?php
            }
        }
        ?></td></tr></table><?php
    }
    /**
     * renderHistory 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')]);
        /* A page has a history in each locale it is written in, so every
           link from here says which one is being looked at and the page
           says so at its head. */
        $history_locale = $data['CURRENT_LOCALE_TAG'] ?? "";
        /* Kept before the language is added, since the chooser below
           puts a language of its own on the end. */
        $query_without_locale = $base_query;
        if ($history_locale != "") {
            $base_query .= "&page_locale=" .
                urlencode($history_locale);
        }
        $append = "";
        if (isset($data['OTHER_BACK_URL']) && $data['OTHER_BACK_URL'] != '') {
            $append = $data['OTHER_BACK_URL'];
        }
        if ($history_locale != "") {
            $locale_options = [];
            foreach ($data['LOCALE_LIST'] ?? [] as $known) {
                $locale_options[$known['LOCALE_TAG']] =
                    $known['LOCALE_NAME'];
            }
            if (empty($locale_options)) {
                $locale_options[$history_locale] = $history_locale;
            }
            $history_locale_url = $query_without_locale .
                "&arg=history" . "&page_id=" .
                ($data['PAGE_ID'] ?? "") . "&page_locale=";
            ?>
            <div class="top-margin wiki-edit-controls">
            <b><label for="history-locale"><?=
                tl('wiki_element_locale') ?></label></b><?php
            $this->view->helper("options")->render("history-locale",
                "page_locale", $locale_options, $history_locale,
                "window.location = \"$history_locale_url\" + this.value;");
            ?>
            </div>
            <?php
        }
        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" >
            <input type="hidden" name="page_locale" value="<?=
                $history_locale ?>" >
            <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>
            <?php
            ?>
            </form>
            </div>
            <?php
        }
        /* The way out of the history is the return mark in the nav bar
           above, which on this screen goes back to editing. */
        ?>
        <div>&nbsp;</div>
        <?php
        $time = time();
        $feed_helper = $this->view->helper("feeds");
        $base_query .= "&arg=history&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&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
    }
    /**
     * renderSourcePage draws the form somebody reads the source of a
     * wiki page with.
     * @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');
        /* As on the editor: which locale is being looked at travels
           with every way of looking at the page. */
        $locale_part = empty($data['CURRENT_LOCALE_TAG']) ? "" :
            "&page_locale=" . urlencode($data['CURRENT_LOCALE_TAG']);
        $simple_base_url = B\wikiUrl("", true,
            $data['CONTROLLER'], $data['GROUP']['GROUP_ID']) .
            C\p('CSRF_TOKEN') . '=' . $data[C\p('CSRF_TOKEN')] .
            $locale_part;
        $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')] .
           $locale_part;
        $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";
        $shows_media_page =
            !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 ? "&settings=false" :
            "&settings=true";
        $new_resources = $is_resources_view ? "&resources=false" :
            "&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 .
                '&arg=history&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 (!$shows_media_page && !$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']);
            e($this->view->helper("helpbutton")->render(
                "Wiki_Page_Types", $data[C\p('CSRF_TOKEN')]));
            ?>
            </div>
            <?php $this->renderPagePropertiesRule($data); ?>
            <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 (!$shows_media_page && !$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->view->element("git")->renderGitStatistics(
                $data); ?>
            </div><?php
        } else if (!$shows_media_page && !$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 (($shows_media_page && !$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
        }
    }
    /**
     * renderBallotCount 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 nothing is handed back
     */
    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') ?>
            <?php
            /* The offer to write to every witness belongs here as much
               as on the screen that starts a ballot: closing a poll
               waits on the same people. */
            if (!empty($data['CAN_EMAIL_WITNESSES'])) {
                ?><?= tl('wiki_element_witness_email_all_says') ?>
                <a href="<?= $this->witnessEmailUrl($data, "*")
                    ?>"><?= tl('wiki_element_witness_email_all')
                    ?></a>.<?php
            }
            ?></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
        }
    }
    /**
     * renderBallotInit 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') ?>
            <?php
            /* The offer to write to every witness belongs with the
               sentence explaining the two ways, above the form. Inside
               it, a reader took it for part of what the form does. */
            if (!empty($data['CAN_EMAIL_WITNESSES'])) {
                ?><?= tl('wiki_element_witness_email_all_says') ?>
                <a href="<?= $this->witnessEmailUrl($data, "*")
                    ?>"><?= tl('wiki_element_witness_email_all')
                    ?></a>.<?php
            }
            ?></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 nothing is handed back
     */
    /**
     * witnessEmailUrl builds the address that writes to a witness, or to every
     * witness when asked for a star. It is a link rather than a button on the
     * form: a button beside the password boxes reads as the way to send them,
     * and somebody typing a password would press it by mistake.
     * @param array $data fields from the controller, holding the group, the
     *     page, and the token that says the request came from this screen
     * @param string $witness whose invitation to send, or a star for every
     *     witness
     * @return string the whole address, ready for an href
     */
    public function witnessEmailUrl($data, $witness)
    {
        $more = [];
        /* The screen that closes a poll says so in the mode, and the
           link has to carry it: without it the site takes the request
           for the screen that starts a ballot and writes to the witness
           about the wrong stage. */
        if (($data['MODE'] ?? "") == "count-ballots") {
            $more["mode"] = "count-ballots";
        }
        $more[C\p('CSRF_TOKEN')] = $data[C\p('CSRF_TOKEN')] ?? "";
        $more["WITNESS_EMAIL"] = $witness;
        return $this->votingPageUrl($data, $more);
    }
    /**
     * renderPagePropertiesRule draws the line that opens a page's properties,
     * with a question mark set opposite above it. Which properties a page shows
     * depends on the type chosen just above, so the help behind that mark is
     * where a writer is told what each of them does rather than each one
     * carrying a mark of its own.
     * @param array $data fields from the controller, holding the token that
     *     says the request came from this screen
     * @return void draws the mark and the line
     */
    public function renderPagePropertiesRule($data)
    {
        ?>
<div class="float-opposite"><?php
        e($this->view->helper("helpbutton")->render(
            "Wiki_Page_Properties", $data[C\p('CSRF_TOKEN')]));
        ?></div><hr><?php
    }
    /**
     * votingPageUrl builds the address of the page a ballot lives on, which is
     * where the voting happens and where the totals appear at the end. The
     * screens a witness or a voter is sent to stand apart from that page, so
     * each of them points back at it.
     * @param array $data fields from the controller, holding the group and the
     *     page
     * @param array $more anything further to carry in the address
     * @return string the whole address, ready for an href
     */
    public function votingPageUrl($data, $more = [])
    {
        $query = ["c" => $data['CONTROLLER'] ?? "group", "a" => "wiki",
            "arg" => "read", "group_id" => $data["GROUP"]["GROUP_ID"] ?? 0,
            "page_name" => $data['PAGE_NAME'] ?? ""];
        return htmlentities(C\SHORT_BASE_URL . "?" .
            http_build_query(array_merge($query, $more)));
    }
    /**
     * renderBallotPart draws what one witness sees after following the link
     * sent to them: a box for their ballot password, or a line saying why the
     * link no longer works. Once their part is recorded there is nothing left
     * for them to type, so the box is not drawn again.
     * @param array $data fields from the controller
     */
    public function renderBallotPart($data)
    {
        ?>
<h2><?= tl('wiki_element_ballot_part_heading') ?></h2><?php
        if (!empty($data['BALLOT_SAYS'])) {
            ?><p class="ballot-part-says"><?= $data['BALLOT_SAYS']
            ?></p><?php
        }
        /* This screen stands apart from the page the ballot lives on,
           and a witness who has just taken their part has nowhere to go
           from here, so it points back at where the voting happens. */
        ?><p><?= (tl('wiki_element_witness_vote_where', "<a href=\"" .
            $this->votingPageUrl($data) . "\">" .
                tl('wiki_element_voting_page') . "</a>")) ?></p><?php
        if (empty($data['BALLOT_GOOD']) || !empty($data['BALLOT_DONE'])) {
            return;
        }
        ?>
        <form method="post">
        <input type="hidden" name="<?=C\p('CSRF_TOKEN')?>" value="<?=
            $data[C\p('CSRF_TOKEN')] ?>" >
        <?php
        foreach (['c', 'a', 'arg', 'group_id', 'page_id', 'ballot_stage',
            'ballot_witness', 'ballot_expires', 'ballot_token'] as $field) {
            ?><input type="hidden" name="<?= $field ?>" value="<?=
                $_REQUEST[$field] ?? "" ?>" ><?php
        }
        ?>
        <div class='csv-form-field'>
        <label for="ballot-password"><?=
            tl('wiki_element_ballot_part_says') ?></label>
        <input id="ballot-password" type='password'
            name='BALLOT_PASSWORD' >
        </div>
        <div class='csv-form-field'><input type='submit' value='<?=
            tl('wiki_element_witness_submit') ?>' ></div>
        </form><?php
    }
    /**
     * renderWitnessForm draws the form on which every witness types their
     * ballot password to start a vote. Where a witness has already taken their
     * part, at this form or by following a link sent to them, they are shown as
     * received rather than asked again. Where the site can send mail and there
     * is more than one witness, each row also offers to write to that witness.
     * @param array $data fields from the controller, holding the witnesses,
     *     which of them have acted, and whether mail may be offered
     */
    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
            }
            if (!empty($data['WITNESS_DONE'][$witness])) {
                /* This witness has already taken their part, whether at
                   this form or by following a link sent to them, so there
                   is nothing left for them to type. */
                ?><div class='csv-form-field'><b><?= $witness
                ?></b>: <b class="green">[<?=
                    tl('wiki_element_witness_received')
                ?>]</b></div><?php
                $i++;
                continue;
            }
            ?>
            <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 ?>]' ><?php
            if (!empty($data['CAN_EMAIL_WITNESSES'])) {
                ?> <a href="<?= $this->witnessEmailUrl($data, $witness)
                    ?>"><?= tl('wiki_element_witness_email_one')
                    ?></a><?php
            }
            ?></div><?php
                $i++;
        }
        if (($data['KEY_FILE_WATCH'] ?? "watched") != "watched") {
            /* While the witnesses take their turns their parts wait
               sealed, and the site watches the file holding the key that
               opens them. Where this file system does not say when a file
               has been read, that watch cannot see anything, and whoever
               is starting the ballot should know before they begin. */
            ?><div class='csv-form-field'><b class="red"><?=
                tl('wiki_element_witness_watch_silent') ?></b></div><?php
        }
        ?>
        <div class='csv-form-field'><input
            type='submit' name='WITNESS_SUBMIT' value='<?=
                tl('wiki_element_witness_submit') ?>' ></div>
        </form><?php
    }
    /**
     * renderBallotConcluded 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 nothing is handed back
     */
    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
    }
    /**
     * dynamicSubstitutions 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'])) {
            /* A page drawn without a reader's mark in hand has none to
               put in its addresses, so an empty one stands rather than a
               warning about a field that was never set. */
            $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 =
                "&". $no_amp_csrf_token;
            $resource_no_right_amp_csrf_token = "&".
                $resource_no_amp_csrf_token;
            $csrf_token = $no_right_amp_csrf_token . "&";
            $resource_csrf_token = $resource_no_right_amp_csrf_token . "&";
        }
        if ($data['CONTROLLER'] == 'static') {
            $address = C\SHORT_BASE_URL . "p/";
            $pre_address = B\controllerUrl("group", true) .
                "{$csrf_token}a=wiki&arg=read&";
        } else {
            $pre_address = B\controllerUrl($data['CONTROLLER'], true) .
                "{$csrf_token}a=wiki&arg=read&";
            $address = $pre_address .
                "group_id=$group_id&page_name=";
        }
        $pre_page = preg_replace('/@@(.*)@(.*)@@/',
            $pre_address . "group_name=$1&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);
        /* A link may name the language of the page it wants, carried
           this far as a stand-in because only here is it known whether
           the address is a query or a path. A group address is a
           query, so the language is another field in it. */
        $pre_page = preg_replace('/\[{locale}\]([a-zA-Z\-_]{2,10})/',
            '&page_locale=$1', $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><p>" .
                    (tl('wiki_element_vote_totals_later', "<a href=\"" .
                        $this->votingPageUrl($data) . "\">" .
                            tl('wiki_element_voting_page')
                                . "</a>")) . "</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);
                        $as_sent = str_replace([" ", "."], "_",
                            $csv_field_name);
                        $replace = (empty($_REQUEST[$csv_field_name]) &&
                            empty($_REQUEST[$as_sent])) ? "" :
                            ' checked="checked" ';
                    } else {
                        $csv_field_name = $csv_field_match[1];
                        $as_sent = str_replace([" ", "."], "_",
                            $csv_field_name);
                        $replace = $_REQUEST[$csv_field_name] ??
                            ($_REQUEST[$as_sent] ?? "");
                    }
                    $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;
    }
    /**
     * substituteWikiSearchForm 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);
    }
    /**
     * renderPodcastStatus 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 = "&sf=" . urlencode($data['SUB_PATH']);
        }
        $status_url = htmlentities(B\wikiUrl($data['PAGE_NAME'], true,
            $controller, $group_id)) . $token .
            "&arg=podcast_status" . $sub_path_query;
        $update_url = htmlentities(B\wikiUrl($data['PAGE_NAME'], true,
            $controller, $group_id)) . $token .
            "&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
    }
    /**
     * renderLocaleChooser draws the control that says which translation
     * of a page is being worked on. Choosing another language loads that
     * language's copy of the page and saves to it, leaving the language
     * the writer reads the site in as it was. The control navigates
     * rather than submits, since submitting would save what is on screen
     * and come back in the language the writer started in, and it
     * returns to whichever of the editing screens the writer was on.
     *
     * @param array $data fields the controller prepared for the page,
     *     read for the languages the page has, which one is showing, the
     *     page's name and group, and the token
     */
    public function renderLocaleChooser($data)
    {
        $locale_options = [];
        foreach ($data['LOCALE_LIST'] ?? [] as $known) {
            $locale_options[$known['LOCALE_TAG']] = $known['LOCALE_NAME'];
        }
        if (empty($locale_options)) {
            $locale_options[$data['CURRENT_LOCALE_TAG']] =
                $data['CURRENT_LOCALE_TAG'];
        }
        $staying_on = "&arg=edit";
        if ($this->is_resources_view) {
            $staying_on .= "&resources=true";
        }
        if ($this->is_settings_view) {
            $staying_on .= "&settings=true";
        }
        if ($this->is_one_resource) {
            $staying_on .= "&n=" .
                urlencode($data['RESOURCE_NAME'] ?? "") .
                "&page_id=" . ($data['PAGE_ID'] ?? "");
        }
        $locale_url = htmlentities(B\wikiUrl(
            $data['PAGE_NAME'] ?? "", true, $data['CONTROLLER'],
            $data["GROUP"]["GROUP_ID"]) . C\p('CSRF_TOKEN') .
            "=" . $data[C\p('CSRF_TOKEN')]) .
            $staying_on . "&page_locale=";
        $this->view->helper("options")->render("page-locale",
            "page_locale", $locale_options,
            $data['CURRENT_LOCALE_TAG'],
            "window.location = \"$locale_url\" + this.value;",
            ["aria-label" => tl('wiki_element_locale'),
            "title" => tl('wiki_element_locale')]);
    }
    /**
     * renderEditSettings draws the fields a writer sets on a page: what
     * kind of page it is, how it looks, and who may see its source.
     *
     * renderEditPageForm calls it, so the editor is read one
     * screen at a time rather than in one long method.
     *
     * @param array $data what the page holds and the token
     *     every form on the page carries
     */
    public function renderEditSettings($data)
    {
        ?>            <?php if (
                !($this->is_git_repository && !$this->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 = (
                $this->is_settings_view) ? [] : ['disabled' => 'disabled'];
            $this->view->helper("options")->render("page-type", "page_type",
                $data['page_types'], $data['current_page_type'], true,
                $attributes);
            /* What each type is for, since the choice decides both how
               the page is drawn and which settings appear beneath. */
            e($this->view->helper("helpbutton")->render(
                "Wiki_Page_Types", $data[C\p('CSRF_TOKEN')]));
            ?>
            </div>
            <?php $this->renderPagePropertiesRule($data); ?>
            <script>
            /* The controls each kind of page uses are settled from here
               rather than from the front page's own block, which is not
               drawn on every screen that holds this chooser; started
               there, an article's own controls were never moved. */
            window.addEventListener("load", function() {
                if (typeof watchPageTypeChoice === "function") {
                    watchPageTypeChoice();
                }
            });
            </script>
            <?php
            /* Drawn whatever kind the page is, so that choosing the
               repository kind on a page that is not one yet brings it
               into view, and choosing another kind puts it away. The
               showing and hiding is by the same map every other
               kind-specific setting uses. */
            ?>
            <div class="top-margin" id="anonymous-issue-setting"><?php
            ?>
            <label for="anonymous-issue-reporting"><b><?=
                tl('social_component_anon_issue_reporting')
            ?></b></label><?php
            $this->view->helper("options")->render(
                "anonymous-issue-reporting", "anonymous_issue_reporting",
                $data['anonymous_issue_choices'],
                $data['current_anonymous_issue'], true, $attributes);
            ?>
            </div>
            <?php ?>
            <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" id="page-border-setting">
            <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>
            <?php
            /* A page names its own theme, header and footer only where
               its group says it may. Where the group says not, these are
               not drawn at all rather than drawn and ignored, and the
               page takes the group's. */
            $page_customizable =
                !empty($data["GROUP"]['PAGE_CUSTOMIZE_ALLOWED']);
            if ($page_customizable) { ?>
            <div class="top-margin" id="page-theme-setting">
            <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>
            <?php } ?>
            <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 } ?>
            <?php
            /* A group may forbid showing a page's source at all. Where it
               does, the line is left out rather than shown switched off
               with a note beside it: a control nobody can use says
               nothing a writer needs. */
            $group_source_off =
                isset($data['GROUP']['PAGE_SOURCE_ALLOWED']) &&
                !$data['GROUP']['PAGE_SOURCE_ALLOWED'];
            if (!$group_source_off) { ?>
            <div class="top-margin" id="public-source-setting">
            <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 );
                ?> id='public-source' >
            </div>
            <?php } ?>
            <div class="top-margin" id="page-title-setting">
            <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" id="meta-author-setting">
            <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" id="advanced-settings-line">
            <a href="#" id="advanced-settings-link"
                onclick="showAdvancedSettings(); return false;"><?=
                tl('wiki_element_advanced_settings') ?></a>
            </div>
            <div class="top-margin" id="page-icon-setting">
            <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" id="meta-robots-setting">
            <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" id="meta-description-setting">
            <label for="meta-description"><b><?=
                tl('wiki_element_meta_description')
            ?></b></label>
            <textarea id="meta-description" class="short-text-area"
                name="description" data-buttons='none'><?=$data['description']
            ?></textarea>
            </div>
            <div class="top-margin" id="article-date-setting"><?php
            /* An article says when it was written or when it was last
               changed. Which of the two it is saying is chosen here, and
               the moment itself is set by pressing the button, so a
               writer never types a date. */
            $when_kinds = ["date" => tl('wiki_element_article_date'),
                "updated" => tl('wiki_element_article_updated')];
            $this->view->helper("options")->render("article-date-kind",
                "article_date_kind", $when_kinds,
                $data['article_date_kind'] ?? "date", false,
                ['class' => 'article-date-kind']);
            $when = trim($data['article_date'] ?? "");
            ?><button type="button" id="article-date-button"
                class="button-box" data-unset="<?=
                tl('wiki_element_article_date_set') ?>"><?=
                ($when === "") ? tl('wiki_element_article_date_set') :
                $when ?></button>
            <input type="hidden" id="article-date" name="article_date"
                value="<?= $when ?>" >
            </div>
            <div class="top-margin" id="meta-properties-setting">
            <label for="meta-properties"><b><?=
                tl('wiki_element_meta_properties')
            ?></b></label>
            <div><?=tl('wiki_element_meta_property_format')?></div>
            <textarea id="meta-properties" class="short-text-area"
                name="properties" data-buttons='none'><?=$data['properties']
            ?></textarea>
            </div>
            <?php
            if (!empty($_SESSION['USER_ID']) &&
                $_SESSION['USER_ID'] == C\ROOT_ID) { ?>
                <div class="top-margin" id="alt-path-setting">
                <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" id="update-description-setting">
            <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>
            <?php if ($page_customizable) { ?>
            <div class="top-margin" id="page-header-setting">
            <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" id="page-footer-setting">
            <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>
            <?php } ?>
            <?php
            /* The arrangement of a front page comes after the settings
               every page shares, since a writer names and describes a
               page before saying how it is laid out. It sits outside the
               block holding the header and footer, which a group may not
               offer at all: put inside it, the arrangement disappeared
               wherever those were not offered. */
            if ($this->is_settings_view) {
                $this->renderFrontPageSettings($data);
            }
            ?>
            </div>
            </div>
            <?php } ?>
<?php
    }
    /**
     * renderImageEditor draws the canvas a picture is edited on and the
     * row of controls above it.
     *
     * renderEditBody calls it in place of the writing box when the one
     * file being edited is a picture. The picture's read address rides
     * on the canvas so image_editor.js can load and draw it, and a
     * hidden field carries the bytes the canvas makes when the writer
     * saves, so the save takes the same form path any resource save
     * takes. The controls are width and height fields, a turn and a
     * mirror mark, and drop-downs for the drawing tool, the shape, the
     * line thickness, and the color.
     *
     * @param array $data what the page holds, read for the file's name
     *     and the token every form on the page carries
     */
    public function renderImageEditor($data)
    {
        $read_url = html_entity_decode(
            $data['RESOURCE_DOWNLOAD_URL'] ?? "");
        $tools = ["doodle" => "\u{223F}", "line" => "\u{2571}",
            "oval" => "\u{25CB}", "oval_filled" => "\u{25CF}",
            "rectangle" => "\u{25AD}", "rectangle_filled" => "\u{25AC}",
            "text" => "\u{1F130}"];
        $widths = ["1" => "\u{2500}\u{2500}\u{2500}\u{2500}",
            "2" => "\u{2501}\u{2501}\u{2501}\u{2501}",
            "4" => "\u{2580}\u{2580}\u{2580}\u{2580}",
            "8" => "\u{2586}\u{2586}\u{2586}\u{2586}",
            "16" => "\u{2588}\u{2588}\u{2588}\u{2588}"];
        ?>
        <div id="image-editor" class="top-margin"
            data-image-url="<?= $read_url ?>">
        <div class="image-editor-controls">
        <input type="number" id="image-editor-width" min="1"
            class="image-editor-size" title="<?=
            tl('wiki_element_image_width') ?>" aria-label="<?=
            tl('wiki_element_image_width') ?>" >
        <span class="image-editor-by">&#215;</span>
        <input type="number" id="image-editor-height" min="1"
            class="image-editor-size" title="<?=
            tl('wiki_element_image_height') ?>" aria-label="<?=
            tl('wiki_element_image_height') ?>" >
        <button type="button" id="image-editor-rotate"
            class="button-box image-editor-mark" title="<?=
            tl('wiki_element_image_rotate') ?>" aria-label="<?=
            tl('wiki_element_image_rotate') ?>"><svg viewBox="0 0 24 24"
            class="image-editor-icon"><path d="M12 5 A7 7 0 1 1 5 12"
            fill="none" stroke="currentColor" stroke-width="2" /><path
            d="M12 1.5 L12 8 L16 5 Z" fill="currentColor"
            /></svg></button>
        <button type="button" id="image-editor-flip-across"
            class="button-box image-editor-mark" title="<?=
            tl('wiki_element_image_flip_across') ?>" aria-label="<?=
            tl('wiki_element_image_flip_across') ?>"><svg viewBox="0 0 24 24"
            class="image-editor-icon"><path d="M12 3 L12 21" fill="none"
            stroke="currentColor" stroke-width="2" stroke-dasharray="3 2"
            /><path d="M9 7 L3 12 L9 17 Z" fill="currentColor" /><path
            d="M15 7 L21 12 L15 17 Z" fill="none" stroke="currentColor"
            stroke-width="2" /></svg></button>
        <button type="button" id="image-editor-flip-down"
            class="button-box image-editor-mark" title="<?=
            tl('wiki_element_image_flip_down') ?>" aria-label="<?=
            tl('wiki_element_image_flip_down') ?>"><svg viewBox="0 0 24 24"
            class="image-editor-icon"><path d="M3 12 L21 12" fill="none"
            stroke="currentColor" stroke-width="2" stroke-dasharray="3 2"
            /><path d="M7 9 L12 3 L17 9 Z" fill="currentColor" /><path
            d="M7 15 L12 21 L17 15 Z" fill="none" stroke="currentColor"
            stroke-width="2" /></svg></button>
        <?php
        $this->view->helper("options")->render("image-editor-tool",
            "image_editor_tool", $tools, "doodle");
        ?>
        <?php
        $this->view->helper("options")->render("image-editor-thickness",
            "image_editor_thickness", $widths, "2");
        ?>
        <input type="color" id="image-editor-color"
            value="#000000" title="<?=
            tl('wiki_element_image_color') ?>" >
        </div>
        <div id="image-editor-text-settings" class="image-editor-panel"
            style="display:none">
        <label class="image-editor-field"><?=
            tl('wiki_element_image_font') ?>
        <?php
        $fonts = ["sans-serif" => "Sans", "serif" => "Serif",
            "monospace" => "Mono", "cursive" => "Cursive"];
        $this->view->helper("options")->render("image-editor-font",
            "image_editor_font", $fonts, "sans-serif");
        ?></label>
        <label class="image-editor-field"><?=
            tl('wiki_element_image_font_size') ?>
            <input type="number" id="image-editor-font-size" min="6"
                max="200" value="24" class="image-editor-size" ></label>
        <?php
        $styles = ["normal" => tl('wiki_element_image_font_normal'),
            "bold" => tl('wiki_element_image_font_bold'),
            "italic" => tl('wiki_element_image_font_italic')];
        $this->view->helper("options")->render("image-editor-font-style",
            "image_editor_font_style", $styles, "normal");
        ?>
        </div>
        <canvas id="image-editor-canvas"
            class="image-editor-canvas"></canvas>
        <input type="hidden" id="image-editor-data"
            name="image_editor_data" value="" >
        </div>
        <?php
    }
    /**
     * renderWikiNavRow draws the row of controls for moving about a wiki
     * page: the settings and resources views, the preview, the history,
     * the syntax page, the help, and the language chooser.
     *
     * renderEditPageForm calls this inside the editing form for an
     * ordinary page. A page that lists files draws it inside the file
     * list controls instead, so the two rows share one line; the editing
     * form wraps the whole page rather than that row, so it cannot be
     * laid out beside the file list itself.
     *
     * \@param array $data fields the controller prepared for the page,
     *     read for what the reader may do and which view is showing
     * \@param string $base_url the page's address with its token, already
     *     escaped for markup, which each mark in the row is built from
     */
    public function renderWikiNavRow($data, $base_url)
    {
        $append = $data['OTHER_BACK_URL'] ?? "";
            ?><div class="top-margin wiki-edit-controls"><?php
                /* One row carries every way of moving about this page:
                   the settings and resources views, the preview, the
                   history, the syntax page, and the help for all of it,
                   with the language chooser among them. The page's own
                   name and the way to the page list are not here, the
                   nav bar above carrying both. */
                if (!$this->is_settings_view &&
                    (!$this->is_resources_view ||
                        $this->shows_media_page)) {
                    $this->icon_helper->renderButton(
                        ['settings-toggle-button',
                        $this->settings_toggle_url], 'settings', "");
                } else {
                    ?><b><label for="page-locale"><?=
                        tl('wiki_element_locale') ?></label></b><?php
                }
                ?>
                <?php
                /* Which locale the page is written in. Choosing another
                   loads that locale's page and saves to it, leaving the
                   language the writer reads the site in as it was. */
                $this->renderLocaleChooser($data);
                /* A writer choosing a language here is choosing which of
                   the page's copies to work on, which is worth a word of
                   explaining, as is giving a language its own name for a
                   page. */
                if (!$this->is_settings_view &&
                    (!$this->is_resources_view ||
                        $this->shows_media_page)) {
                    if (!$this->shows_media_page && !$this->is_one_resource
                        && !$this->is_page_alias && !$this->is_share &&
                        !$this->is_shortener && !$this->is_git_repository) {
                        $this->icon_helper->renderButton(
                            ['resources-toggle-button',
                            $this->resources_toggle_url], 'resources', "");
                    }
                    /* The preview shares the editor's window and starts
                       closed, and the history is the other way of
                       looking at the page rather than a setting for it,
                       so the two stand together. */
                    if (!$this->is_git_repository) {
                        if (!empty($data["WIKI_PREVIEW"]) &&
                            !$this->shows_media_page) {
                            $this->icon_helper->renderButton(
                                ['wiki-preview-toggle',
                                "javascript:toggleWikiPreview()"],
                                'preview', "");
                        }
                        $this->icon_helper->renderButton($base_url .
                            $append . '&arg=history&page_id=' .
                            $data['PAGE_ID'], "history");
                        $syntax_page = intval(
                            $data["GROUP"]["RENDER_ENGINE"] ??
                            C\MEDIAWIKI_ENGINE) == C\MARKDOWN_ENGINE ?
                            "Markdown_Syntax" : "Syntax";
                        $this->icon_helper->renderButton(htmlentities(
                            B\wikiUrl($syntax_page, true,
                            $data['CONTROLLER'], C\PUBLIC_GROUP_ID)) .
                            C\p('CSRF_TOKEN') . '=' .
                            $data[C\p('CSRF_TOKEN')] .
                            '&arg=read', 'wiki_syntax');
                    }
                }
                /* One mark explains the language chooser and the icons
                   beside it, rather than each carrying its own. */
                e($this->view->helper("helpbutton")->render(
                    "Page_Setting_and_Resources",
                    $data[C\p('CSRF_TOKEN')]));
                ?>
            </div><?php
    }
    /**
     * renderEditBody draws the box a writer types the page into, or the
     * one screen an article is written on, or a repository's address.
     *
     * renderEditPageForm calls it, so the editor is read one
     * screen at a time rather than in one long method.
     *
     * @param array $data what the page holds and the token
     *     every form on the page carries
     */
    public function renderEditBody($data)
    {
        ?>            <?php
            if (!$this->is_settings_view && !$this->is_resources_view &&
                $this->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
                $this->icon_helper->renderFormButton(
                    "button", "clone_copy", false,
                    "git-icon-control", 'onclick="gitCopyClone(this)" ' .
                    'data-clone="' . $push_url . '"');
                $this->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
                $this->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->view->element("git")->renderGitStatistics(
                $data); ?>
                </div>
            <?php
            } else if (
                !$this->is_settings_view && !$this->is_resources_view) { ?>
                <div id='page-container'><?php
                /* An article is written on one screen: the thumbnail, the
                   title, the author and the abstract are moved here out of
                   the settings panel when the kind is an article, so they
                   are drawn once and there is only ever one field of each
                   name in the form. The categories below them are picked
                   from what the group knows rather than typed. */
                $filed_under = [];
                $body_source = $data['PAGE'] ?? "";
                if (is_string($body_source)) {
                    $found = [];
                    preg_match_all("/\{\{category\|([^}]*)\}\}/i",
                        $body_source, $found);
                    foreach ($found[1] ?? [] as $one) {
                        $one = trim($one);
                        if ($one !== "" && !in_array($one, $filed_under)) {
                            $filed_under[] = $one;
                        }
                    }
                }
                /* The marks saying what an article is filed under are
                   how the filing is kept, and a writer picks the filing
                   from a control of its own, so the writing area shows
                   the words without them. */
                $written_page = ($data['PAGE'] ?? "");
                /* Which categories an article is filed under is set by
                   the picker above, and kept in the page's own words so
                   the site can find it. A writer has no reason to meet
                   those marks among their sentences, so they are taken
                   out of what is shown and put back when the page is
                   saved. */
                if (($data['current_page_type'] ?? "") == "news_article") {
                    $written_page = trim(preg_replace(
                        '/\{\{category\|[^}]*\}\}\s*/', "",
                        $written_page));
                }
                if (!$this->is_one_resource) {
                ?><div id="article-header" data-thumbnail-label="<?=
                    tl('wiki_element_thumbnail') ?>" data-abstract-label="<?=
                    tl('wiki_element_abstract') ?>">
                    <div id="article-header-top">
                    <div id="article-header-fields"></div>
                    <div id="article-header-thumb">
                    <div id="banner-lines">
                    <?php
                    /* What a banner shows and who holds the rights to it
                       belong beneath it, since neither means anything
                       without the picture above them. Each says what it
                       is in the box itself rather than in a label
                       beside it. */
                    ?><input type="text" id="banner-caption"
                        name="banner_caption" class="banner-line"
                        maxlength="<?= C\SHORT_TITLE_LEN ?>"
                        value="<?= $data['banner_caption'] ?? "" ?>"
                        placeholder="<?=
                        tl('wiki_element_banner_caption') ?>" >
                    <input type="text" id="banner-rights"
                        name="banner_rights" class="banner-line"
                        maxlength="<?= C\SHORT_TITLE_LEN ?>"
                        value="<?= $data['banner_rights'] ?? "" ?>"
                        placeholder="<?=
                        tl('wiki_element_banner_rights') ?>" >
                    </div>
                    </div>
                    </div>
                </div>
                <div id="article-categories-setting" class="top-margin">
                <?php
                /* The heading is the chooser: it names what the list
                   beneath it is and offers what may be added to it, so a
                   writer picks from one control instead of reading a
                   heading and then finding a separate box below the
                   list. */
                $offered = ["" => tl('wiki_element_article_categories')];
                foreach ($data['front_page_categories'] ?? [] as $one) {
                    $offered[$one] = $one;
                }
                ?><div class="tag-add-row"><?php
                $this->view->helper("options")->render(
                    "article-category-add", "article_category_add",
                    $offered, "", false,
                    ['class' => 'article-category-heading']);
                ?><button type="button" id="article-category-add-button"
                    class="button-box" data-remove-label="<?=
                    tl('wiki_element_article_category_remove')
                    ?>"><?= tl('wiki_element_article_category_add')
                    ?></button>
                </div>
                <ul class="tag-list" id="article-category-list"><?php
                foreach ($filed_under as $one_category) { ?>
                    <li class="tag-item"><span class="tag-text"><?=
                        $one_category ?></span><button type="button"
                        class="tag-remove article-category-remove"
                        title="<?= tl('wiki_element_article_category_remove')
                        ?>">&times;</button></li><?php
                } ?></ul>
                <input type="hidden" id="article-categories"
                    name="article_categories" value="<?=
                    implode(",", $filed_under) ?>" >
                </div><?php
                }
                if (!str_starts_with($data['current_page_type'], 't') ||
                    !is_numeric(substr($data['current_page_type'], 1))) {
                    if (empty($data['SPREADSHEET']) && $this->shows_image) {
                        /* A picture is drawn on a canvas the writer may
                           change, with a row of controls above it for the
                           size, turning, mirroring, and the drawing tools.
                           The picture's own address rides on the canvas so
                           the editor can load it, and the file name rides
                           on the row so a save writes back the same file in
                           the same kind. */
                        $this->renderImageEditor($data);
                    } else if (empty($data['SPREADSHEET'])) {
                        /* One file kept with a page holds no markup,
                           so the window is not split and no preview is
                           offered for it. */
                        $split = (empty($data["WIKI_PREVIEW"]) ||
                            $this->is_one_resource) ? "" :
                            " wiki-edit-split wiki-preview-closed";
                        /* The editor's own toolbar is put in just before
                           the textarea by wiki.js, so the textarea sits in
                           a box of its own and the toolbar lands in there
                           with it rather than beside the preview. */
                        ?>
                        <div class="wiki-edit-pair<?= $split ?>"
                            id="wiki-edit-pair">
                        <div class="wiki-edit-source">
                        <textarea id="wiki-page"
                            data-page-name="<?= $data['PAGE_NAME'] ?? "" ?>"
                            class="tall-text-area" name="page"
                            <?php
                            /* A file kept with a page is its own kind of
                               text, not the page's markup, so the marks
                               for writing markup would do nothing to it
                               and are not offered. */
                            if ($this->is_one_resource) {
                                $data_buttons = 'none';
                            } else if ((!isset($data['page_type']) ||
                                    $data['page_type'] != 'presentation')) {
                                $data_buttons = 'all,!wikibtn-slide';
                            } else {
                                $data_buttons = 'all';
                            }?>
                            data-buttons='<?=$data_buttons ?>'
                            <?php
                            /* The page's own group and id travel on the
                               field itself, since the preview draws a
                               resource by asking the site for it and
                               needs to say which page's files it
                               wants. */
                            ?>
                            data-group-id='<?= $data['GROUP']['GROUP_ID']
                                ?? 0 ?>'
                            data-page-id='<?=
                                $data['WIKI_PREVIEW_PAGE_ID'] ??
                                ($data['PAGE_ID'] ?? 0) ?>'
                            <?php
                            /* A file kept with the page is asked for as
                               a path where the site serves pretty
                               addresses and as a query where it does
                               not, and the two are not
                               interchangeable. */
                            ?>
                            data-pretty-addresses='<?=
                                C\REDIRECTS_ON ? 1 : 0 ?>'
                            <?php
                            /* A file kept with a page is asked for under
                               a mark of its own, which for a group
                               anyone may join is a row of zeros rather
                               than the reader's own, as the site does
                               when it fills a page in for reading. */
                            $resource_mark = $data[C\p('CSRF_TOKEN')] ?? "";
                            if ($data['GROUP']['GROUP_ID'] ==
                                C\PUBLIC_GROUP_ID ||
                                (!empty($data["GROUP"]["REGISTER_TYPE"]) &&
                                $data["GROUP"]["REGISTER_TYPE"] ==
                                C\PUBLIC_JOIN)) {
                                $resource_mark = str_pad("", 11, "0");
                            }
                            ?>
                            data-resource-mark='<?= $resource_mark ?>'
                            data-base-address='<?=
                                C\SHORT_BASE_URL ?>'
                            <?php
                            /* Whether this page draws a table of contents
                               is kept in its head, which the preview
                               never sees, so it is written here for the
                               preview to read. */
                            ?>
                            data-draw-toc='<?=
                                empty($data['HEAD']['toc']) ? 0 : 1 ?>'
 ><?=
                            $written_page ?></textarea></div><?php
                        if (!$this->is_one_resource) {
                            $this->renderWikiPreview($data);
                        }
                        ?></div><?php
                        if (!$this->is_one_resource) {
                            $this->renderWikiPreviewStart($data);
                        }
                    } else {
                        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);
                    ?></div>
                    <div class="top-margin" id="edit-reason-container">
                    <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
            }?>
<?php
    }
    /**
    /**
     * renderEditButtons draws the button that saves what a writer has
     * typed into the editor.
     *
     * renderEditPageForm calls it, so the editor is read one
     * screen at a time rather than in one long method.
     *
     * @param array $data what the page holds and the token
     *     every form on the page carries
     */
    public function renderEditButtons($data)
    {
        ?>            <div id="save-container" class="top-margin center"><?php
            /* Saving sits beside the keep-as-you-write control and is
               offered only while that control is off. */
            if ($this->is_settings_view) {
                /* The settings are a form to fill in and send, with no
                   writing area beside them and no keeping as you write,
                   so saving is a word rather than a mark: there is
                   nothing here to make its meaning plain. */
                ?><button class="button-box" type="submit"><?=
                    tl('wiki_element_savebutton') ?></button><?php
            } else if (!$this->is_resources_view && !$this->is_one_resource &&
                !($this->is_git_repository && !$this->is_settings_view)) {
                $this->icon_helper->renderFormButton("submit", "save", false,
                    "icon-anchor-button", "id='wiki-save-button'");
            }?>
<?php
    }
}
X