Civi::url() - Simplify/normalize "fragment" and "relative" handling. Fix some edge...
authorTim Otten <totten@civicrm.org>
Tue, 25 Jul 2023 22:13:10 +0000 (15:13 -0700)
committerTim Otten <totten@civicrm.org>
Tue, 25 Jul 2023 23:02:35 +0000 (16:02 -0700)
Civi/Core/Url.php
tests/phpunit/Civi/Core/UrlTest.php
tests/phpunit/E2E/Core/PathUrlTest.php

index 9ae35d2891c516420f78306641a87c9e19896298..70eb99bbee3ccb3fe13f7b1a9dedde74a4d0f648 100644 (file)
@@ -523,6 +523,7 @@ final class Url implements \JsonSerializable {
       $scheme = \CRM_Core_Menu::isPublicRoute($this->getPath()) ? 'frontend' : 'backend';
     }
 
+    // Goal: After this switch(), we should have the $scheme, $path, and $query combined.
     switch ($scheme) {
       case 'assetBuilder':
         $assetName = $this->getPath();
@@ -534,11 +535,8 @@ final class Url implements \JsonSerializable {
       case 'asset':
         if (preg_match(';^\[([\w\.]+)\](.*)$;', $this->getPath(), $m)) {
           [, $var, $rest] = $m;
-          $result = rtrim(\Civi::paths()->getVariable($var, 'url'), '/');
-          if ($preferFormat === 'relative') {
-            $result = \CRM_Utils_Url::toRelative($result);
-          }
-          $result .= $rest . $this->composeSuffix();
+          $varValue = rtrim(\Civi::paths()->getVariable($var, 'url'), '/');
+          $result = $varValue . $rest . $this->composeQuery();
         }
         else {
           throw new \RuntimeException("Malformed asset path: {$this->getPath()}");
@@ -547,7 +545,7 @@ final class Url implements \JsonSerializable {
 
       case 'ext':
         $parts = explode('/', $this->getPath(), 2);
-        $result = \Civi::resources()->getUrl($parts[0], $parts[1] ?? NULL, FALSE) . $this->composeSuffix();
+        $result = \Civi::resources()->getUrl($parts[0], $parts[1] ?? NULL, FALSE) . $this->composeQuery();
         break;
 
       // Handle 'frontend', 'backend', 'service', and any extras.
@@ -556,12 +554,6 @@ final class Url implements \JsonSerializable {
         if ($result === NULL) {
           throw new \RuntimeException("Unknown URL scheme: $scheme");
         }
-        if ($preferFormat === 'relative') {
-          $result = \CRM_Utils_Url::toRelative($result);
-        }
-        if ($fragment = $this->composeFragment()) {
-          $result .= '#' . $fragment;
-        }
         break;
     }
 
@@ -569,6 +561,12 @@ final class Url implements \JsonSerializable {
       $result = \Civi::resources()->addCacheCode($result);
     }
 
+    $result .= $this->composeFragment();
+
+    if ($preferFormat === 'relative') {
+      $result = \CRM_Utils_Url::toRelative($result);
+    }
+
     // TODO decide if the current default is good enough for future
     $ssl = $this->getSsl() ?: \CRM_Utils_System::isSSL();
     if ($ssl && str_starts_with($result, 'http:')) {
@@ -603,24 +601,29 @@ final class Url implements \JsonSerializable {
     return $this->__toString();
   }
 
-  private function composeSuffix(): string {
-    $result = '';
+  /**
+   * @return string
+   *   '' or '?foo=bar'
+   */
+  private function composeQuery(): string {
     if ($this->query !== NULL && $this->query !== '') {
-      $result .= '?' . $this->query;
+      return '?' . $this->query;
     }
-    $fragment = $this->composeFragment();
-    if ($fragment !== NULL && $fragment !== '') {
-      $result .= '#' . $fragment;
+    else {
+      return '';
     }
-    return $result;
   }
 
-  private function composeFragment(): ?string {
-    $fragment = $this->fragment;
+  /**
+   * @return string
+   *   '' or '#foobar'
+   */
+  private function composeFragment(): string {
+    $fragment = $this->fragment ?: '';
     if ($this->fragmentQuery !== NULL && $this->fragmentQuery !== '') {
-      $fragment = ($fragment ?: '') . '?' . $this->fragmentQuery;
+      $fragment .= '?' . $this->fragmentQuery;
     }
-    return $fragment;
+    return ($fragment === '') ? '' : "#$fragment";
   }
 
   private static function detectFormat(): string {
index c12933c13a6bccfd9f4d464a29b862eabff8d1f2..4bbbb8812ff8a890a98c0e18483801802dcc58c5 100644 (file)
@@ -34,11 +34,15 @@ class UrlTest extends \CiviUnitTestCase {
     $absolutes = [];
     $absolutes['flag'] = Civi::url('backend://civicrm/admin', 'a');
     $absolutes['method'] = Civi::url('backend://civicrm/admin')->setPreferFormat('absolute');
+    $absolutes['ext'] = Civi::url('ext://org.civicrm.search_kit/js/foobar.js', 'a');
+    $absolutes['asset'] = Civi::url('asset://[civicrm.packages]/js/foobar.js', 'a');
 
     $relatives = [];
     $relatives['default'] = Civi::url('backend://civicrm/admin');
     $relatives['flag'] = Civi::url('backend://civicrm/admin', 'r');
     $relatives['method'] = Civi::url('backend://civicrm/admin')->setPreferFormat('relative');
+    $relatives['ext'] = Civi::url('ext://org.civicrm.search_kit/js/foobar.js', 'r');
+    $relatives['asset'] = Civi::url('asset://[civicrm.packages]/js/foobar.js', 'r');
 
     foreach ($absolutes as $key => $url) {
       $this->assertRegExp(';^https?://;', (string) $url, "absolutes[$key] should be absolute URL");
index 98a316fa961f46d30715cb96aa8c6dee19fbcf5d..c08fb5650f17979b94b0ee46f4f451ac3b5c3a0e 100644 (file)
@@ -107,6 +107,10 @@ class PathUrlTest extends \CiviEndToEndTestCase {
 
     $urlPats[] = [';^https?://.*civicrm;', \Civi::url('frontend://civicrm/event/info?reset=1', 'a')];
     $urlPats[] = [';^https://.*civicrm;', \Civi::url('frontend://civicrm/event/info?reset=1', 'as')];
+    $urlPats[] = [';civicrm(/|%2F)a(/|%2F).*#/mailing/new\?angularDebug=1;', \Civi::url('backend://civicrm/a/#/mailing/new?angularDebug=1')];
+    $urlPats[] = [';/jquery.timeentry.js\?r=.*#foo;', \Civi::url('asset://[civicrm.packages]/jquery/plugins/jquery.timeentry.js', 'c')->addFragment('foo')];
+    $urlPats[] = [';/stuff.js\?r=.*#foo;', \Civi::url('ext://org.civicrm.search_kit/stuff.js', 'c')->addFragment('foo')];
+    $urlPats[] = [';#foo;', \Civi::url('assetBuilder://crm-l10n.js?locale=en_US')->addFragment('foo')];
 
     // Some test-harnesses have HTTP_HOST. Some don't. It's pre-req for truly relative URLs.
     if (!empty($_SERVER['HTTP_HOST'])) {