foldershare-8.x-1.2/src/Entity/Builder/FolderShareBreadcrumbBuilder.php
src/Entity/Builder/FolderShareBreadcrumbBuilder.php
<?php
namespace Drupal\foldershare\Entity\Builder;
use Drupal\Core\Breadcrumb\Breadcrumb;
use Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface;
use Drupal\Core\Controller\TitleResolver;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Access\AccessManagerInterface;
use Drupal\Core\Link;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Routing\RouteProviderInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Symfony\Component\HttpFoundation\Request;
use Drupal\foldershare\Constants;
use Drupal\foldershare\FolderShareInterface;
use Drupal\foldershare\Entity\FolderShare;
/**
* Builds a page breadcrumb showing the ancestors of the given entity.
*
* <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.
*
* This class builds a breadcrumb that includes a chain of links to
* each of the ancestors of the given entity. The chain starts with a link
* to the site's home page. This is followed by a link to the canonical
* root folder list page. The remaining links lead to ancestors of the
* entity. Per Drupal convention, the entity itself is not included on
* the end of the breadcrumb.
*
* <b>Service:</b>
* A service for this breadcrumb builder should be registered in
* MODULE.services.yml.
*
* <b>Parameters:</b>
* The service for this breadcrumb builder must pass a FolderShare entity.
*
* @ingroup foldershare
*
* @see \Drupal\foldershare\Entity\FolderShare
*/
class FolderShareBreadcrumbBuilder implements BreadcrumbBuilderInterface {
use StringTranslationTrait;
/*---------------------------------------------------------------------
*
* Fields.
*
* These fields cache values from construction and dependency injection.
*
*---------------------------------------------------------------------*/
/**
* The entity storage manager, set at construction time.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $entityStorage;
/**
* The route provider, set at construction time.
*
* @var \Drupal\Core\Routing\RouteProviderInterface
*/
protected $routeProvider;
/**
* The current user account, set at construction time.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $currentUser;
/**
* The route title resolver, set at construction time.
*
* @var \Drupal\Core\Controller\TitleResolver
*/
protected $titleResolver;
/*---------------------------------------------------------------------
*
* Construct.
*
*---------------------------------------------------------------------*/
/**
* Constructs the bread crumb builder.
*
* The arguments here must match those in the service declaration in
* MODULE.services.yml.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
* The entity manager service for the FolderShare entity type.
* @param \Drupal\Core\Routing\RouteProviderInterface $routeProvider
* The route provider services.
* @param \Drupal\Core\Access\AccessManagerInterface $accessManager
* The access manager service.
* @param \Drupal\Core\Session\AccountInterface $currentUser
* The current user account.
* @param \Drupal\Core\Controller\TitleResolver $titleResolver
* The page title resolver.
*/
public function __construct(
EntityTypeManagerInterface $entityTypeManager,
RouteProviderInterface $routeProvider,
AccessManagerInterface $accessManager,
AccountInterface $currentUser,
TitleResolver $titleResolver) {
$this->entityStorage = $entityTypeManager->getStorage(FolderShare::ENTITY_TYPE_ID);
$this->currentUser = $currentUser;
$this->routeProvider = $routeProvider;
$this->titleResolver = $titleResolver;
}
/*---------------------------------------------------------------------
*
* Build.
*
*---------------------------------------------------------------------*/
/**
* {@inheritdoc}
*/
public function applies(RouteMatchInterface $routeMatch) {
//
// There are several cases where this breadcrumb builder applies:
//
// - The route names the root folder groups page, or one of the individual
// group pages.
//
// - The route names a single FolderShare entity, such as for a view
// or edit page.
//
// - The route is for a "command" form that works with a selection
// of FolderShare entities, such as for delete, rename, copy, move,
// or change owner.
//
$routeName = $routeMatch->getRouteName();
switch ($routeName) {
case Constants::ROUTE_FOLDERSHARE_COMMAND_FORM:
// The route is for a command form. Get the encoded parameter,
// which should be there for all command form routes.
$encoded = $routeMatch->getRawParameter('encoded');
return ($encoded !== NULL);
case Constants::ROUTE_FOLDERSHARE:
// The route is for an entity page. There should be a FolderShare
// entity ID.
$entity = $routeMatch->getParameter(FolderShare::ENTITY_TYPE_ID);
return ($entity instanceof FolderShareInterface);
case Constants::ROUTE_ROOT_ITEMS_PERSONAL:
case Constants::ROUTE_ROOT_ITEMS_PUBLIC:
case Constants::ROUTE_ROOT_ITEMS_ALL:
// The route is for page listing root items.
return TRUE;
default:
// The route is not recognized.
return FALSE;
}
}
/**
* {@inheritdoc}
*/
public function build(RouteMatchInterface $routeMatch) {
//
// There are three cases this breadcrumb builder needs to handle:
//
// - The route names a single FolderShare entity, such as for a view
// or edit page.
//
// - The route is for a "command" form that works with a selection
// of FolderShare entities, such as for delete, rename, copy, move,
// or change owner.
//
// - The route is for FolderShare, but there is no entity ID. This can
// be for the main root folder groups page, or for one of the group
// pages.
//
$routeName = $routeMatch->getRouteName();
switch ($routeName) {
case Constants::ROUTE_FOLDERSHARE_COMMAND_FORM:
// The route is for a command form.
return $this->buildForCommandForm($routeMatch);
case Constants::ROUTE_FOLDERSHARE:
// The route is for an entity page. There should be a FolderShare
// entity ID.
$entity = $routeMatch->getParameter(FolderShare::ENTITY_TYPE_ID);
if ($entity !== NULL) {
return $this->buildForEntityPage($routeMatch, $entity);
}
// The route is for an entity, but the entity isn't ours. This
// should not occur because the applies() method earlier should
// already have rejected such a page.
return $this->buildDefault($routeMatch);
case Constants::ROUTE_ROOT_ITEMS_PERSONAL:
case Constants::ROUTE_ROOT_ITEMS_PUBLIC:
case Constants::ROUTE_ROOT_ITEMS_ALL:
// The route is for page listing root items.
return $this->buildForRootListPage($routeMatch);
default:
// The route is not recognized. This should not occur because
// the applies() method earlier should already have rejected
// such a page.
return $this->buildDefault($routeMatch);
}
}
/**
* Builds an array of breadcrumb links when there is no entity or form.
*
* When a page is for FolderShare, but it doesn't have an entity ID or
* command form parameters, we cannot build a breadcrumb of ancestor
* links. Instead, this function returns an abbreviated breadcrumb that
* only includes a home page link and a root folder list link.
*
* @param \Drupal\Core\Routing\RouteMatchInterface $routeMatch
* The route description, including the name of the route and its
* parameters.
*
* @return \Drupal\Core\Link[]
* Returns an array of links for the breadcrumb, from the site's home
* page to the current FolderShare entity.
*/
private function buildDefault(RouteMatchInterface $routeMatch) {
//
// Cache control
// -------------
// Breadcrumbs vary per user because viewing permissions
// vary per user role and per folder.
$breadcrumb = new Breadcrumb();
$breadcrumb->addCacheableDependency($this->currentUser);
$breadcrumb->addCacheableDependency($routeMatch);
$dummyRequest = new Request();
//
// Link to home page
// -----------------
// The first link goes to the site's home page.
$links = [];
$route = $this->routeProvider->getRouteByName('<front>');
$title = $this->titleResolver->getTitle($dummyRequest, $route);
$links[] = Link::createFromRoute($title, '<front>');
$breadcrumb->setLinks($links);
return $breadcrumb;
}
/**
* Builds an array of breadcrumb links for a root list page.
*
* When a page is for a root list, we cannot build a breadcrumb of ancestor
* links. Instead, this function returns an abbreviated breadcrumb that
* only includes a home page link.
*
* @param \Drupal\Core\Routing\RouteMatchInterface $routeMatch
* The route description, including the name of the route and its
* parameters.
*
* @return \Drupal\Core\Link[]
* Returns an array of links for the breadcrumb, from the site's home
* page to the current FolderShare entity.
*/
private function buildForRootListPage(
RouteMatchInterface $routeMatch) {
//
// Cache control
// -------------
// Breadcrumbs vary per user because viewing permissions
// vary per user role and per folder.
$breadcrumb = new Breadcrumb();
$breadcrumb->addCacheableDependency($this->currentUser);
$breadcrumb->addCacheableDependency($routeMatch);
$dummyRequest = new Request();
//
// Link to home page
// -----------------
// The first link goes to the site's home page.
$links = [];
$route = $this->routeProvider->getRouteByName('<front>');
$title = $this->titleResolver->getTitle($dummyRequest, $route);
$links[] = Link::createFromRoute($title, '<front>');
$breadcrumb->setLinks($links);
return $breadcrumb;
}
/**
* Builds an array of breadcrumb links for a FolderShare entity page.
*
* FolderShare entity pages are those that view or edit a single entity
* and where the ID of the entity is a well-known argument on the URL.
*
* For these pages, this function gets the ancestors of the entity and
* returns an array of links to those ancestors. The first link in the
* returned array is, by convention, for the site's home page. The next
* link is for the canonical root folder list. The next links are for
* ancestors, from the root folder to the parent of the page's item.
* The last link in the returned array is for the page's item.
*
* @param \Drupal\Core\Routing\RouteMatchInterface $routeMatch
* The route description, including the name of the route and its
* parameters.
* @param \Drupal\foldershare\FolderShareInterface $item
* The FolderShare entity for the breadcrumbs.
* @param bool $includeItem
* When TRUE, the current FolderShare entity (whether a file or a folder)
* is included at the end of the breadcrumb. When FALSE, it is not.
*
* @return \Drupal\Core\Link[]
* Returns an array of links for the breadcrumb, from the site's home
* page to the current FolderShare entity.
*/
private function buildForEntityPage(
RouteMatchInterface $routeMatch,
FolderShareInterface $item,
bool $includeItem = FALSE) {
//
// Cache control
// -------------
// Breadcrumbs vary per user because viewing permissions
// vary per user role and per folder.
$breadcrumb = new Breadcrumb();
$breadcrumb->addCacheableDependency($this->currentUser);
$breadcrumb->addCacheableDependency($routeMatch);
$currentRequest = \Drupal::request();
$dummyRequest = new Request();
//
// Link to home page.
// ------------------
// Link to the site's home page.
$links = [];
$route = $this->routeProvider->getRouteByName('<front>');
$title = $this->titleResolver->getTitle($dummyRequest, $route);
$links[] = Link::createFromRoute($title, '<front>');
//
// Link to root list page.
// -----------------------
// Link to one of the root list pages.
//
// Several cases:
// - The root is owned by the current user.
// - If the current user is anonymous, use PUBLIC_ROOT_LIST.
// - Otherwise use USER_ROOT_LIST.
//
// - The root is owned by someone else.
// - If the current user is an admin, use ALL_ROOT_LIST.
// - If the root is shared with anonymous, use PUBLIC_ROOT_LIST.
// - Otherwise use USER_ROOT_LIST.
$rootItem = $item->getRootItem();
$rootOwner = $rootItem->getOwner();
if ((int) $this->currentUser->id() === (int) $rootOwner->id()) {
if ($this->currentUser->isAnonymous() === TRUE) {
$routeName = Constants::ROUTE_ROOT_ITEMS_PUBLIC;
}
else {
$routeName = Constants::ROUTE_ROOT_ITEMS_PERSONAL;
}
}
elseif ($this->currentUser->hasPermission(Constants::ADMINISTER_PERMISSION) === TRUE) {
$routeName = Constants::ROUTE_ROOT_ITEMS_ALL;
}
elseif ($this->currentUser->isAnonymous() === TRUE) {
$routeName = Constants::ROUTE_ROOT_ITEMS_PUBLIC;
}
elseif ($rootItem->isAccessPublic() === TRUE) {
$routeName = Constants::ROUTE_ROOT_ITEMS_PUBLIC;
}
else {
$routeName = Constants::ROUTE_ROOT_ITEMS_PERSONAL;
}
$route = $this->routeProvider->getRouteByName($routeName);
$title = $this->titleResolver->getTitle($dummyRequest, $route);
$links[] = Link::createFromRoute($title, $routeName);
//
// Link to ancestors
// -----------------
// When VIEWing a file or folder, the breadcrumb ends with the
// parent folder.
//
// When EDITing a file or folder, the breadcrumb ends with the action
// after the item name.
//
// Distinguishing between these is a little awkward. VIEW routes
// do not have a fixed title because the title comes
// via a callback to get the item's title. For EDIT routes,
// there is a fixed title.
//
// Get ancestors of this item. The list does not include this item.
// The first entry in the list is the root.
$ancestors = $item->findAncestorFolders();
// Loop through the ancestors, starting at the root. For each one,
// add a link to the item's page.
$routeName = Constants::ROUTE_FOLDERSHARE;
$routeParam = Constants::ROUTE_FOLDERSHARE_ID;
foreach ($ancestors as $ancestor) {
// Breadcrumb cacheing also depends on this ancestor.
$breadcrumb->addCacheableDependency($ancestor);
// The BreadcrumbBuilderInterface that this class is implementing,
// and the build() method in particular, is required to return a
// Breadcrumb object. And that object is strictly a list of Link
// objects.
//
// This is a problem here because an ancestor might not provide
// 'view' access to this account. If it does not, we'd like to
// return straight text instead of a link, since clicking on
// the link would get an error anyway.
//
// Unfortunately, there is no way to do this. We must return
// a Link, regardless of viewing permissions.
//
// No need to HTML escape the folder name here. This is done
// automatically by Link.
$links[] = Link::createFromRoute(
$ancestor->getName(),
$routeName,
[$routeParam => (int) $ancestor->id()]);
}
// The last item in the link array is either:
// - The entity's page if the current route is for editing it.
// - A parent entity when a command is operating on its children.
$route = $routeMatch->getRouteObject();
$addedLast = FALSE;
if ($route !== NULL) {
$breadcrumb->addCacheableDependency($route);
$title = $this->titleResolver->getTitle($currentRequest, $route);
if (empty($title) === FALSE) {
// Yes, the route has a title. This is for an edit page.
// Add a link to the view page for the entity.
$links[] = Link::createFromRoute(
$item->getName(),
$routeName,
[$routeParam => $item->id()]);
$addedLast = TRUE;
}
}
if ($includeItem === TRUE && $addedLast === FALSE) {
// This is a command and we're showing its edit/confirmation form.
// Add a link to the view page for the entity.
$links[] = Link::createFromRoute(
$item->getName(),
$routeName,
[$routeParam => $item->id()]);
}
// Breadcrumbs vary per item.
$breadcrumb->addCacheableDependency($item);
$breadcrumb->setLinks($links);
return $breadcrumb;
}
/**
* Builds an array of breadcrumb links for a FolderShare command form.
*
* FolderShare command forms are a response from a command plugin.
* Such plugins are invoked from the GUI menu and they may have multiple
* values, including:
* - an optional parent folder.
* - an optional destination folder (such as for move and copy).
* - an optional selection.
*
* The selection may have any number of FolderShare entity IDs, with any
* mix of kinds. There may be no selection. And there may be no parent
* or destination.
*
* All of these parameters are encoded as a single cryptic argument on
* the command form URL - because they are too much and too complex to
* encode with ? arguments, and because it would encourage users to fiddle
* with them. This means they all come through as a single "encoded"
* argument on the route.
*
* This function returns breadcrumbs based upon the decoded argument.
*
* @param \Drupal\Core\Routing\RouteMatchInterface $routeMatch
* The route description, including the name of the route and its
* parameters.
*
* @return \Drupal\Core\Link[]
* Returns an array of links for the breadcrumb, from the site's home
* page to the current FolderShare entity.
*/
private function buildForCommandForm(RouteMatchInterface $routeMatch) {
//
// Command parameters
// ------------------
// Get the encoded parameters and decode them.
$encoded = $routeMatch->getRawParameter('encoded');
$parameters = (array) json_decode(base64_decode($encoded), TRUE);
// If there are no parameters (which should not be possible), then
// we can only build a generic abbreviated breadcrumb.
if (empty($parameters) === TRUE ||
isset($parameters['configuration']) === FALSE) {
return $this->buildDefault($routeMatch);
}
$configuration = (array) $parameters['configuration'];
// If there is a selection, and the selection has just one entity ID,
// then we can build a normal breadcrumb using that entity ID.
if (isset($configuration['selectionIds']) === TRUE) {
$selectionIds = $configuration['selectionIds'];
if (count($selectionIds) === 1) {
$itemId = $selectionIds[0];
$item = FolderShare::load($itemId);
if ($item === NULL) {
// The selection ID is bad. Revert to the default breadcrumb.
return $this->buildDefault($routeMatch);
}
// Use the item to build an entity page breadcrumb.
return $this->buildForEntityPage($routeMatch, $item, TRUE);
}
// Otherwise the selection is empty or it has more than one items.
}
// If there is a parent, use it.
if (isset($configuration['parentId']) === TRUE) {
$parentId = $configuration['parentId'];
$item = FolderShare::load($parentId);
if ($item === NULL) {
// The parent ID is bad. Revert to the default breadcrumb.
return $this->buildDefault($routeMatch);
}
// Use the item to build an entity page breadcrumb.
return $this->buildForEntityPage($routeMatch, $item, TRUE);
}
// There is no selection and no parent. It doesn't make sense to use
// the destination ID, so fall back to the default breadcrumb.
return $this->buildDefault($routeMatch);
}
}
