<?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;">⇈</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']}'><<</a> ");
} else {
e("<a id='prev-link' class='none'" .
" href=''><<</a> ");
}
$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(" <a id='next-link' href='" .
"{$data['NEXT_LINK']}'>>></a>");
} else {
e(" <a id='next-link' class='none'" .
" href=''>>></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') . "'>⇅</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('"','"');
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" => ["≣", tl('wiki_element_list_view')],
"grid" => ["⊞", tl('wiki_element_grid_view')],
"detail" => ["<span class='dotted-underline'>▢</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') . "'>⇅</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')
?>'>📋</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') . "'>⋯</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">×</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'> </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 = "◊";
}
}
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("'", "'", $name) . "' ";
if (!empty($resource['is_dir'])) {
$drag_attributes .= " data-folder-target='" .
str_replace("'", "'", $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) ? "" : " <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'>ⓘ</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 . "\")' ".
">♦</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("'", "'", $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 ?>)'>✓</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
?>")'>✕</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"
>…</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("'", "'", $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'>↑</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> </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> </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> </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'> </div>
<h1><?= tl('wiki_element_raw_data'); ?></h1>
<p><?=tl('wiki_element_check_form_hashes'); ?></p>
<div id='spreadsheet'> </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') .
"'>↻</span></a>";
$queued_html = "<span class='gray' title='" .
tl('wiki_element_podcast_queued') . "' role='img' " .
"aria-label='" . tl('wiki_element_podcast_queued') .
"'>⌛</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') .
"'>⚠</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">×</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')
?>">×</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'> </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
}
}