foldershare-8.x-1.2/src/Form/UIAncestorMenu.php
src/Form/UIAncestorMenu.php
<?php
namespace Drupal\foldershare\Form;
use Drupal\Core\Url;
use Drupal\Component\Utility\Html;
use Symfony\Component\HttpFoundation\Request;
use Drupal\foldershare\Constants;
use Drupal\foldershare\Entity\FolderShare;
use Drupal\foldershare\FolderShareInterface;
/**
* Provides support for the ancestor menu used on multiple pages.
*
* <B>Warning:</B> This class is strictly internal to the FolderShare
* module. The class's existance, name, and content may change from
* release to release without any promise of backwards compatability.
*
* @ingroup foldershare
*/
final class UIAncestorMenu {
/*--------------------------------------------------------------------
*
* Functions.
*
*-------------------------------------------------------------------*/
/**
* Creates a render description for an ancestor menu.
*
* The last item in the ancestor menu is a list of available root lists
* for the user:
* - Personal files.
* - Public files.
* - All files.
*
* The anonoymous user always sees only the "public" list, regardless of
* the value of the $allowPublicRootList argument.
*
* The "all" list is only included if the user has module admin permissions.
*
* The "public" list is only included if $allowPublicRootList is TRUE.
*
* @param int $currentId
* (optional, default = FolderShareInterface::USER_ROOT_LIST) The integer
* entity ID of the current FolderShare item. If the value is negative or
* FolderShareInterface::USER_ROOT_LIST, if the current item is
* the user's root list.
* @param bool $allowPublicRootList
* (optional, default = TRUE) When TRUE, the public root list is available.
*
* @return array
* Returns an array of renderable elements for an ancestor menu's
* entries.
*/
public static function build(
int $currentId = FolderShareInterface::USER_ROOT_LIST,
bool $allowPublicRootList = TRUE) {
//
// Setup.
// ------
// Get services and the current request.
$routeProvider = \Drupal::service('router.route_provider');
$titleResolver = \Drupal::service('title_resolver');
$dummyRequest = new Request();
//
// Load entity.
// ------------
// Load the current entity, if any.
$currentItem = NULL;
if ($currentId >= 0) {
$currentItem = FolderShare::load($currentId);
if ($currentItem === NULL) {
$currentId = FolderShareInterface::USER_ROOT_LIST;
}
}
$uiClass = 'foldershare-ancestormenu';
$menuButtonClass = $uiClass . '-menu-button';
$menuClass = $uiClass . '-menu';
//
// Add ancestor list markup.
// -------------------------
// Build a list of ancestor folders for the ancestor menu. For each one,
// include the ancestor's URL as an attribute. Javascript will use this
// to load the appropriate page. The URL is not included in an <a> for
// the menu item so that menu items aren't styled as links.
//
// File classes are added so that themes can style the item with
// folder icons.
$menuMarkup = "<ul class=\"hidden $menuClass\">";
if ($currentItem !== NULL) {
// The page is for entity. Get its ancestors.
$folders = $currentItem->findAncestorFolders();
// Reverse ancestors from root first to root last.
$folders = array_reverse($folders);
// Push the current entity onto the ancestor list so that it gets
// included in the menu.
array_unshift($folders, $currentItem);
// Add ancestors to menu.
foreach ($folders as $item) {
// Get the URL to the folder.
$url = rawurlencode($item->toUrl(
'canonical',
[
'absolute' => TRUE,
])->toString());
// Get the name for the folder.
$name = Html::escape($item->getName());
// Add the HTML. Include file classes that mark this as a folder
// or file.
if ($item->isFolder() === TRUE) {
$fileClasses = 'file file--folder file--mime-folder-directory';
}
else {
$mimes = explode('/', $item->getMimeType());
$fileClasses = 'file file--' . $mimes[0] .
' file--mime-' . $mimes[0] . '-' . $mimes[1];
}
if ($item->isSystemDisabled() === TRUE) {
$attr = '';
}
else {
$attr = 'data-foldershare-id="' . $item->id() . '"';
}
$menuMarkup .= "<li $attr data-foldershare-url=\"$url\"><div><span class=\"$fileClasses\"></span>$name</div></li>";
}
}
//
// Add root list.
// --------------
// Show a list of available root lists in a submenu. If there is only
// one available root list, then don't use a submenu.
$currentUser = \Drupal::currentUser();
// Start by assembling a list of available root lists for this user.
$rootLists = [];
if ($currentUser->isAnonymous() === TRUE) {
// The anonymous user can only view the public list.
$rootLists[] = [
"public",
Constants::ROUTE_ROOT_ITEMS_PUBLIC,
];
}
elseif ($currentUser->hasPermission(Constants::ADMINISTER_PERMISSION) === TRUE) {
// An admin user can view all of the lists.
$rootLists[] = [
"personal",
Constants::ROUTE_ROOT_ITEMS_PERSONAL,
];
if ($allowPublicRootList === TRUE) {
$rootLists[] = [
"public",
Constants::ROUTE_ROOT_ITEMS_PUBLIC,
];
}
$rootLists[] = [
"all",
Constants::ROUTE_ROOT_ITEMS_ALL,
];
}
else {
// Authenticated non-admin users can view their personal list.
$rootLists[] = [
"personal",
Constants::ROUTE_ROOT_ITEMS_PERSONAL,
];
if ($allowPublicRootList === TRUE) {
$rootLists[] = [
"public",
Constants::ROUTE_ROOT_ITEMS_PUBLIC,
];
}
}
// Build markup for the available root lists.
$fileClasses = 'file file--folder file--mime-rootfolder-group-directory';
if (count($rootLists) === 1) {
$rootListName = $rootLists[0][0];
$rootListRoute = $rootLists[0][1];
// Get the root list's route.
$route = $routeProvider->getRouteByName($rootListRoute);
// Create a route URL to the root list.
$url = Url::fromRoute(
$rootListRoute,
[],
['absolute' => TRUE])->toString();
// Get the route's title.
$title = $titleResolver->getTitle($dummyRequest, $route);
// Create markup for a menu entry to the root list.
$attr = "data-foldershare-id=\"$rootListName\" data-foldershare-url=\"$url\"";
$menuMarkup .= "<li $attr><div><span class=\"$fileClasses\"></span>$title</div></li>";
}
else {
$title = (string) t('Lists');
$menuMarkup .= "<li><div>$title</div><ul>";
foreach ($rootLists as $rootList) {
$rootListName = $rootList[0];
$rootListRoute = $rootList[1];
// Get the root list's route.
$route = $routeProvider->getRouteByName($rootListRoute);
// Create a route URL to the root list.
$url = Url::fromRoute(
$rootListRoute,
[],
['absolute' => TRUE])->toString();
// Get the route's title.
$title = $titleResolver->getTitle($dummyRequest, $route);
// Create markup for a menu entry to the root list.
$attr = "data-foldershare-id=\"$rootListName\" data-foldershare-url=\"$url\"";
$menuMarkup .= "<li $attr><div><span class=\"$fileClasses\"></span>$title</div></li>";
}
$menuMarkup .= "</ul></li>";
}
$menuMarkup .= '</ul>';
//
// Create menu button.
// -------------------
// Create HTML for a button. Include:
//
// - Class 'hidden' so that the button is initially hidden and only shown
// later by Javascript, if the browser supports scripting.
$buttonText = (string) t('Ancestors');
$buttonMarkup = "<button type=\"button\" class=\"hidden $menuButtonClass\"><span>$buttonText</span></button>";
//
// Create UI
// ---------
// Everything is hidden initially, and only exposed by Javascript, if
// the browser supports Javascript.
$renderable = [
'#attributes' => [
'class' => [
'foldershare-ancestormenu',
],
],
$uiClass => [
'#type' => 'container',
'#weight' => -90,
'#attributes' => [
'class' => [
$uiClass,
'hidden',
],
],
// Add a hierarchical menu of ancestors. Javascript uses jQuery.ui
// to build a menu from this and presents it from a menu button.
// The menu was built and marked as hidden.
//
// Implementation note: The field is built using an inline template
// to avoid Drupal's HTML cleaning that can remove classes and
// attributes on the menu items, which we need to retain to provide
// the URLs of ancestor folders. Those URLs are used by Javascript
// to load the appropriate page when a menu item is selected.
$menuClass => [
'#type' => 'inline_template',
'#template' => '{{ menu|raw }}',
'#context' => [
'menu' => $menuMarkup,
],
],
// Add a button to go up a folder. Javascript binds a behavior
// to the button to load the parent page. The button is hidden
// initially and only shown if the browser supports Javascript.
//
// Implementation note: The field is built using an inline template
// so that we get a <button>. If we used the '#type' 'button',
// Drupal instead creates an <input>. Since we specifically want a
// button so that jQuery.button() will button-ize it, we have to
// bypass Drupal.
$menuButtonClass => [
'#type' => 'inline_template',
'#template' => '{{ button|raw }}',
'#context' => [
'button' => $buttonMarkup,
],
],
],
];
return $renderable;
}
}
