diff --git a/src/Plugin/Block/UwCblLinks.php b/src/Plugin/Block/UwCblLinks.php index 9d5631e819a1c7348e00527cbb185d132b32760c..cd16c5a5a9e987004fc2fd1221609670bdc1173c 100644 --- a/src/Plugin/Block/UwCblLinks.php +++ b/src/Plugin/Block/UwCblLinks.php @@ -8,6 +8,7 @@ use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Path\PathValidatorInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; +use Drupal\Core\Url; use Drupal\media\Entity\Media; use Drupal\path_alias\AliasManager; use Drupal\uw_cfg_common\Service\UWService; @@ -266,13 +267,8 @@ class UwCblLinks extends BlockBase implements ContainerFactoryPluginInterface { // If this is not an external link, get the friendly path // to the page. If it is external, just use the value. elseif (!UrlHelper::isExternal($value)) { - // Get the base path of the site, this is required for Pantheon. - $base_path = $this->requestStack - ->getCurrentRequest() - ?->getBasePath(); - // Add the url to the links array. - $links['items'][$count][$index] = $this->prepareLink($value, $base_path); + $links['items'][$count][$index] = $this->cleanUpLink($value, TRUE, FALSE); } else { @@ -284,10 +280,10 @@ class UwCblLinks extends BlockBase implements ContainerFactoryPluginInterface { // See if link title needs to be modified. if (empty($item['title'])) { - $links['items'][$count]['title'] = $this->getTitleOverride($item['uri'], $base_path); + $links['items'][$count][$index] = $this->getTitleOverride($item['uri']); } else { - $links['items'][$count]['title'] = $value; + $links['items'][$count][$index] = $value; } } else { @@ -312,11 +308,6 @@ 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'; @@ -472,7 +463,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->cleanUpLink($links['items'][$i]['uri'] ?? '', $base_path); + $value_uri = $this->cleanUpLink($links['items'][$i]['uri'] ?? ''); if (isset($links['items'][$i]['uri'])) { $link = $this->t('Link (@uri)', ['@uri' => $value_uri]); @@ -696,7 +687,7 @@ class UwCblLinks extends BlockBase implements ContainerFactoryPluginInterface { // set the form error. if ( $value !== '' && - !preg_match('/^(http(s):\/\/.)/', $value) && + !preg_match('/^(https?:\/\/.)/i', $value) && !preg_match('/^[\/#]/', $value) ) { @@ -784,6 +775,9 @@ class UwCblLinks extends BlockBase implements ContainerFactoryPluginInterface { ) { $links['items'][$count][$key] = NULL; } + elseif ($key === 'uri') { + $links['items'][$count][$key] = $this->cleanUpLink($item['link_info'][$key]); + } else { $links['items'][$count][$key] = $item['link_info'][$key]; } @@ -928,17 +922,24 @@ class UwCblLinks extends BlockBase implements ContainerFactoryPluginInterface { * * @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. + * @param bool $return_alias + * Should alias be returned instead of an internal url. + * @param bool $prefix_base_path + * Should base_path be concateneted before url. * * @return string * The cleaned link with specified prefixes removed. */ - private function cleanUpLink(string $raw_link, ?string $base_path = NULL): string { + private function cleanUpLink(string $raw_link, bool $return_alias = FALSE, bool $prefix_base_path = TRUE): string { if (empty($raw_link)) { return $raw_link; } + // Get the base path of the site, this is required for Pantheon. + $base_path = $this->requestStack + ->getCurrentRequest() + ?->getBasePath(); + // Function to modify its own variable, leaving function argument unchanged. $link = $raw_link; @@ -949,17 +950,8 @@ class UwCblLinks extends BlockBase implements ContainerFactoryPluginInterface { $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. + // In some cases, the external link is prefixed with a leading slash. This + // removes that leading slash. $updated = preg_replace(self::LEADING_SLASH_REPLACE_REGEX, '$1', $link); // In case match is not found, unchanged value is returned. @@ -968,38 +960,40 @@ class UwCblLinks extends BlockBase implements ContainerFactoryPluginInterface { $link = $updated; } - return $link; - } + $path = $this->getInternalUrlIfValid($link, $base_path); - /** - * Prepares and sanitizes a link by removing specific base path prefixes. - * - * This method removes the base path and 'internal:/' prefix from the - * provided link to ensure it is in the desired format. - * - * @param string $link - * The link to be sanitized. - * @param string|null $base_path - * The base path to be removed if the link starts with it. - * - * @return string - * The sanitized link without the specified prefixes. - */ - private function prepareLink(string $link, ?string $base_path = NULL): string { + // If node alias is used, modify it so a canonical link is used. + if ($path && !$path->isExternal()) { + if ($return_alias) { + $canonical = $path->toString(); + } + else { + $canonical = $path->getInternalPath(); - if (empty($link)) { - return $link; - } + // Detects if there is a fragment or query string. + $fragment_query = preg_replace('/^[^#?]+/', '', $path->toString()); - $url = $this->cleanUpLink($link, $base_path); + // Need to check if fragment/query starts with # or ?, to avoid + // a scenario where the link is returned unchanged. + if (!empty($fragment_query) && (str_starts_with($fragment_query, '#') || str_starts_with($fragment_query, '?'))) { + $canonical .= $fragment_query; + } + } - $path = $this->pathValidator->getUrlIfValidWithoutAccessCheck($url); + if (!str_starts_with($canonical, '/')) { + $canonical = '/' . $canonical; + } - if ($path && !$path->isExternal()) { - return $path->toString(); + if ($base_path && $prefix_base_path) { + $canonical = $base_path . $canonical; + } + + if ($link !== $canonical) { + $link = $canonical; + } } - return $url; + return $link; } /** @@ -1011,17 +1005,15 @@ class UwCblLinks extends BlockBase implements ContainerFactoryPluginInterface { * * @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 { + private function getTitleOverride(string $url): string { $title = ""; - $link = $this->cleanUpLink($url, $base_path); + $link = $this->cleanUpLink($url, prefix_base_path: FALSE); /** @var \Drupal\Core\Url|bool $route_check */ $path = $this->pathValidator->getUrlIfValidWithoutAccessCheck($link); @@ -1036,8 +1028,10 @@ class UwCblLinks extends BlockBase implements ContainerFactoryPluginInterface { ->load($params['view_id']); if ($view) { - $view->getDisplay($params['display_id']); - $title = $view->label(); + $display = $view->getDisplay($params['display_id']); + + // Get display id title, if that fails, use view title. + $title = $display['display_options']['title'] ?? $view->label(); } } // This part should take care of nodes, taxonomy terms and webforms. @@ -1060,4 +1054,34 @@ class UwCblLinks extends BlockBase implements ContainerFactoryPluginInterface { return $title ?? ""; } + /** + * Validates and retrieves an internal URL if applicable. + * + * Checks if the provided URL starts with the specified base path + * and attempts to validate it as an internal path without access checks. + * + * @param string $url + * The URL to be validated. + * @param string $base_path + * The base path to check against. + * + * @return \Drupal\Core\Url|null + * The valid internal URL object if the URL is valid and matches the + * base path, or NULL otherwise. + */ + private function getInternalUrlIfValid(string $url, string $base_path): ?Url { + $link = $url; + + // Remove base path, this will be used to validate url. + if ($base_path && str_starts_with(strtolower($link), $base_path . '/')) { + $link = substr_replace($link, '', 0, strlen($base_path)); + } + + if ($path = $this->pathValidator->getUrlIfValidWithoutAccessCheck($link)) { + return $path; + } + + return NULL; + } + }