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