View source
<?php
namespace Drupal\menu_entity_index;
use Drupal\Core\Config\ConfigFactory;
use Drupal\Core\Database\Connection;
use Drupal\Core\DependencyInjection\DependencySerializationTrait;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\ContentEntityType;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityRepositoryInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Menu\MenuLinkInterface;
use Drupal\Core\Menu\MenuLinkManagerInterface;
use Drupal\Core\Messenger\MessengerTrait;
use Drupal\Core\ParamConverter\ParamNotConvertedException;
use Drupal\Core\Path\PathValidatorInterface;
use Drupal\Core\PathProcessor\InboundPathProcessorInterface;
use Drupal\Core\Routing\RouteMatch;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\TypedData\TranslatableInterface;
use Drupal\Core\Url;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\Routing\Exception\MethodNotAllowedException;
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
use Symfony\Component\Routing\Matcher\RequestMatcherInterface;
class Tracker implements TrackerInterface {
use DependencySerializationTrait;
use MessengerTrait;
use StringTranslationTrait;
protected $configFactory;
protected $config;
protected $database;
protected $entityRepository;
protected $entityTypeManager;
protected $menuLinkManager;
protected $pathProcessor;
protected $requestStack;
protected $router;
protected $pathValidator;
protected $viewsData = NULL;
public function __construct(ConfigFactory $config_factory, Connection $connection, EntityRepositoryInterface $entity_repository, EntityTypeManagerInterface $entity_type_manager, MenuLinkManagerInterface $menu_link_manager, InboundPathProcessorInterface $path_processor, RequestStack $request_stack, RequestMatcherInterface $router, PathValidatorInterface $path_validator) {
$this->configFactory = $config_factory;
$config = $this->configFactory
->get('menu_entity_index.configuration');
if ($config
->isNew()) {
$this
->messenger()
->addError($this
->t('The @service service has not been <a href=":url">configured</a> yet.', [
'@service' => $this
->t('Menu Entity Index Tracker'),
':url' => Url::fromRoute('menu_entity_index.configure')
->toString(),
]));
}
$this->config = $config;
$this->database = $connection;
$this->entityRepository = $entity_repository;
$this->entityTypeManager = $entity_type_manager;
$this->menuLinkManager = $menu_link_manager;
$this->pathProcessor = $path_processor;
$this->requestStack = $request_stack;
$this->router = $router;
$this->pathValidator = $path_validator;
}
public function setViewsData($views_data) {
$this->viewsData = $views_data;
}
public function getAvailableMenus() {
$options = [];
$eids = $this->entityTypeManager
->getStorage('menu', 'AND')
->getQuery()
->execute();
$menus = $this->entityTypeManager
->getStorage('menu')
->loadMultiple($eids);
foreach ($menus as $name => $menu) {
$options[$name] = $menu
->label();
}
asort($options);
return $options;
}
public function getAvailableEntityTypes() {
$options = [];
$types = $this->entityTypeManager
->getDefinitions();
foreach ($types as $type_id => $type) {
if ($type instanceof ContentEntityType) {
$options[$type_id] = $type
->getLabel();
}
}
if (isset($options['menu_link_content'])) {
unset($options['menu_link_content']);
}
return $options;
}
public function getTrackedMenus() {
if ($this->config
->get('all_menus')) {
return array_keys($this
->getAvailableMenus());
}
$menus = $this->config
->get('menus');
return array_values((array) $menus);
}
public function getConfiguration() {
return $this->configFactory
->get('menu_entity_index.configuration');
}
public function setConfiguration(array $form_values = [], $force_rebuild = FALSE) {
$entity_type_ids = !empty($form_values['entity_types']) ? array_filter($form_values['entity_types']) : [];
$force = $force_rebuild || $this
->setTrackedEntityTypes($entity_type_ids);
if ($form_values['all_menus']) {
$this
->setTrackAllMenus($force);
}
elseif (!empty($form_values['menus'])) {
$menus = array_filter($form_values['menus']);
$this
->setTrackedMenus($menus, $force);
}
if (is_object($this->viewsData)) {
$this->viewsData
->clear();
}
}
protected function setTrackAllMenus($force = FALSE) {
$old_value = $this->config
->get('all_menus');
$this->configFactory
->getEditable('menu_entity_index.configuration')
->set('all_menus', TRUE)
->set('menus', [])
->save();
if (!$old_value || $force) {
$this
->untrackMenus($this
->getTrackedMenus());
$this
->trackMenus($this
->getTrackedMenus());
}
}
protected function setTrackedMenus(array $menus = [], $force = FALSE) {
$old_values = $this
->getTrackedMenus();
$this->configFactory
->getEditable('menu_entity_index.configuration')
->set('all_menus', FALSE)
->set('menus', $menus)
->save();
$this->config = $this->configFactory
->get('menu_entity_index.configuration');
if ($force) {
$this
->untrackMenus(array_keys($this
->getAvailableMenus()));
$this
->trackMenus($this
->getTrackedMenus());
}
else {
$new_menus = (array) array_diff($menus, $old_values);
$this
->untrackMenus((array) array_diff($old_values, $menus));
$this
->trackMenus($new_menus);
}
}
public function getTrackedEntityTypes() {
$entity_type_ids = $this->config
->get('entity_types');
return array_values((array) $entity_type_ids);
}
public function isTrackedEntityType(EntityTypeInterface $type) {
return $type instanceof ContentEntityType && in_array($type
->id(), $this
->getTrackedEntityTypes());
}
protected function setTrackedEntityTypes(array $entity_type_ids = []) {
$values = $this->config
->get('entity_types');
$this->configFactory
->getEditable('menu_entity_index.configuration')
->set('entity_types', $entity_type_ids)
->save();
$this->config = $this->configFactory
->get('menu_entity_index.configuration');
$this
->untrackEntityTypeIds((array) array_diff($values, $entity_type_ids));
$new_types = (array) array_diff($entity_type_ids, $values);
return count($new_types) > 0;
}
protected function trackMenus(array $menus = []) {
if (empty($menus)) {
return;
}
$operations = [];
foreach ($menus as $menu) {
$arguments = [
[
$menu,
],
];
$operations[] = [
'menu_entity_index_track_batch',
$arguments,
];
}
batch_set([
'title' => $this
->t('Scanning menu links'),
'operations' => $operations,
'file' => drupal_get_path('module', 'menu_entity_index') . '/menu_entity_index.batch.inc',
]);
}
protected function untrackMenus(array $menu_names = []) {
if (empty($menu_names)) {
return;
}
$this->database
->delete('menu_entity_index')
->condition('menu_name', (array) $menu_names, 'IN')
->execute();
}
protected function untrackEntityTypeIds(array $entity_type_ids = []) {
if (empty($entity_type_ids)) {
return;
}
$this->database
->delete('menu_entity_index')
->condition('target_type', (array) $entity_type_ids, 'IN')
->execute();
}
public function deleteEntity(EntityInterface $entity) {
if ($entity
->getEntityTypeId() !== 'menu_link_content') {
return;
}
if (!in_array($entity
->getMenuName(), $this
->getTrackedMenus())) {
return;
}
$query = $this->database
->delete('menu_entity_index')
->condition('entity_type', $entity
->getEntityTypeId())
->condition('entity_id', $entity
->id());
if ($entity
->getEntityType()
->hasKey('langcode')) {
$query
->condition('langcode', $entity
->language()
->getId());
}
$query
->execute();
}
protected function isEntityTranslatable(EntityInterface $entity) {
return $entity instanceof TranslatableInterface && $entity
->isTranslatable();
}
protected function getRequestForUrl($url) {
$current_request = $this->requestStack
->getCurrentRequest();
if (strpos($url, $current_request
->getSchemeAndHttpHost()) === 0) {
$url = substr($url, strlen($current_request
->getSchemeAndHttpHost()));
}
if (!empty($current_request
->getBaseUrl()) && strpos($url, $current_request
->getBaseUrl()) === 0) {
$url = substr($url, strlen($current_request
->getBaseUrl()));
}
$validated_url = $this->pathValidator
->getUrlIfValidWithoutAccessCheck($url);
if (!$validated_url || $validated_url
->isExternal()) {
return NULL;
}
$request = Request::create($url);
$request->headers
->set('Accept', 'text/html');
$processed = $this->pathProcessor
->processInbound($url, $request);
if (empty($processed)) {
return NULL;
}
try {
$request->attributes
->add($this->router
->matchRequest($request));
return $request;
} catch (ParamNotConvertedException $e) {
return NULL;
} catch (ResourceNotFoundException $e) {
return NULL;
} catch (MethodNotAllowedException $e) {
return NULL;
} catch (AccessDeniedHttpException $e) {
return NULL;
}
}
public function updateEntity(EntityInterface $entity) {
if ($entity
->getEntityTypeId() !== 'menu_link_content') {
return;
}
if (!in_array($entity
->getMenuName(), $this
->getTrackedMenus())) {
return;
}
if (!$entity
->isNew()) {
$this
->deleteEntity($entity);
}
$targets = [];
try {
$url = $entity
->getUrlObject();
if (!$url
->isExternal()) {
$url
->setOption('absolute', TRUE);
}
$url = $url
->toString();
} catch (\InvalidArgumentException $e) {
return;
}
$route_request = $this
->getRequestForUrl($url);
if ($route_request) {
$route_match = RouteMatch::createFromRequest($route_request);
$parameters = $route_match
->getParameters();
foreach ($parameters as $parameter) {
if ($parameter instanceof ContentEntityInterface) {
if (in_array($parameter
->getEntityTypeId(), $this
->getTrackedEntityTypes())) {
if (!$this
->isEntityTranslatable($parameter)) {
$targets[] = $parameter;
}
else {
if ($this
->isEntityTranslatable($entity)) {
$targets[] = $parameter
->hasTranslation($entity
->language()
->getId()) ? $parameter
->getTranslation($entity
->language()
->getId()) : $parameter;
}
else {
foreach ($parameter
->getTranslationLanguages() as $language) {
if ($entity
->language()
->getId() === $language
->getId()) {
$targets[] = $parameter
->getTranslation($entity
->language()
->getId());
}
}
}
}
}
}
}
}
if (count($targets) > 0) {
$this
->addEntityTargets($entity, $targets);
}
}
protected function addEntityTargets(EntityInterface $entity, array $targets = []) {
if (empty($targets)) {
return FALSE;
}
$parent_link = NULL;
$parent_entity = NULL;
$parent_id = $entity
->getParentId();
if (!empty($parent_id) && $this->menuLinkManager
->hasDefinition($parent_id)) {
$parent_link = $this->menuLinkManager
->createInstance($parent_id);
if ($parent_link
->getBaseId() == 'menu_link_content') {
$parent_entity = $this->entityRepository
->loadEntityByUuid($parent_link
->getBaseId(), $parent_link
->getDerivativeId());
}
elseif ($parent_link
->getBaseId() == 'views_view') {
$parent_entity = $parent_link
->loadView();
}
}
$menu_link = $this->menuLinkManager
->createInstance($entity
->getPluginId());
$host_values = [
'menu_name' => $entity
->getMenuName(),
'level' => $menu_link ? $this
->getMenuLinkLevel($menu_link) : 0,
'entity_type' => $entity
->getEntityTypeId(),
'entity_subtype' => $entity
->bundle(),
'entity_id' => $entity
->id(),
'entity_uuid' => $entity
->uuid(),
'parent_type' => $parent_link ? $parent_link
->getBaseId() : '',
'parent_id' => $parent_entity ? $parent_entity
->id() : NULL,
'parent_uuid' => $parent_link ? (string) $parent_link
->getDerivativeId() : '',
'langcode' => $entity
->getEntityType()
->hasKey('langcode') ? $entity
->language()
->getId() : '',
];
$query = $this->database
->insert('menu_entity_index')
->fields([
'menu_name',
'level',
'entity_type',
'entity_subtype',
'entity_id',
'entity_uuid',
'parent_type',
'parent_id',
'parent_uuid',
'langcode',
'target_type',
'target_subtype',
'target_id',
'target_uuid',
'target_langcode',
]);
foreach ($targets as $target_entity) {
$values = [
'target_type' => $target_entity
->getEntityTypeId(),
'target_subtype' => $target_entity
->bundle(),
'target_id' => $target_entity
->id(),
'target_uuid' => $target_entity
->uuid(),
'target_langcode' => $target_entity
->getEntityType()
->hasKey('langcode') ? $target_entity
->language()
->getId() : '',
];
$query
->values($values + $host_values);
}
return $query
->execute();
}
protected function getMenuLinkLevel(MenuLinkInterface $menu_link, $level = 0) {
$parent_id = $menu_link
->getParent();
if (!empty($parent_id) && $this->menuLinkManager
->hasDefinition($parent_id)) {
$parent_link = $this->menuLinkManager
->createInstance($parent_id);
if ($parent_link) {
return $this
->getMenuLinkLevel($parent_link, $level + 1);
}
return $level;
}
else {
return $level;
}
}
public function getHostData(EntityInterface $entity) {
$data = [];
$type = $entity
->getEntityTypeId();
if (in_array($entity
->getEntityTypeId(), $this
->getTrackedEntityTypes())) {
$id = $entity
->id();
$result = $this->database
->select('menu_entity_index')
->fields('menu_entity_index', [
'entity_type',
'entity_id',
'menu_name',
'level',
'langcode',
])
->condition('target_type', $type)
->condition('target_id', $id)
->orderBy('menu_name', 'ASC')
->orderBy('level', 'ASC')
->execute();
$menus = [];
foreach ($result as $row) {
if (!isset($menus[$row->menu_name])) {
$entity = $this->entityTypeManager
->getStorage('menu')
->load($row->menu_name);
$menus[$row->menu_name] = $entity
->label();
}
$entity = $this->entityTypeManager
->getStorage($row->entity_type)
->load($row->entity_id);
if ($entity) {
$data[] = [
'menu_name' => $menus[$row->menu_name],
'level' => $row->level,
'label' => $entity
->getTitle(),
'link' => $entity
->access('view') ? $entity
->toUrl() : '',
'language' => $entity
->language()
->getName(),
];
}
}
}
return $data;
}
}