<?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;
/**
* Element used to draw the navigation bar on group feed and wiki pages.
*
* @author Chris Pollett
*/
class GroupbarElement extends Element
{
/**
* Used to draw the navigation bar on the group feed and wiki page portion
* of the yioop website
*
* @param array $data contains antiCSRF token, as well as data on
* used to initialize title for group or wiki page
*/
public function render($data)
{
if (isset($data["HEAD"]['page_type']) &&
$data["HEAD"]['page_type'] == 'presentation' &&
($data['MODE'] ?? 'read') == 'read' ) {
/* A presentation being read is a slide deck taking the whole
screen, so it carries no bar. Somebody who may edit the
page still needs the way in, and the way to its
discussion, so those two are drawn on their own in the
corner, held there while the deck moves under them. */
$this->renderPresentationCorner($data);
return;
}
$logo = $this->view->helper("logo")->renderResponsive($data,
$this->view->logo_alt_text ?? "");
$logged_in = !empty($data["ADMIN"]);
$is_browse = (!$logged_in || isset($data['SUBSCRIBE_LINK']) ||
!empty($data["NOT_MEMBER"]));
$is_wiki = isset($data['ELEMENT']) && $data['ELEMENT'] == 'wiki';
$token_string = ($logged_in) ? C\p('CSRF_TOKEN') . "=" .
$data[C\p('CSRF_TOKEN')]
: "";
/* A page has a copy in each language, so a link that does not
say which one reaches the copy the site is written in. The
language the reader is looking at rides with the token on
every link below, which is how the wiki view names it. */
if (!empty($data['CURRENT_LOCALE_TAG'])) {
$token_string .= ($token_string === "") ? "" : "&";
$token_string .= "page_locale=" .
urlencode($data['CURRENT_LOCALE_TAG']);
}
$alt_base_query = B\feedsUrl("",
"", true, $data['CONTROLLER']) . $token_string;
$base_query = ($is_wiki) ? htmlentities(B\wikiUrl("", true,
$data['CONTROLLER'],
($data["GROUP"]["GROUP_ID"] ?? 0))) : $alt_base_query;
$admin_url = htmlentities(B\controllerUrl('admin', true));
/* controllerUrl leaves a url ready for the first thing in its
query, the same as wikiUrl does, so the token when there is one
is that first thing and what follows needs a separator; with
nobody signed in there is no token and what follows is itself
the first thing. */
$admin_query = $admin_url . $token_string;
if ($token_string !== "") {
$admin_query .= "&";
}
?><div class="none" >[<a href="#center-container"><?=
tl('groupbar_element_skip_nav')
?>]</a></div><div id='nav-bar' class="nav-bar">
<div class='inner-bar'><?php
$this->view->helper("hamburger")->render($logged_in);
if (!empty($data['ACTIVITY_METHOD']) &&
$data['ACTIVITY_METHOD'] == 'wiki') {
$this->renderWikiNavButtons($data);
}
?>
<div class="title-block">
<h1><a href="<?=C\SHORT_BASE_URL ?><?php
if ($logged_in) {
e("?$token_string");
}
?>"><?php e($logo); ?></a></h1>
<div class='nav-span'> - <?php
if (!empty($data['ACTIVITY_METHOD']) &&
$data['ACTIVITY_METHOD'] == 'wiki') {
$group_url = $this->getGroupUrl(
($data['GROUP']['GROUP_NAME'] ?? ''),
$token_string, $is_browse);
$wiki_page_list_url = rtrim(htmlentities(B\wikiUrl(
"pages", true, $data['CONTROLLER'],
($data["GROUP"]["GROUP_ID"] ?? 0))) . $token_string, "?");
$this->view->helper("iconlink")->renderButton(
$admin_query . "a=manageAccounts", "account_home");
$page_name = str_replace("_", " ", ($data['PAGE_NAME'] ?? '')?? "");
if ($page_name == "Main") {
$page_name = tl('groupbar_element_wiki');
}
$is_page_list = false;
if (($data['MODE'] ?? 'read') == 'pages') {
/* A list narrowed to one category is that category's own
page, so the bar names the category rather than the
whole of a group's pages. */
$page_name = empty($data["LIST_CATEGORY"]) ?
tl('groupbar_element_page_list') :
$data["LIST_CATEGORY"];
$is_page_list = true;
}
?><a class="bar-item" href="<?=$group_url ?>"><?=
urldecode(($data['GROUP']['GROUP_NAME'] ?? '')) ?></a>:<?php
$colon = "";
if (!$is_page_list) {
$this->view->helper("iconlink")->renderButton(
$wiki_page_list_url, "pages");
$colon = ":";
}
if (($data['PAGE_NAME'] ?? '') != "Main" && in_array(
($data['MODE'] ?? 'read'), ['read', 'edit', 'source'])) {
$colon = ":";
$wiki_main_url = htmlentities(B\wikiUrl(
"Main", true, $data['CONTROLLER'],
($data["GROUP"]["GROUP_ID"] ?? 0))) . $token_string;
$this->view->helper("iconlink")->renderButton(
$wiki_main_url, "wiki");
}
e($colon);
if (($data['MODE'] ?? 'read') == 'read' &&
($_REQUEST['repo_view'] ?? "") == 'issues') {
$read_url = htmlentities(B\wikiUrl(($data['PAGE_NAME'] ?? ''),
true, $data['CONTROLLER'],
($data["GROUP"]["GROUP_ID"] ?? 0))) . $token_string;
?><a class="bar-item" href='<?=$read_url ?>'><?=
urldecode($page_name)?></a><?php
} else if (($data['MODE'] ?? 'read') == 'read') {
?><div class="bar-item"><?=urldecode($page_name)?></div><?php
} else if (($data['MODE'] ?? 'read') == 'pages') {
?><div class="wide-bar-item"><?=
urldecode($page_name)?></div><?php
} else {
$wiki_base_url = htmlentities(B\wikiUrl(
($data['PAGE_NAME'] ?? ''), true, $data['CONTROLLER'],
($data["GROUP"]["GROUP_ID"] ?? 0))) . $token_string;
?><a class="bar-item" href='<?=$wiki_base_url
?>' target='preview'><?=urldecode($page_name); ?></a><?php
/* The bar says which view of the page is open, so a
reader can see where they are. The history is a mode
of its own; the settings and the resources are views
of the editing screen, marked by their own flags, and
each is named here the same way. */
if (($data['MODE'] ?? 'read') == 'history') {
?>:<?=tl('groupbar_element_history');?><?php
} else if (!empty($data['settings']) &&
$data['settings'] != "false") {
?>:<?=tl('groupbar_element_settings');?><?php
} else if (!empty($data['resources']) &&
$data['resources'] != "false" &&
empty($data['PAGE_LISTS_FILES'])) {
?>:<?php
$this->renderResourcesCrumb($data);
} else if (!empty($data['RESOURCE_NAME'])) {
?>:<?php
if (empty($data['PAGE_LISTS_FILES'])) {
$this->renderResourcesCrumb($data);
?>:<?php
}
?><?=$data['RESOURCE_NAME']?><?php
}
}
} else if (($data['SUBTITLE'] ?? "") == C\PERSONAL_GROUP_PREFIX) {?>
<?=
$this->view->helper("iconlink")->renderButton(
$admin_query . "a=manageAccounts",
"account_home");?> <?=
(($data['ACTIVITY_METHOD'] ?? "") == 'userMail') ?
tl('groupbar_element_mail') :
tl('groupbar_element_messages') ?><?php
} else {
$this->view->helper("iconlink")->renderButton(
$admin_query . "a=manageAccounts", "account_home");
e(" ");
if (!empty($data['JUST_THREAD']) &&
!empty($data['WIKI_PAGE_NAME'])) {
$group_name = $data['GROUP_NAME'] ??
$data['PAGES'][0][L\CrawlConstants::SOURCE_NAME]
?? "";
$group_url = $this->getGroupUrl($group_name,
$token_string, $is_browse);
$group_id = $data["GROUP_ID"] ??
$data['PAGES'][0]["GROUP_ID"] ?? "";
$wiki_base_url = htmlentities(B\wikiUrl(
$data['WIKI_PAGE_NAME'], true, $data['CONTROLLER'],
$group_id)) . $token_string;?>
<a class="bar-item" href='<?=$group_url ?>' ><?=$group_name
?></a>:<a class="bar-item" href='<?=$wiki_base_url ?>' ><?=
$data['WIKI_PAGE_NAME'] ?></a>:<div class="bar-item"><?=
tl('groupbar_element_talk');?></div><?php
} else if (!empty($data['JUST_GROUP_ID'])) {
$group_url = $this->getGroupUrl($data['SUBTITLE'],
$token_string, $is_browse); ?>
<a class="wide-bar-item" href='<?=$group_url ?>' ><?=
urldecode($data['SUBTITLE']);
?></a>:<div class="bar-item"><?=
tl('groupbar_element_talk');?></div><?php
} else if (!empty($data['JUST_USER_ID'])) {
if (empty($data['PAGES'][0]["USER_NAME"])) {
e(tl("groupbar_element_no_path_info"));
} else {?>
<div class="wide-bar-item" ><?=
tl("groupbar_element_userfeed",
urldecode($data['PAGES'][0]["USER_NAME"]))?></div><?php
}
} else if (!empty($data['JUST_THREAD'])) {
if (!empty($data['GROUP_NAME'])) {
$group_url = $this->getGroupUrl($data['GROUP_NAME'],
$token_string, $is_browse);
?><a class="bar-item" href='<?=$group_url?>'><?=
urldecode($data['GROUP_NAME'])
?></a>:<a class="bar-item" href='<?=htmlentities(
B\feedsUrl("group", $data["GROUP_ID"],
true, 'group')) . $token_string?>' ><?=
tl("groupbar_element_talk")
?></a>:<div class="bar-item"><?=
urldecode($data['SUBTITLE'])?></div><?php
} else {
e(urldecode($data['SUBTITLE']));
}
if (!empty($data['SHOW_THREAD_FOLLOW'])) {
$follow_base = B\feedsUrl("thread",
$data['JUST_THREAD'], true, "group") .
C\p('CSRF_TOKEN') . "=" . $data[C\p('CSRF_TOKEN')];
if (!empty($data['THREAD_FOLLOWED'])) {
$follow_url = $follow_base .
"&arg=togglethreadmail&follow=0";
$follow_type = "thread_followed";
} else {
$follow_url = $follow_base .
"&arg=togglethreadmail&follow=1";
$follow_type = "thread_unfollowed";
}
$this->view->helper("iconlink")->renderButton(
$follow_url, $follow_type);
}
} else if (!empty($data['VIEW_MODE']) &&
$data['VIEW_MODE'] == "ungrouped") {
e(tl('groupbar_element_discussions'));
} else {
e(tl('groupbar_element_my_groups'));
}
}?></div>
</div>
</div>
</div>
<?php
}
/**
* Computes the url to show one group in the
* already belong to the group ($is_browse == false) and the
* just looking at group ($is_browse == true) setting.
*
* @param string $group_name name of single group to show
* @param string $token_string CSRF token to show legitimately logged in
* @param bool $is_browse whether already belong to group (false) or not.
* @return string url to either My Groups or Join Groups pages for group
*/
public function getGroupUrl($group_name, $token_string, $is_browse)
{
/* controllerUrl leaves the url ready for the first thing in its
query, so the token when there is one is that first thing and
needs a separator after it; with nobody signed in there is no
token and what follows is itself the first thing. */
$token_query = ($token_string === "") ? "" :
$token_string . "&";
if ($is_browse) {
$group_url = htmlentities(B\controllerUrl('admin', true)) .
$token_query . "a=manageGroups&context=group" .
"&group_filter=%3D$group_name&browse=true";
} else {
$group_url = htmlentities(B\controllerUrl('group', true)) .
$token_query . "group_filter=%3D$group_name";
}
return $group_url;
}
/**
* Draws navigation controls in the GroupBar when on Wiki pages
* (floated to opposite side of current reading direction) allowing
* a user depending on the access to the group to edit or view source of a
* page and to see the pages discussion thread.
*
* @param array $data associative array of data from the controller/
* SocialComponent used to draw th enav buttons.
*/
/**
* renderPresentationCorner draws the way into editing a presentation
* and the way to its discussion, for a reader who may edit it. A
* presentation being read fills the screen with its slides and shows
* no bar, so these two would otherwise have nowhere to stand and a
* writer would have no way back to the page's source. The marks
* themselves are the ones the bar draws for any wiki page, asked for
* here and held in a corner of the window rather than placed in the
* page, so the deck can move under them.
*
* @param array $data fields the controller prepared for the page,
* read for who may edit, the page's name and group, and the
* thread its discussion lives in
*/
public function renderPresentationCorner($data)
{
if (empty($data["ADMIN"]) || empty($data["CAN_EDIT"])) {
return;
}
e("<div class='presentation-corner'>");
$this->renderWikiNavButtons($data);
e("</div>");
}
/**
* renderResourcesCrumb draws the mark in the bar standing for the
* list of files kept with a page, which is also the way back to that
* list. It is drawn where the reader is looking at that list or at
* one file from it.
*
* @param array $data fields the controller prepared for the page,
* read for the page's name and group and the token
*/
public function renderResourcesCrumb($data)
{
$token_string = empty($data[C\p('CSRF_TOKEN')]) ? "" :
C\p('CSRF_TOKEN') . "=" . $data[C\p('CSRF_TOKEN')];
$list_url = htmlentities(B\wikiUrl(($data['PAGE_NAME'] ?? ''),
true, $data['CONTROLLER'],
($data["GROUP"]["GROUP_ID"] ?? 0))) . $token_string;
if ($token_string !== "") {
$list_url .= "&";
}
$list_url .= "arg=edit&resources=true";
$this->view->helper("iconlink")->renderButton($list_url,
"resources");
}
/**
* renderWikiNavButtons draws navigation controls in the GroupBar when
* on Wiki pages, floated to the opposite side of the current reading
* direction, allowing a user, depending on their access to the group,
* to edit or view the source of a page and to see the page's
* discussion thread.
*
* @param array $data associative array of data from the controller
* and SocialComponent used to draw the nav buttons.
*/
public function renderWikiNavButtons($data)
{
$read_mode = in_array(($data['MODE'] ?? 'read'), ['read', 'ballot-init',
'count-ballots', 'ballot-concluded']);
$logged_in = !empty($data["ADMIN"]);
$token_string = ($logged_in) ? C\p('CSRF_TOKEN') . "=" .
$data[C\p('CSRF_TOKEN')]
: "";
/* A page has a copy in each language, so a link that does not
say which one reaches the copy the site is written in. The
language the reader is looking at rides with the token on
every link below, which is how the wiki view names it. */
if (!empty($data['CURRENT_LOCALE_TAG'])) {
$token_string .= ($token_string === "") ? "" : "&";
$token_string .= "page_locale=" .
urlencode($data['CURRENT_LOCALE_TAG']);
}
$can_edit = $logged_in && !empty($data["CAN_EDIT"]);
$public_source = empty($data['NO_HISTORY_SOURCE']);
$wiki_base_url = htmlentities(B\wikiUrl(
($data['PAGE_NAME'] ?? ''), true, $data['CONTROLLER'],
($data["GROUP"]["GROUP_ID"] ?? 0))) . $token_string;
/* wikiUrl leaves a url ready for the first thing in its query, so
the token, when there is one, is that first thing and what
follows it needs a separator of its own. Nobody signed in
carries no token, and then what follows is itself the first
thing and needs no separator; writing one anyway is what put a
stray ampersand straight after the question mark. */
if ($token_string !== "") {
$wiki_base_url .= "&";
}
$page_type = $data["HEAD"]['page_type'] ??
($data['page_type'] ?? "");
$is_git_repository = ($page_type == 'git_repository');
$issues_url = $wiki_base_url . "arg=read&repo_view=issues";
$discuss_url = (!empty($data['DISCUSS_THREAD']) &&
($can_edit|| $public_source)) ?
htmlentities(B\feedsUrl("thread",
$data['DISCUSS_THREAD'], true, "group")) . $token_string : "";
e("<div class='float-opposite medium-margin'>");
/* The settings, the resources and the history are ways of
looking at a page while editing it, so the way back from each
is the editing screen rather than the read screen. Each used
to carry a close control of its own for that, which this
replaces. */
$beside_editing = (!empty($data['settings']) &&
$data['settings'] != "false") ||
(!empty($data['resources']) &&
$data['resources'] != "false") ||
($data['MODE'] ?? 'read') == 'history';
$editing_one_file = !empty($data['RESOURCE_NAME']) &&
(empty($data['resources']) || $data['resources'] == "false");
$page_lists_files = !empty($data['PAGE_LISTS_FILES']);
if (($beside_editing || $editing_one_file) && $can_edit) {
$edit_url = $wiki_base_url . "arg=edit";
if ($editing_one_file && !$page_lists_files) {
$edit_url .= "&resources=true";
}
if (!empty($data['SUB_PATH'])) {
$edit_url .= "&sf=".urlencode($data['SUB_PATH']);
}
$this->view->helper("iconlink")->renderButton(
$edit_url, "view_edit");
} else if (!$read_mode) {
$read_url = $wiki_base_url . "arg=read";
if (!empty($data['SUB_PATH'])) {
$read_url .= "&sf=".urlencode($data['SUB_PATH']);
}
$this->view->helper("iconlink")->renderButton(
$read_url, "view_read");
} else if ($can_edit) {
$edit_url = $wiki_base_url . "arg=edit";
$media_file = $data['MEDIA_NAME'] ?? "";
$media_ext = strtolower(pathinfo($media_file,
PATHINFO_EXTENSION));
if ($media_file !== "" &&
(in_array($media_ext, C\EDITABLE_RESOURCE_EXTENSIONS) ||
in_array($media_ext, C\EDITABLE_IMAGE_EXTENSIONS))) {
$edit_url .= "&n=" . urlencode($media_file);
}
if (!empty($data['SUB_PATH'])) {
$edit_url .= "&sf=".urlencode($data['SUB_PATH']);
}
$this->view->helper("iconlink")->renderButton(
$edit_url, "edit");
} else if ($public_source) {
$source_url = $wiki_base_url . "arg=source";
if (!empty($data['SUB_PATH'])) {
$source_url .= "&sf=".urlencode($data['SUB_PATH']);
}
$this->view->helper("iconlink")->renderButton(
$source_url, "view_source");
}
if ($is_git_repository) {
e("<div id='discuss-button'>");
$this->view->helper("iconlink")->renderButton(
$issues_url, "issue_tracker");
e("</div>");
} else if (!empty($discuss_url)) {
e("<div id='discuss-button'>");
$this->view->helper("iconlink")->renderButton(
$discuss_url, "discuss_page");
e("</div>");
}
e("</div>");
}
}