From bce9cc6bbc24e6b6b72c0af0ee91d2c6222c564a Mon Sep 17 00:00:00 2001 From: Igor Biki <ibiki@uwaterloo.ca> Date: Wed, 5 Feb 2025 14:03:30 -0500 Subject: [PATCH] Feature/istwcms 7266 ibiki pantheon related links --- src/Plugin/Block/UwCblLinks.php | 181 +++++++++++++++++++++++++------- 1 file changed, 141 insertions(+), 40 deletions(-) diff --git a/src/Plugin/Block/UwCblLinks.php b/src/Plugin/Block/UwCblLinks.php index 174e36da..9d5631e8 100644 --- a/src/Plugin/Block/UwCblLinks.php +++ b/src/Plugin/Block/UwCblLinks.php @@ -6,6 +6,7 @@ use Drupal\Component\Utility\UrlHelper; use Drupal\Core\Block\BlockBase; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Path\PathValidatorInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\media\Entity\Media; use Drupal\path_alias\AliasManager; @@ -23,6 +24,11 @@ use Symfony\Component\HttpFoundation\RequestStack; */ class UwCblLinks extends BlockBase implements ContainerFactoryPluginInterface { + /** + * Regular expression to detect leading slash for external links. + */ + public const LEADING_SLASH_REPLACE_REGEX = '/^\/(https?:\/\/)/i'; + /** * The entity type manager. * @@ -51,6 +57,13 @@ class UwCblLinks extends BlockBase implements ContainerFactoryPluginInterface { */ protected $requestStack; + /** + * Path validator from the core. + * + * @var \Drupal\Core\Path\PathValidatorInterface + */ + protected $pathValidator; + /** * Constructs a BlockComponentRenderArray object. * @@ -68,6 +81,8 @@ class UwCblLinks extends BlockBase implements ContainerFactoryPluginInterface { * The path alias. * @param \Symfony\Component\HttpFoundation\RequestStack $requestStack * The request stack. + * @param \Drupal\Core\Path\PathValidatorInterface $pathValidator + * Path validator from core. */ public function __construct( array $configuration, @@ -76,7 +91,8 @@ class UwCblLinks extends BlockBase implements ContainerFactoryPluginInterface { EntityTypeManagerInterface $entity_type_manager, UwService $uwService, AliasManager $pathAliasManager, - RequestStack $requestStack + RequestStack $requestStack, + PathValidatorInterface $pathValidator, ) { parent::__construct($configuration, $plugin_id, $plugin_definition); @@ -84,6 +100,7 @@ class UwCblLinks extends BlockBase implements ContainerFactoryPluginInterface { $this->uwService = $uwService; $this->pathAliasManager = $pathAliasManager; $this->requestStack = $requestStack; + $this->pathValidator = $pathValidator; } /** @@ -93,7 +110,7 @@ class UwCblLinks extends BlockBase implements ContainerFactoryPluginInterface { ContainerInterface $container, array $configuration, $plugin_id, - $plugin_definition + $plugin_definition, ) { return new static( @@ -103,7 +120,8 @@ class UwCblLinks extends BlockBase implements ContainerFactoryPluginInterface { $container->get('entity_type.manager'), $container->get('uw_cfg_common.uw_service'), $container->get('path_alias.manager'), - $container->get('request_stack') + $container->get('request_stack'), + $container->get('path.validator'), ); } @@ -251,18 +269,10 @@ class UwCblLinks extends BlockBase implements ContainerFactoryPluginInterface { // Get the base path of the site, this is required for Pantheon. $base_path = $this->requestStack ->getCurrentRequest() - ->getBasePath(); - - $value = $this->prepareLink($value, $base_path); - - $pathauto = $this->pathAliasManager - ->getAliasByPath($value); - - // Get the correct url. - $url = $base_path . $pathauto; + ?->getBasePath(); // Add the url to the links array. - $links['items'][$count][$index] = $url; + $links['items'][$count][$index] = $this->prepareLink($value, $base_path); } else { @@ -273,21 +283,8 @@ class UwCblLinks extends BlockBase implements ContainerFactoryPluginInterface { elseif ($index === 'title') { // See if link title needs to be modified. if (empty($item['title'])) { - $node_path = $this->pathAliasManager->getPathByAlias($url); - - if (preg_match('/^\/node\/(\d+)$/', $node_path, $matches)) { - $node_id = $matches[1]; - if ($node_id) { - $node_title = $this->entityTypeManager->getStorage('node') - ->load($node_id) - ?->getTitle(); - - if ($node_title) { - $links['items'][$count]['title'] = $node_title; - } - } - } + $links['items'][$count]['title'] = $this->getTitleOverride($item['uri'], $base_path); } else { $links['items'][$count]['title'] = $value; @@ -315,6 +312,11 @@ class UwCblLinks extends BlockBase implements ContainerFactoryPluginInterface { // Array to store the links. $links = []; + // Base path if exists. + $base_path = $this->requestStack + ->getCurrentRequest() + ?->getBasePath(); + // Attach the js library. $form['#attached']['library'][] = 'uw_custom_blocks/uw_cbl_links'; @@ -470,7 +472,7 @@ class UwCblLinks extends BlockBase implements ContainerFactoryPluginInterface { // If there is a link already set, setup the details to // have the link in the title so when it is closed, // we can see what the actual is. - $value_uri = $this->prepareLink($links['items'][$i]['uri'] ?? ''); + $value_uri = $this->cleanUpLink($links['items'][$i]['uri'] ?? '', $base_path); if (isset($links['items'][$i]['uri'])) { $link = $this->t('Link (@uri)', ['@uri' => $value_uri]); @@ -483,7 +485,7 @@ class UwCblLinks extends BlockBase implements ContainerFactoryPluginInterface { $settings['link_info'] = [ '#type' => 'details', '#title' => $link, - '#open' => $i == $num_of_rows - 1 ? TRUE : FALSE, + '#open' => $i === $num_of_rows - 1, '#prefix' => '<div class="uw-links__link-info">', '#suffix' => '</div>', ]; @@ -921,6 +923,54 @@ class UwCblLinks extends BlockBase implements ContainerFactoryPluginInterface { ]; } + /** + * Cleans up links by removing 'internal:' and base path. + * + * @param string $raw_link + * The original link that needs to be cleaned up. + * @param string|null $base_path + * The base path to remove from the link if it exists, default NULL. + * + * @return string + * The cleaned link with specified prefixes removed. + */ + private function cleanUpLink(string $raw_link, ?string $base_path = NULL): string { + if (empty($raw_link)) { + return $raw_link; + } + + // Function to modify its own variable, leaving function argument unchanged. + $link = $raw_link; + + // Remove internal: from the link. This has to go first, before base_path + // check. Links are formatted with internal followed by base_path, if it + // exists. + if (str_starts_with($link, 'internal:')) { + $link = substr_replace($link, '', 0, strlen('internal:')); + } + + // If a link starts with base_path, remove it, but only when base_path + // is not NULL. And make sure the resulting link starts with slash. + if ($base_path && str_starts_with($link, $base_path)) { + $link = substr_replace($link, '', 0, strlen($base_path)); + + if (!str_starts_with($link, '/')) { + $link = '/' . $link; + } + } + + // If an external link is prefix with leading slash. + $updated = preg_replace(self::LEADING_SLASH_REPLACE_REGEX, '$1', $link); + + // In case match is not found, unchanged value is returned. + // Null is returned in case of an error. + if (is_string($updated) && $link !== $updated) { + $link = $updated; + } + + return $link; + } + /** * Prepares and sanitizes a link by removing specific base path prefixes. * @@ -941,22 +991,73 @@ class UwCblLinks extends BlockBase implements ContainerFactoryPluginInterface { return $link; } - // If a link starts with base_path, remove it. - if (str_starts_with($link, $base_path . '/')) { - $link = substr_replace($link, '', 0, strlen($base_path)); - } + $url = $this->cleanUpLink($link, $base_path); + + $path = $this->pathValidator->getUrlIfValidWithoutAccessCheck($url); - // Remove internal:/ from the link. - if (str_starts_with($link, 'internal:/')) { - $link = substr_replace($link, '', 0, strlen('internal:/')); + if ($path && !$path->isExternal()) { + return $path->toString(); } - // Prepend leading slash if not found. - if (!str_starts_with($link, '/')) { - $link = '/' . $link; + return $url; + } + + /** + * Retrieves an overridden title based on the given URL. + * + * This method checks if the URL corresponds to routed entity, + * such as a node, view, taxonomy term, or user. If found, it retrieves + * the title or name of the entity as the overridden title. + * + * @param string $url + * The URL to validate and extract the title from. + * @param string|null $base_path + * Base path. + * + * @return string + * The overridden title is if available, or an empty string + * if no title can be determined. + */ + private function getTitleOverride(string $url, ?string $base_path = NULL): string { + $title = ""; + + $link = $this->cleanUpLink($url, $base_path); + + /** @var \Drupal\Core\Url|bool $route_check */ + $path = $this->pathValidator->getUrlIfValidWithoutAccessCheck($link); + if ($path && $path->isRouted()) { + + $route = $path->getRouteName(); + $params = $path->getRouteParameters(); + + if (str_starts_with($route, 'view')) { + /** @var \Drupal\views\Entity\View $view */ + $view = $this->entityTypeManager->getStorage('view') + ->load($params['view_id']); + + if ($view) { + $view->getDisplay($params['display_id']); + $title = $view->label(); + } + } + // This part should take care of nodes, taxonomy terms and webforms. + elseif ($route === 'entity.node.canonical') { + $route_parts = explode('.', $route); + $entity_type = $route_parts[1]; + if (isset($params[$entity_type])) { + $entity_id = $params[$entity_type]; + + $title = $this->entityTypeManager->getStorage($entity_type) + ?->load($entity_id) + ?->label(); + } + } } - return $link; + // In case there was a NULL pointer exception when loading the entity + // title will be NULL, and the function must return string. + // Returning an empty string. + return $title ?? ""; } } -- GitLab