Problem/Motivation

file_create_url() got no love at all during the D8 cycle.

Consequently, it's:

  1. still procedural
  2. calls \Drupal::service() four times, and uses a global
  3. is not unit testable

Additionally, in current D8, two additional problems arose:

  1. 90% of the calls need to be wrapped like this file_url_transform_relative(file_create_url($css_asset['data'])); because it generates absolute URL's and we want to avoid the url.site cache context whenever we can.
  2. Some of the calls that would like to use relative URLs currently can't because they need to pass the result as a Url object, and the problem is that non-absolute file_create_url() URL's are root-relative, so they contain the path that drupal is installed in. The Url class however doesn't accept that and can't deal with it. That is what #2646744: \Drupal\Core\Url does not accept root-relative (file) URLs, making it impossible to let LinkGenerator create root-relative file URL links is about. However, that is in my (berdir) opinion almost impossible to solve, because if you have /admin/foo without any further information, it's not possible to tell if /admin is now the base path or not.. Even if the base path really is /admin, it might still be an internal /-prefixed URL.

Proposed resolution

We have two possible options, but both involve adding a FileUrlGenerator service with methods to deal with this. The best way to fix the above problem is to return Url objects already from that service, at least when we need it. Initial versions had a method with an argument to get a relative/absolute URL, but that has been abandoned.

Option 1)
Add 3 dedicated methods for each use case: relative string: generate(), absolute string: generateAbsolute(), Url object: generateUrl(). Advantage is that *many* cases right now really do just need a string, but it leads to considerable duplication within the methods as the current implementation is designed to make it as efficient as possible. E.g. if we get a relative file URL, and want a relative result, we just generate that. And not a Url object or absolute URL just to make it then relative again like we do now.

Option 2)
Only add generateUrl(), if you want a string, use toString(), if you want it to be absolute, use setAbsolute()->toString(). That comes with a certain overhead when you don't need a Url object both in terms of code execution and also characters you have to type. But I more and more places deal with Url objects and the ->toString() thing is everywhere now.. entity toUrl/toLink, and we deprecated several other API's in favor of Url/Link .. toString(). It also allows us to improve cacheability metadata handling, as it can be added automatically.

The patch up until comment #130 implements Option 1, and I (again, berdir) initially believed that to be better, but I'm starting to reconsider. As far as the amount of calls on each of those calls go, these are the current numbers, identified with file.+->generate\(:
generate(): 76 calls in 32 files
generateAbsolute(): 35 calls in 13 files
generateUrl(): 6 calls in 5 files.

That does look it overwhelmingly points out that the separate methods are very useful. *But* a huge amount of those calls are actually in tests and are neither performance-relevant nor the main use case for DX. If I exclude those, it changes quite a bit:
generate(): 21 calls in 14 files
generateAbsolute(): 3 calls in 3 files
generateUrl(): 4 calls in 4 files.

Still considerably more generate() calls than generateUrl() but it does paint a more realistic picture.

Update

The decision was initially made to go with Option 2. However, considerable drawbacks were discovered while implementing that, for example because several related and underlying API's can only return strings and multiple conversions between Url object and back were necessary to display an image style for example. See #137for the details. Changing these API's is not feasible in the already large scope of this issue.

Because of that, this decision was reverted and starting with #151, the patch when back to an earlier version implementing Option 1.

Remaining tasks

Create a follow-up to deprecate the non-Url methods in Drupal 9, likely as a meta issue that first needs to update the related API's. #3087434: [meta] Use FileUrlGenerator::generate() everywhere, then deprecate generateString() and generateAbsoluteString()

Update #2646744: \Drupal\Core\Url does not accept root-relative (file) URLs, making it impossible to let LinkGenerator create root-relative file URL links after this is resolved. While the bug is not technically fixed, it becomes much less important as we can create a Url object directly instead of having to convert it.

User interface changes

None.

API changes

None.

Data model changes

None.

Release note

Most file URLs will now use relative instead of absolute links, after refactoring of the file generation APIs. See the <a href="https://www.drupal.org/node/3223515">change record</a> for more details.

CommentFileSizeAuthor
#257 2669074-257.patch2.13 KBmstrelan
#254 2669074-253.patch194.98 KBalexpott
#254 250-253-interdiff.txt950 bytesalexpott
#250 2669074-250-interdiff.txt2.74 KBkim.pepper
#250 2669074-250.patch194.86 KBkim.pepper
#247 2669074-247-interdiff.txt891 byteskim.pepper
#247 2669074-247.patch194.64 KBkim.pepper
#246 2669074-246.patch194.51 KBmondrake
#246 interdiff_243-246.txt10.63 KBmondrake
#243 2669074-243-interdiff.txt9.47 KBkim.pepper
#243 2669074-243.patch191.7 KBkim.pepper
#238 2669074-238-interdiff.txt9.26 KBkim.pepper
#238 2669074-238.patch185.76 KBkim.pepper
#234 2669074-234-interdiff.txt1.18 KBkim.pepper
#234 2669074-234.patch185.08 KBkim.pepper
#232 2669074-232-interdiff.txt5.6 KBkim.pepper
#232 2669074-232.patch185.07 KBkim.pepper
#229 2669074-229-interdiff.txt717 byteskim.pepper
#229 2669074-229.patch185.07 KBkim.pepper
#227 2669074-227-interdiff.txt916 byteskim.pepper
#227 2669074-227.patch185.08 KBkim.pepper
#226 2669074-226-interdiff.txt4.38 KBkim.pepper
#226 2669074-226.patch185.38 KBkim.pepper
#225 2669074-225-interdiff.txt2.72 KBkim.pepper
#225 2669074-225.patch184.1 KBkim.pepper
#224 2669074-224-interdiff.txt20.71 KBkim.pepper
#224 2669074-224.patch184.16 KBkim.pepper
#223 2669074-223-interdiff.txt629 byteskim.pepper
#223 2669074-223.patch185.18 KBkim.pepper
#219 interdiff.txt495 bytesKapilV
#219 2669074-219.patch185.14 KBKapilV
#217 2669074-217-interdiff.txt5 KBkim.pepper
#217 2669074-217.patch185.19 KBkim.pepper
#214 2669074-213-interdiff.txt13.77 KBkim.pepper
#214 2669074-213.patch182.59 KBkim.pepper
#212 interdiff.txt1.46 KBKapilV
#212 2669074-222.patch183.47 KBKapilV
#211 2669074-211.patch182.63 KBkim.pepper
#208 reroll_diff_205-208.txt5.5 KBNeslee Canil Pinto
#208 2669074-208.patch183.24 KBNeslee Canil Pinto
#205 2669074-205.patch183.24 KBandypost
#205 interdiff.txt788 bytesandypost
#204 2669074-204.patch183.03 KBandypost
#204 interdiff.txt331 bytesandypost
#202 2669074-9.2.x-202.patch183.04 KBs.messaris
#201 interdiff_198-201.txt6.38 KBravi.shankar
#201 2669074-201.patch182.94 KBravi.shankar
#198 raw_diff.txt40.18 KBravi.shankar
#198 2669074-198.patch181.96 KBravi.shankar
#195 2669074-195.patch182.26 KBNono95230
#193 2669074-193.patch182.63 KBdaffie
#188 2669074-188-interdiff.txt25.05 KBkim.pepper
#188 2669074-188.patch183.7 KBkim.pepper
#186 2669074-186-interdiff.txt942 byteskim.pepper
#186 2669074-186.patch180.21 KBkim.pepper
#184 2669074-184-interdiff.txt1.67 KBkim.pepper
#184 2669074-184.patch180.21 KBkim.pepper
#182 2669074-182-interdiff.txt1.79 KBkim.pepper
#182 2669074-182.patch180.2 KBkim.pepper
#180 2669074-180-interdiff.txt77.5 KBkim.pepper
#180 2669074-180.patch180.18 KBkim.pepper
#178 2669074-178-interdiff.txt11.54 KBkim.pepper
#178 2669074-178.patch178.24 KBkim.pepper
#177 2669074-177-interdiff.txt2.06 KBkim.pepper
#177 2669074-177.patch177.87 KBkim.pepper
#175 2669074-175.patch177.85 KBkim.pepper
#170 2669074-170.patch181.92 KBbradjones1
#170 reroll-interdiff-169-170.txt11.56 KBbradjones1
#169 2669074-169.patch181.86 KBbradjones1
#162 2669074-162.patch181.79 KBWim Leers
#162 interdiff.txt2.64 KBWim Leers
#151 2669074-151.patch181.8 KBandypost
#151 interdiff.txt3.31 KBandypost
#150 2669074-150.patch185.11 KBandypost
#147 interdiff-2669074-145-147.txt6.39 KBvoleger
#147 2669074-147.patch185.17 KBvoleger
#145 2669074-145.patch182.11 KBkim.pepper
#137 2669074-137-interdiff.txt14.36 KBBerdir
#137 2669074-137.patch183.12 KBBerdir
#137 2669074-137.patch183.12 KBBerdir
#134 2669074-134-interdiff.txt79.49 KBBerdir
#134 2669074-134.patch176.76 KBBerdir
#132 2669074-132-interdiff.txt480 bytesBerdir
#132 2669074-132.patch182.73 KBBerdir
#130 2669074-130.patch183.02 KBBerdir
#125 2669074-125-interdiff.txt841 byteskim.pepper
#125 2669074-125.patch183.08 KBkim.pepper
#122 2669074-122-interdiff.txt17.9 KBkim.pepper
#122 2669074-122.patch182.44 KBkim.pepper
#119 2669074-119-interdiff.txt647 byteskim.pepper
#119 2669074-119.patch173.25 KBkim.pepper
#116 2669074-116-interdiff.txt3.1 KBkim.pepper
#116 2669074-116.patch172.92 KBkim.pepper
#114 2669074-114.patch169.81 KBkim.pepper
#112 2669074-112.patch170.53 KBkim.pepper
#112 2669074-112-interdiff.txt4.29 KBkim.pepper
#108 2669074-107.patch170.1 KBBerdir
#107 2669074-107-interdiff.txt4 KBBerdir
#102 2669074-102-interdiff.txt17 KBBerdir
#102 2669074-102.patch169.38 KBBerdir
#99 2669074-99-interdiff.txt56.63 KBBerdir
#99 2669074-99.patch169.25 KBBerdir
#97 2669074-97.patch166.07 KBkim.pepper
#97 2669074-97-interdiff.txt9.23 KBkim.pepper
#92 2669074-92.patch166.05 KBkim.pepper
#92 2669074-92-interdiff.txt76.9 KBkim.pepper
#88 2669074-88.patch166.55 KBkim.pepper
#88 2669074-88-interdiff.txt37.16 KBkim.pepper
#86 2669074-86.patch147.21 KBkim.pepper
#86 2669074-86-interdiff.txt84.98 KBkim.pepper
#83 2669074-83.patch63.84 KBkim.pepper
#83 2669074-83-interdiff.txt7.25 KBkim.pepper
#81 convert-2669074-81.patch61.7 KBBerdir
#79 convert-2669074-79-interdiff.txt4.59 KBBerdir
#79 convert-2669074-79.patch61.9 KBBerdir
#77 convert-2669074-77-interdiff.txt1.7 KBBerdir
#77 convert-2669074-77.patch59.64 KBBerdir
#75 convert-2669074-75-interdiff.txt3.7 KBBerdir
#75 convert-2669074-75.patch59.2 KBBerdir
#72 convert-2669074-72-interdiff.txt51.05 KBBerdir
#72 convert-2669074-72.patch57.42 KBBerdir
#66 interdiff-2669074-61-66.txt1.01 KBgaydabura
#66 convert-2669074-66.patch10.74 KBgaydabura
#61 interdiff-2669074-58-61.txt1.48 KBgaydabura
#61 convert-2669074-61.patch11.2 KBgaydabura
#51 interdiff-2669074-49-51.txt809 bytesgaydabura
#51 convert-2669074-51.patch10.63 KBgaydabura
#49 interdiff-47-49.txt365 bytesgaydabura
#49 convert-2669074-49.patch10.67 KBgaydabura
#47 interdiff-37-47.txt2.33 KBgaydabura
#47 convert-2669074-47.patch10.43 KBgaydabura
#46 drupal-convert_file_functions-2669074-46.patch10.45 KBabramm
#37 convert-2669074-37.patch10.44 KBandypost
#37 2669074-interdif-32.txt1.74 KBandypost
#32 convert-2669074-32.patch10.46 KBnickolaj
#31 convert-2669074-31.patch4.01 KBnickolaj
#26 interdiff-2669074-22-26.txt3.03 KByogeshmpawar
#26 convert-2669074-26.patch10.44 KByogeshmpawar
#25 convert-2669074-25.patch10.38 KBdhruveshdtripathi
#22 interdiff-19-22.txt1.25 KBgaurav.kapoor
#22 2669074-22.patch10.38 KBgaurav.kapoor
#19 interdifff.txt834 bytesPavan B S
#19 2669074-19.patch10.45 KBPavan B S
#17 interdiff-2669074-7-17.txt1.49 KB20th
#17 2669074-17.patch10.44 KB20th
#7 interdiff5-7.txt3.89 KBCameron Tod
#7 2669074-file-url-service-7.patch10.45 KBCameron Tod
#5 interdiff-2669074-2-5.txt1.07 KBaerozeppelin
#5 2669074-5.patch8.1 KBaerozeppelin
#2 file_create_url_service-2669074-2.patch8.33 KBWim Leers
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Wim Leers created an issue. See original summary.

Wim Leers’s picture

Issue summary: View changes
Status: Active » Needs review
Issue tags: +Needs tests
FileSize
8.33 KB

Here's the simplest possible patch that turns it into a service. Still lots left to do. IS updated.

mondrake’s picture

If I am not mistaken,

git grep 'file_create_url(' | wc -l gives 274 instances of file_create_url in core code,

while

git grep 'file_create_url(' | git grep 'file_url_transform_relative(' | wc -l tells us that 228 of the 274 (83%) instances are actually part of a file_url_transform_relative(file_create_url(...)) construct.

For 8.1, does it warrant for a single method that would remove the need for double call?

Status: Needs review » Needs work

The last submitted patch, 2: file_create_url_service-2669074-2.patch, failed testing.

aerozeppelin’s picture

Status: Needs work » Needs review
FileSize
8.1 KB
1.07 KB

Updates to #2.

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.0-beta1 was released on March 2, 2016, which means new developments and disruptive changes should now be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Cameron Tod’s picture

Re #4, how about something like this?

dawehner’s picture

Questions:

  • Should we add an interface already or are there more functions which could be converted later?
  • Do we need unit tests already for this issue? IMHO this would be a nice thing.
+++ b/core/includes/file.inc
@@ -193,57 +193,12 @@ function file_stream_wrapper_uri_normalize($uri) {
+ * @deprecated in Drupal 8.0.x-dev and will be removed before Drupal 9.0.0.

@@ -261,23 +216,12 @@ function file_create_url($uri) {
+ *
+ * @deprecated in Drupal 8.0.x-dev and will be removed before Drupal 9.0.0.
+ *   Use\Drupal\Core\File\FileUrlGenerator::transformRelative().

nitpick: IMHO this will be either 8.1 or 8.2

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.0-beta1 was released on August 3, 2016, which means new developments and disruptive changes should now be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Mile23’s picture

Title: Convert file_create_url() to service » Convert file_create_url() to service, deprecate it
Priority: Major » Normal
Issue summary: View changes
Status: Needs review » Needs work
Issue tags: -Contributed project soft blocker +Kill includes, +@deprecated

Still needs tests, since that's ostensibly why we're doing this. :-)

I don't see a linked contrib issue so I'm taking away the soft blocker tag.

  1. +++ b/core/includes/file.inc
    @@ -193,57 +193,12 @@ function file_stream_wrapper_uri_normalize($uri) {
    + * @deprecated in Drupal 8.0.x-dev and will be removed before Drupal 9.0.0.
    

    8.3.x.

  2. +++ b/core/includes/file.inc
    @@ -193,57 +193,12 @@ function file_stream_wrapper_uri_normalize($uri) {
    -      $path = $GLOBALS['base_url'] . '/' . UrlHelper::encodePath($options['path']);
    

    Soooo glad to get rid of $GLOBALS. :-)

  3. +++ b/core/includes/file.inc
    @@ -261,23 +216,12 @@ function file_create_url($uri) {
    + * @deprecated in Drupal 8.0.x-dev and will be removed before Drupal 9.0.0.
    

    8.3.x.

Wim Leers’s picture

The D8 port of the CDN module is soft-blocked on it. For now, it redirects hook_file_url_alter() to its cdn.file_url_generator service: http://cgit.drupalcode.org/cdn/tree/cdn.module?h=8.x-3.0-alpha1&id=032e7....

20th’s picture

20th’s picture

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.0-alpha1 will be released the week of January 30, 2017, which means new developments and disruptive changes should now be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Mile23’s picture

Patch in #7 still applies to 8.4.x, but it also needs deprecation message updates for core version.

20th’s picture

Assigned: Unassigned » 20th

Updating target version in deprecation messages, for starters.

Two other functions look like good candidates for this service: file_valid_url(), file_build_uri().

20th’s picture

yogeshmpawar’s picture

Status: Needs work » Needs review
Pavan B S’s picture

+++ b/core/lib/Drupal/Core/File/FileUrlGenerator.php
@@ -0,0 +1,185 @@
+        // If this is not a properly formatted stream, then it is a shipped file.
...
+          $path .= '#' . $options['fragment'];

Line is exceeding 80 characters.
Applying the patch, please review.

claudiu.cristea’s picture

Status: Needs review » Needs work

Two main remarks:

  1. I see this service exposes 2 public methods. So let's write a contract for them.
  2. Searched the entire core for unit testing of file_create_url() but I failed to find. Correct me if I'm wrong. I think we need to add some testing along with the service.

Nits:

  1. +++ b/core/includes/file.inc
    @@ -246,23 +201,12 @@ function file_create_url($uri) {
    + *   Use\Drupal\Core\File\FileUrlGenerator::transformRelative().
    

    Space after "Use". Aso, according to https://www.drupal.org/docs/develop/coding-standards/api-documentation-a...

    Lines containing comments (including docblocks) must wrap as close to 80 characters as possible without going over (...)

    So, "Use" can go to the first line.

  2. +++ b/core/lib/Drupal/Core/File/FileUrlGenerator.php
    @@ -0,0 +1,186 @@
    +/**
    + * @file
    + * Contains \Drupal\Core\File\FileUrlGenerator.
    + */
    +
    

    Should be removed. See https://www.drupal.org/docs/develop/coding-standards/api-documentation-a...

    The @file doc block MUST be present for all PHP files, with one exception: files that contain a namespaced class/interface/trait, whose file name is the class name with a .php extension, and whose file path is closely related to the namespace (under PSR-4 or a similar standard), SHOULD NOT have a @file documentation block.

    This is the case.

  3. +++ b/core/lib/Drupal/Core/File/FileUrlGenerator.php
    @@ -0,0 +1,186 @@
    +   *
    

    No empty line between @param blocks.

  4. +++ b/core/lib/Drupal/Core/File/FileUrlGenerator.php
    @@ -0,0 +1,186 @@
    +   * @return string
    +   *   A string containing a URL that may be used to access the file.
    +   *   If the provided string already contains a preceding 'http', 'https', or
    +   *   '/', nothing is done and the same string is returned. If a stream wrapper
    +   *   could not be found to generate an external URL, then FALSE is returned.
    

    Rearrange text so lines are filled till the end. See my previous remark. Also, as I see , this method returns also FALSE. Need to reflect this in docs.

gaurav.kapoor’s picture

Assigned: 20th » gaurav.kapoor
gaurav.kapoor’s picture

Status: Needs work » Needs review
FileSize
10.38 KB
1.25 KB
gaurav.kapoor’s picture

Assigned: gaurav.kapoor » Unassigned
claudiu.cristea’s picture

Status: Needs review » Needs work

NW because of #20.

+++ b/core/lib/Drupal/Core/File/FileUrlGenerator.php
@@ -0,0 +1,180 @@
+   *   A string containing a URL that may be used to access the file.If the

Space before "If..."

#20.1, and partially 4 were not addressed.

dhruveshdtripathi’s picture

Status: Needs work » Needs review
FileSize
10.38 KB

Made changes according to comment #24

yogeshmpawar’s picture

Changes as per comment #24 & also added interdiff.
#25 failed to apply on 8.4.x branch (giving error as "fatal: patch fragment without header at line 412: @@ -193,57 +193,12 @@ function file_stream_wrapper_uri_normalize($uri) {")

yogeshmpawar’s picture

Any Update on this issue ?

Mile23’s picture

Status: Needs review » Needs work
Issue tags: +Needs change record, +Needs followup

Patch still applies. Re-running the test.

This will need a follow-up to remove usages, and a change record to talk about the deprecation.

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.0-alpha1 will be released the week of July 31, 2017, which means new developments and disruptive changes should now be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.0-alpha1 will be released the week of January 17, 2018, which means new developments and disruptive changes should now be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

nickolaj’s picture

Issue tags: -Needs followup
FileSize
10.46 KB

The last submitted patch, 31: convert-2669074-31.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

nickolaj’s picture

andypost’s picture

Status: Needs review » Needs work

Now new class needs tests

andypost’s picture

+++ b/core/includes/file.inc
@@ -186,57 +186,12 @@ function file_stream_wrapper_uri_normalize($uri) {
+ * @deprecated in Drupal 8.6.x and will be removed before Drupal 9.0.0.
+ *   Use \Drupal\Core\File\FileUrlGenerator::generate().
...
+  return \Drupal::service('file_url_generator')->generate($uri);

@@ -254,23 +209,12 @@ function file_create_url($uri) {
+ * @deprecated in Drupal 8.6.x and will be removed before Drupal 9.0.0.
+ *   Use \Drupal\Core\File\FileUrlGenerator::transformRelative().
...
+  return \Drupal::service('file_url_generator')->transformRelative($file_url);

This functions needs link to change record and should trigger error https://www.drupal.org/core/deprecation#how-function

andypost’s picture

Status: Needs work » Needs review
FileSize
1.74 KB
10.44 KB
$ git grep file_url_transform_relative|wc -l
258

$ git grep file_create_url|wc -l
310

$ git grep -F 'file_url_transform_relative(file_create_url(' |wc -l
202

so most used both file_url_transform_relative(file_create_url($path . $string))
lots of time used in tests & wraps file entity url generation

So it means that the functions used mostly to build external URL and convert it to string

I think it needs rework

Patch
- a bit of clean-up logic
- docs fixes

+++ b/core/lib/Drupal/Core/File/FileUrlGenerator.php
@@ -0,0 +1,180 @@
+        return $uri;
...
+        return $path;
...
+      return $uri;
...
+        return $wrapper->getExternalUrl();
...
+        return FALSE;

cyclomatic complexity 10 and return type string or false

Wim Leers’s picture

Nice to see progress here :)

+++ b/core/lib/Drupal/Core/File/FileUrlGenerator.php
@@ -0,0 +1,176 @@
+    // Allow the URI to be altered, e.g. to serve a file from a CDN or static
+    // file server.
+    $this->moduleHandler->alter('file_url', $uri);

I think this can be deprecated now, because in a world of services, you'd actually want to decorate this service.

See core/modules/big_pipe/big_pipe.services.yml for an example of service decoration.

andypost’s picture

@Wim deprecation of the hook probably needs separate issue

Berdir’s picture

If decorating a service would be the answer to everything then why does symfony have events? Service decorators can be useful in some cases, but they have problems, especially in drupal with our serialization handling. Events/hooks are still useful for anything that likely has multiple customizations and where the order might be relevant.

Converting to an event in a separate issue might be useful yes, but agreed that we shouldn't do that here.

almaudoh’s picture

It's always a nice thing when we can convert these old functions to properly testable, swappable services.

--- a/core/includes/file.inc
+++ b/core/includes/file.inc
@@ -186,57 +186,12 @@ function file_stream_wrapper_uri_normalize($uri) {
  *
  * @see https://www.drupal.org/node/515192
  * @see file_url_transform_relative()
+ *
+ * @deprecated in Drupal 8.6.x and will be removed before Drupal 9.0.0.
+ *   Use \Drupal\Core\File\FileUrlGenerator::generate().
  */
 function file_create_url($uri) {

 /**
@@ -254,23 +209,12 @@ function file_create_url($uri) {
  *   original value of $file_url.
  *
  * @see file_create_url()
+ *
+ * @deprecated in Drupal 8.6.x and will be removed before Drupal 9.0.0.
+ *   Use \Drupal\Core\File\FileUrlGenerator::transformRelative().
  */
 function file_url_transform_relative($file_url) {
-  // Unfortunately, we pretty much have to duplicate Symfony's
-  // Request::getHttpHost() method because Request::getPort() may return NULL

If we're deprecating these, we should have @trigger_error as well to enforce the deprecation.

andypost’s picture

@almaudoh to trigger error it need to replace usage and #37 explains that to replace usage we need to reconsider API for new service to prevent file_url_transform_relative(file_create_url( with is ~70% of usage

arnested’s picture

Notice that the patch need to be rerolled to incorporate the change from #2257231. That change also introduced some test cases.

Also I suggested fixing some edge case errors in #2983058.

Version: 8.6.x-dev » 8.7.x-dev

Drupal 8.6.0-alpha1 will be released the week of July 16, 2018, which means new developments and disruptive changes should now be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

jhedstrom’s picture

Status: Needs review » Needs work
Issue tags: +Needs reroll

Marking NW for the reroll.

abramm’s picture

Here's a re-roll of #37 including changes from #2257231. Posting here to run tests.

Remaining work:

  1. Update tests in \Drupal\KernelTests\Core\File\UrlTransformRelativeTest::testFileUrlTransformRelative() to cover a new service.
  2. What else?

I'm sorry but interdiff failed.

gaydabura’s picture

Status: Needs work » Needs review
FileSize
10.43 KB
2.33 KB

re-rolled

Status: Needs review » Needs work

The last submitted patch, 47: convert-2669074-47.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

gaydabura’s picture

Status: Needs review » Needs work

The last submitted patch, 49: convert-2669074-49.patch, failed testing. View results

gaydabura’s picture

gaydabura’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 51: convert-2669074-51.patch, failed testing. View results

naveenvalecha’s picture

+++ b/core/lib/Drupal/Core/File/FileUrlGenerator.php
@@ -0,0 +1,175 @@
+    return preg_replace('|^https?://' . $http_host . '|', '', $file_url);

Move the original code here.
preg_replace('|^https?://' . preg_quote($http_host, '|') . '|', '', $file_url);

gaydabura’s picture

Status: Needs work » Needs review
FileSize
9.92 KB
1.09 KB

thnx @naveenvalecha , it was all about dots.
updated

Status: Needs review » Needs work

The last submitted patch, 55: convert-2669074-55.patch, failed testing. View results

savkaviktor16@gmail.com’s picture

Issue tags: -Needs reroll
gaydabura’s picture

Status: Needs work » Needs review
FileSize
10.65 KB
674 bytes

for some reason i removed service. fixed.

andypost’s picture

  1. +++ b/core/includes/file.inc
    @@ -184,57 +183,12 @@ function file_stream_wrapper_uri_normalize($uri) {
    + * @deprecated in Drupal 8.6.x and will be removed before Drupal 9.0.0.
    
    @@ -252,23 +206,12 @@ function file_create_url($uri) {
    + * @deprecated in Drupal 8.6.x and will be removed before Drupal 9.0.0.
    

    it should be 8.7.x now

  2. +++ b/core/includes/file.inc
    @@ -184,57 +183,12 @@ function file_stream_wrapper_uri_normalize($uri) {
    +  return \Drupal::service('file_url_generator')->generate($uri);
    
    @@ -252,23 +206,12 @@ function file_create_url($uri) {
    +  return \Drupal::service('file_url_generator')->transformRelative($file_url);
    

    Also it needs to throw error, test deprecated code & remove usage according to https://www.drupal.org/core/deprecation#how-function

andypost’s picture

Title: Convert file_create_url() to service, deprecate it » Convert file_create_url() & file_url_transform_relative() to service, deprecate it

CR updated to mention both functions

gaydabura’s picture

Status: Needs work » Needs review
FileSize
11.2 KB
1.48 KB

updated

andypost’s picture

Not sure it makes sense to trigger error now because usage of the functions are very broad

core8$ git grep file_create_url |wc -l
314
core8$ git grep file_url_transform_relative| wc -l
261

But the issue gives me 403 for some reason #2940025: Remove deprecated functions from file module

Status: Needs review » Needs work

The last submitted patch, 61: convert-2669074-61.patch, failed testing. View results

gaydabura’s picture

Assigned: Unassigned » gaydabura
Status: Needs work » Needs review
gaydabura’s picture

Status: Needs review » Needs work
gaydabura’s picture

Status: Needs work » Needs review
FileSize
10.74 KB
1.01 KB

Patch updated to not trigger error.
After researching core code we can see a lot of internal usage of this two functions. So Issue need to be spitted to another one, where we can make a decision of usage this functions/methods in every case, like if its related to file entity, make sense to add method to drupal/core/modules/file/src/Entity/File.php and other cases need to be re-thinked aswell. But imho its out of scope of this task

Berdir’s picture

Status: Needs review » Needs work
  1. +++ b/core/includes/file.inc
    @@ -184,57 +183,14 @@ function file_stream_wrapper_uri_normalize($uri) {
      * @see https://www.drupal.org/node/515192
      * @see file_url_transform_relative()
    + *
    + * @deprecated in Drupal 8.7.x and will be removed before Drupal 9.0.0.
    + *   Use \Drupal\Core\File\FileUrlGenerator::generate().
    + *
    ...
      */
     function file_create_url($uri) {
    

    This is tricky because we're trying to avoid adding more @deprecated without a @trigger_error(), at the same time, 300 usages is obviously too much to update in a single patch.

    We should probably do at least a few of them, though and we need a plan to do the others, which means I guess a follow-up (meta) issue to organize that.

    I know some people won't like it, but with 300+ usages and a lot of that in tests and other places that we can't/won't actually inject, I'm wondering if this one would deserve a method on \Drupal: \Drupal::fileUrlGenerator()->generate()?

    And last, the change record should also cover the $relative argument and show how to convert the very common combined usage of file_url_transform_relative(file_create_url().

  2. +++ b/core/lib/Drupal/Core/File/FileUrlGenerator.php
    @@ -0,0 +1,174 @@
    +/**
    + * Generates file URLs.
    + */
    +class FileUrlGenerator {
    

    Not sure if this needs an interface or not. It's certainly not a value object or anything like that, so possibly?

  3. +++ b/core/lib/Drupal/Core/File/FileUrlGenerator.php
    @@ -0,0 +1,174 @@
    +   * @param bool $relative
    +   *   Set to TRUE to return a URL that is relative to the current request's
    +   *   base URI.
    +   *
    

    I guess the question now is what the default value for this should be.

    Consdering that relative should be the default now, I'd say we should do $relative = TRUE?

  4. +++ b/core/lib/Drupal/Core/File/FileUrlGenerator.php
    @@ -0,0 +1,174 @@
    +   *
    +   * @see https://www.drupal.org/node/515192
    +   * @see file_url_transform_relative()
    

    that d.o node is the D7 documentation, I think we can just remove that.

    and the reference to the deprecated function is also not needed and the replacement is on the same service and easy to find.

gaydabura’s picture

@Berdir
1. Agree, imho we can create new issue for replacement deprecated usage. But i guess it can impact on contrib modules a lot. Still i'd like to have new issue for replacement.
2. Make sense
3. It depends from usage, from what i saw in core code, very often usage is "file_url_transform_relative(file_create_url()" - so agree #3
4. Agree

Berdir’s picture

From #2244513-46: Move the unmanaged file APIs to the file_system service (file.inc), which suggested to move all the following methods on a service:

file_uri_target()
file_stream_wrapper_uri_normalize()
file_create_url() ?
file_url_transform_relative() ?
file_valid_uri()
file_build_uri()

It's easier to add them at all once instead of having to extend an interface later on.

Re #68:

1. not sure what you mean with contrib impact, it will have that anyway, if anything, adding a \Drupal method will make it easier. Maybe skip calls in tests and convert all others here?
3. Note that I'm adding $file->createFileUrl() in #2402533: Provide File::createFileUrl() as a replacement for the deprecated File:url() implementation, with a $relative = TRUE argument, so making this consistent would be great.

Wim Leers’s picture

It's easier to add them at all once instead of having to extend an interface later on.

+1

Wim Leers’s picture

Berdir’s picture

Status: Needs work » Needs review
FileSize
57.42 KB
51.05 KB

Hm, on closer investigation, I don't think any of these additional methods belong into this new service.

* file_uri_target() is the counter-part to file_uri_scheme(), which is already on file_system
* file_stream_wrapper_uri_normalize() is also very related to these, they're all about (stream wrapper) URI's, not file URLs.
* file_valid_uri() is again about stream wrapper URI's and as a small wrapper around two functions/methods that were already moved to file_system.
* file_build_uri() also is about creating stream wrapper URIs and is tightly connected to these other functions.

FileSystem is getting quite big with that other issue, so maybe these methods could be on the stream wrapper manager, but then we'd also need to move some existing methods from FileSystem to that to avoid a cross-dependency.

I did however start with updating all the deprecated calls and it's actually not too bad. Most of them are in tests and a *ton* of them were in a few unit tests that I've now properly mocked and completely replaced with the expected hardcoded string instead of calling a mock function for the expected URLs.

I also added an interface for this, I can imagine this being something that sites might want to have different implementations of, instead of using the file_url_alter() hook for example which has some limitations on what it can do.

Most of the remaining calls can probably be done with a global search & replace, but I stopped for now as there are a bunch of overlaps with #2646744: \Drupal\Core\Url does not accept root-relative (file) URLs, making it impossible to let LinkGenerator create root-relative file URL links and it will be easier to convert once that is in, especially since that will use $file->createFileUrl() in many places.

Berdir’s picture

+++ b/core/lib/Drupal/Core/File/FileUrlGeneratorInterface.php
@@ -0,0 +1,59 @@
+/**
+ * Created by PhpStorm.
+ * User: berdir
...
+ * Time: 16:27
+ */

note for the next reroll, remove this ;)

Status: Needs review » Needs work

The last submitted patch, 72: convert-2669074-72.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

Berdir’s picture

Status: Needs work » Needs review
FileSize
59.2 KB
3.7 KB

Oops, this should be better.

Status: Needs review » Needs work

The last submitted patch, 75: convert-2669074-75.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

Berdir’s picture

Status: Needs work » Needs review
FileSize
59.64 KB
1.7 KB

Fixed the inconsistent relative argument.

Status: Needs review » Needs work

The last submitted patch, 77: convert-2669074-77.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

Berdir’s picture

Status: Needs work » Needs review
FileSize
61.9 KB
4.59 KB

Fixed the twig extension test and added explicit unit test coverage for file_url(), now that we can mock it.

Also noticed that $relative was not considered for all cases, actually only the one that didn't really matter that much (in case you passed in an existing absolute URL, which was most likely external), so that caused some other interesting test fails, and we only start to notice now as we actually start to use that option.

Status: Needs review » Needs work

The last submitted patch, 79: convert-2669074-79.patch, failed testing. View results

Berdir’s picture

Status: Needs work » Needs review
FileSize
61.7 KB

Still applied with git apply -3.

jibran’s picture

Test coverage looks good so removing the tag.

  1. +++ b/core/lib/Drupal/Core/Asset/JsCollectionRenderer.php
    @@ -17,14 +18,24 @@ class JsCollectionRenderer implements AssetCollectionRendererInterface {
    +   * @param \Drupal\Core\File\FileUrlGeneratorInterface $file_url_generator
    +   *   The file URL generator.
    
    +++ b/core/lib/Drupal/Core/Template/TwigExtension.php
    @@ -63,13 +71,16 @@ class TwigExtension extends \Twig_Extension {
    +   * @param \Drupal\Core\File\FileUrlGeneratorInterface $file_url_generator
    +   *   The file URL generator.
    
    +++ b/core/modules/ckeditor/src/Plugin/Editor/CKEditor.php
    @@ -74,13 +82,16 @@ class CKEditor extends EditorBase implements ContainerFactoryPluginInterface {
    +   * @param \Drupal\Core\File\FileUrlGeneratorInterface $file_url_generator
    +   *   The file URL generator.
    

    Do you want to mention that is optional now and will be required in 9.0.0?

  2. +++ b/core/modules/color/color.module
    @@ -487,7 +489,7 @@ function color_scheme_form_submit($form, FormStateInterface $form_state) {
    -      $css_optimizer = new CssOptimizer();
    +      $css_optimizer = \Drupal::service('asset.css.optimizer');
    

    Should be mention this in the change record?

kim.pepper’s picture

Fixes for #82

  1. I added the trigger_error dance in these constructors
  2. Also added a trigger error here, and moved calling the service out of the nested for loops so we only do it once

Did a few code sniffer cleanups too.

kim.pepper’s picture

I also updated the change record for #82.2

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

kim.pepper’s picture

Although the IS says we'd do the remove usages in a separate issue, I went ahead and added @trigger_error and replaced usages in this issue. It seems to be the preferred way to do it all in one go.

I also updated deprecation messages to reference 8.8.0.

Status: Needs review » Needs work

The last submitted patch, 86: 2669074-86.patch, failed testing. View results

kim.pepper’s picture

Assigned: gaydabura » Unassigned
Status: Needs work » Needs review
FileSize
37.16 KB
166.55 KB

Replaced a few missed instances of file_url_transform_relative()

Status: Needs review » Needs work

The last submitted patch, 88: 2669074-88.patch, failed testing. View results

kim.pepper’s picture

Not sure why relative paths aren't being returned for a lot of these fails.

kim.pepper’s picture

Instead of passing flags that change method behaviour, I usually prefer to add a method, e.g.:

<?php

  public function generateRelative($uri) {
    return $this->generate($uri, TRUE);
  }

?>

Thoughts?

kim.pepper’s picture

Status: Needs work » Needs review
FileSize
76.9 KB
166.05 KB

Wow. I really misunderstood the API. I've hopefully corrected most of the mis-use of transform-relative in this patch.

kim.pepper’s picture

How about:

<?php
public function generateAbsolute($uri);
public function generateRelative($uri);
public function transformRelative($uri);
?>

Status: Needs review » Needs work

The last submitted patch, 92: 2669074-92.patch, failed testing. View results

Berdir’s picture

I'm open to improve the API and make those methods more explicit, but it doesn't solve the underlying problem from #2646744: \Drupal\Core\Url does not accept root-relative (file) URLs, making it impossible to let LinkGenerator create root-relative file URL links, that we are unable to create a Url object from a relative-transformed URL.

I'm thinking about being able to return a URL object from those methods, or have a method that does that, and then internally do the relative-transform in a way that includes removing the base path already, because then we can be sure that it does contain it. Not fully thought through yet...

kim.pepper’s picture

Yeah, I don't think I have my head around it either. I will be at DrupalCon Seattle if anyone wants to discuss?

kim.pepper’s picture

Status: Needs work » Needs review
FileSize
9.23 KB
166.07 KB

Fixes for #92

Berdir’s picture

Assigned: Unassigned » Berdir

Looking into better methods and returning Url objects...

Berdir’s picture

This adds 3 methods instead of just one with a boolean argument:

generate()
generateAbsolute()
generateUrl(), which returns a Url object.

The 3 methods are currently completely separate on purpose, because there are many difference cases, and instead of having to generate a full URL only to strip it off again, we can now in most cases just generate what we need. And we can also create correct Url objects that don't have to be root-relative, which means most of what #2646744: \Drupal\Core\Url does not accept root-relative (file) URLs, making it impossible to let LinkGenerator create root-relative file URL links was doing is now basically included here.

Also updated a lot of tests to use $file->createFileUrl() (which still uses a boolean flag, can't change that anymore, but it's still much simpler)

Known issues:
* Url doesn't support data: urls but file_create_url() does
* image styles are still absolute, will need to figure out if we can fix that here, because we are currently removing the cache context, but it was incorectly set anyway.

Very much WIP, just want to see where this is at in regards to test fails. This is unfortunately much more than just 1:1 conversion now, we *could* try to split it up, but having and using those extra methods verifies that it works, so we'll see.

Berdir’s picture

Will also need to carefully review the existing test coverage to make sure that actually makes sense now and I plan to write a comprehensive unit test for FileUrlGenerator that tests the many possible variations for all 3 methods.

Another thing I noticed again in kernel tests is how messed up their public/base path is: 'http://localhost/vfs://root/sites/simpletest/29803809/files/file.png', we should try to make that a bit saner, but not here.

Status: Needs review » Needs work

The last submitted patch, 99: 2669074-99.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

Berdir’s picture

Status: Needs work » Needs review
FileSize
169.38 KB
17 KB

Fixing tests and cleanup. Definitely confused about the image style situation, most tests assert for a relative URL, but that works on an absolute one too and there doesn't seem to be any tests that assert that it actually is absolute. Wanted to but not changed that, seems like that's better for a dedicated issue.

The url.site assertion is also a bit pointless in \Drupal\Tests\image\Functional\ImageFieldDisplayTest::_testImageFieldFormatters() because that's still TRUE as NodeController always adds that.

Status: Needs review » Needs work

The last submitted patch, 102: 2669074-102.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

kim.pepper’s picture

A couple of minor nits:

  1. +++ b/core/lib/Drupal/Core/Asset/CssOptimizer.php
    @@ -16,6 +17,27 @@ class CssOptimizer implements AssetOptimizerInterface {
    +      @trigger_error('Calling CssOptimizer::__construct() with the $file_url_generator argument is supported in drupal 8.7.0 and will be required before 9.0.0. See https://www.drupal.org/node/2940031.', E_USER_DEPRECATED);
    

    Needs to be 8.8.0 now

  2. +++ b/core/lib/Drupal/Core/Asset/JsCollectionRenderer.php
    @@ -17,14 +18,28 @@ class JsCollectionRenderer implements AssetCollectionRendererInterface {
    +      @trigger_error('Calling JsCollectionRenderer::__construct() with the $file_url_generator argument is supported in drupal 8.7.0 and will be required before 9.0.0. See https://www.drupal.org/node/2940031.', E_USER_DEPRECATED);
    

    ditto

leolandotan’s picture

Hi guys, I was brought to this issue due to having an issue with URLs in my project that aren't encoded. In my project, I use the
tag where I have a tag that looks like this:
<source srcset="//test.domain.com/sites/default/files/styles/my_image_style/public/This is a test image.jpg 1x" media="all and (min-width: 992px)" type="image/jpeg">
Before I used the CDN module, the tag looked like this:
<source srcset="//test.domain.com/sites/default/files/styles/my_image_style/public/This%20is%20a%20test%20image.jpg 1x" media="all and (min-width: 992px)" type="image/jpeg">

The URL without URL encoding wasn't able to reach the image because it only saw the valid link to be "//test.domain.com/sites/default/files/styles/my_image_style/public/This" because after was already a space and treated everything after it to be another value like the "1x".

Since this issue is connected to the `file_create_url($uri)` function, this function calls `drupal_encode_path($uri)` then passes the URI or path to the `rawurlencode()` PHP function that adds converts spaces into "%20". With this finding, was it intended to forego URL encoding inside the `FileUrlGenerator::generate()` method or it needs to be there?

------
UPDATE
My apologies for wrongly posting this comment. This was an issue I found referenced in the CDN D8 module. Please disregard.

Berdir’s picture

> My apologies for wrongly posting this comment. This was an issue I found referenced in the CDN D8 module. Please disregard.

No worries.

But yes, the goal of this is to mostly just refactor how things work, with minimal functional changes. I was also thinking that this sounds like a bug in the CDN module instead, if it does return absolute URL's then it need to take care of encoding, just like our own code here already does. We can't know if it's already encoded or not, so might end up encoding it twice.

Berdir’s picture

Status: Needs work » Needs review
FileSize
4 KB

Ok, figured out the deal with image styles. They aren't absolute on HEAD, but the trick is that buildUrl() *does* return an absolute URL and that is set in template_preprocess_image_style(), but then template_preprocess_image() makes it relative again, and that didn't work yet. So we're calling generate()/file_create_url() repeatedly on the same thing, that's not confusing at all.

The bug was that generate() didn't call transformRelative() on http/https URL's, that's fixed now and should be good enough for this issue.

Berdir’s picture

Hm, something ate the actual patch.

kim.pepper’s picture

Looking good. Just a few comments:

  1. +++ b/core/lib/Drupal/Core/Asset/CssOptimizer.php
    @@ -257,7 +279,23 @@ public function rewriteFileURI($matches) {
    +      @trigger_error('The file_generator service must be injected into CssOptimizer.');
    

    Do we need to specify the type of error here? Also, do we want to point to the change record?

  2. +++ b/core/lib/Drupal/Core/Asset/JsCollectionRenderer.php
    @@ -17,14 +18,28 @@ class JsCollectionRenderer implements AssetCollectionRendererInterface {
    +      @trigger_error('Calling JsCollectionRenderer::__construct() with the $file_url_generator argument is supported in drupal 8.7.0 and will be required before 9.0.0. See https://www.drupal.org/node/2940031.', E_USER_DEPRECATED);
    

    Needs to be updated to 8.8.0 and follow standard message format.

  3. +++ b/core/lib/Drupal/Core/File/FileUrlGenerator.php
    @@ -0,0 +1,237 @@
    +  public function generateUrl($uri) {
    ...
    +  public function generate($uri) {
    ...
    +  public function generateAbsolute($uri) {
    

    Is there no way to share common logic here? Seems to be a bit of duplicated code.

  4. +++ b/core/lib/Drupal/Core/File/FileUrlGeneratorInterface.php
    @@ -0,0 +1,88 @@
    +  public function generate($uri);
    ...
    +  public function generateUrl($uri);
    

    I think we need clearer documentation on the difference between these two methods. Perhaps its the method names themselves? I'd be interested to hear others opinions on the method names.

  5. +++ b/core/lib/Drupal/Core/Template/TwigExtension.php
    @@ -63,13 +71,20 @@ class TwigExtension extends \Twig_Extension {
    +      @trigger_error('Calling TwigExtension::__construct() with the $file_url_generator argument is supported in drupal 8.7.0 and will be required before 9.0.0. See https://www.drupal.org/node/2940031.', E_USER_DEPRECATED);
    

    Needs to be 8.8.0.

  6. +++ b/core/modules/color/color.module
    @@ -491,7 +493,7 @@ function color_scheme_form_submit($form, FormStateInterface $form_state) {
    +      $css_optimizer = new CssOptimizer(\Drupal::service('file_url_generator'));
    

    Should we create a variable for this outside the foreach?

  7. +++ b/core/modules/file/file.module
    @@ -1452,13 +1453,9 @@ function template_preprocess_file_link(&$variables) {
    -  $variables['#cache']['contexts'][] = 'url.site';
    

    Do we need to add something about the removal of the cache context to the change record?

Berdir’s picture

Thanks for the review. Yes, the deprecation messages definitely need work, didn't bother with that yet.

3. I avoided that for now on purpose. Each method has different cases, some convert from relative to absolute, others from absolute to relative. And there are 4 or so different return cases. The only way to more than 2-3 lines of code between the thre would be to do stuff twice. There's still the option to only have generateUrl(), as WimLeers suggested, but looking at the usage, the majority of calls do not need Url objects, so again, going through that would be extra work.

4. i tried to document the different methods and differences on the class itself, felt like that was fairly clear, but open to suggestions on how to improve it.

6. It is my understand that this is done on purpose, which is also why it doesn't use the service. Each loop sets properties on that object, I don't know if it would share some state if reused. An earlier patch actually just used the service and it didn't cause any test fails.. but it's color.module, I can't imagine our test coverage there is very good.

7. Yeah maybe, still an option to not make all those absolute => relative changes in this issue, but as I mentioned above, then we don't have any real-world usage of the new method.. we could do generateUrl()->setAbsolute() I suppose, but then we're again back to the topic of transforming it to relative only to make it absolute again.

kim.pepper’s picture

4. Maybe generate() can be generatePath() as it always returns a relative path?

kim.pepper’s picture

Fixed the deprecation messages and some code sniff issues to keep the ball rolling.

kim.pepper’s picture

4. Maybe generate() can be generatePath() as it always returns a relative path?

I was going to suggest generateRelative() too, but then read the docblock and it says this can be altered by CDN etc. to return an absolute URL. So I think this is ok for now.

kim.pepper’s picture

Re-roll of #112

Status: Needs review » Needs work

The last submitted patch, 114: 2669074-114.patch, failed testing. View results

kim.pepper’s picture

Status: Needs work » Needs review
FileSize
172.92 KB
3.1 KB

Fix usage of deprecated functions in recent commits.

Status: Needs review » Needs work

The last submitted patch, 116: 2669074-116.patch, failed testing. View results

kim.pepper’s picture

Wondering if this fail is the due to the changes in #102?

kim.pepper’s picture

Status: Needs work » Needs review
FileSize
173.25 KB
647 bytes

Looks like it. Here's a fix for MediaEmbedFilterTest.

jibran’s picture

Here is a walk by review. A lot of points are related to DIC. If those parts are deliberately left for now then please ignore parts of the review.

  1. +++ b/core/modules/file/file.module
    @@ -1504,7 +1502,7 @@ function template_preprocess_file_link(&$variables) {
    -  $variables['link'] = \Drupal::l($link_text, Url::fromUri($url, $options));
    +  $variables['link'] = Link::fromTextAndUrl($link_text, $url->setOptions($options));
    

    Unrelated change.

  2. +++ b/core/modules/file/src/Plugin/Field/FieldFormatter/BaseFieldFileFormatterBase.php
    @@ -47,9 +46,7 @@ public function viewElements(FieldItemListInterface $items, $langcode) {
    +      $url = \Drupal::service('file_url_generator')->generateUrl($items->getEntity()->getFileUri());
    

    The service should be injected.

  3. +++ b/core/modules/file/src/Plugin/Field/FieldFormatter/FileUriFormatter.php
    @@ -51,10 +51,7 @@ public function settingsForm(array $form, FormStateInterface $form_state) {
    +      $value = \Drupal::service('file_url_generator')->generate($value);
    

    The service should be injected.

  4. +++ b/core/modules/file/src/Plugin/views/field/File.php
    @@ -64,13 +64,7 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
    +      $this->options['alter']['url'] = \Drupal::service('file_url_generator')->generateUrl($this->getValue($values, 'uri'));
    

    The service should be injected.

  5. +++ b/core/modules/image/src/Plugin/Field/FieldFormatter/ImageFormatter.php
    @@ -198,18 +198,12 @@ public function viewElements(FieldItemListInterface $items, $langcode) {
    +    $file_url_generator = \Drupal::service('file_url_generator');
    

    The service should be injected.

  6. +++ b/core/modules/media/src/Plugin/Filter/MediaEmbed.php
    @@ -214,7 +214,7 @@ protected function renderMedia(MediaInterface $media, $view_mode, $langcode) {
    +      '#uri' => \Drupal::service('file_url_generator')->generate('core/modules/media/images/icons/no-thumbnail.png'),
    

    The service should be injected.

  7. +++ b/core/tests/Drupal/KernelTests/Core/File/StreamWrapperTest.php
    @@ -99,9 +99,11 @@ public function testUriFunctions() {
    +    $file_url_generator = \Drupal::service('file_url_generator');
    

    This should be converted to $this->container->get('file_url_generator');

Wim Leers’s picture

Status: Needs review » Needs work

Impressive work, @Berdir and @kim.pepper! 👏🙏

Another thing I noticed again in kernel tests is how messed up their public/base path

This has always confused me too and I always wondered why it was that way. Never found the time to dig in to that either.

Ok, figured out the deal with image styles.

Nice detective work! This is definitely a case of things having gotten more spread out and confusing over time.

#110.3: the benefit of having only generateUrl() which returns an Url object would be that you would call ->setAbsolute()->toString() on it to generate an absolute file URL and ->toString() to generate a relative URL. At least, that's the theory. That's how it works for non-file URLs. It's more complex than that.


  1. +++ b/core/core.services.yml
    @@ -372,6 +372,9 @@ services:
    +    arguments: [ '@stream_wrapper_manager', '@request_stack', '@module_handler']
    

    Übernit: [ ' should be ['.

  2. +++ b/core/includes/theme.inc
    @@ -791,8 +794,12 @@ function template_preprocess_links(&$variables) {
     function template_preprocess_image(&$variables) {
    +
    +  /** @var \Drupal\Core\File\FileUrlGeneratorInterface $file_url_generator */
    

    Übernit: extraneous blank line.

  3. +++ b/core/lib/Drupal/Core/File/FileUrlGenerator.php
    @@ -0,0 +1,235 @@
    +    // Allow the URI to be altered, e.g. to serve a file from a CDN or static
    +    // file server.
    +    $this->moduleHandler->alter('file_url', $uri);
    

    I think we should consider deprecating hook_file_url_alter() too. Once this is a service, we can ask that modules decorate the file_url_generator service instead!

  4. +++ b/core/lib/Drupal/Core/File/FileUrlGenerator.php
    @@ -0,0 +1,235 @@
    +  public function generate($uri) {
    ...
    +  public function generateAbsolute($uri) {
    

    See my reply to #110.3. Do we think that is realistic? If so, then all this complex code would only exist once instead of thrice!

kim.pepper’s picture

Status: Needs work » Needs review
FileSize
182.44 KB
17.9 KB

Thanks for the reviews!

Re: #120

  1. Fixed
  2. Fixed
  3. Fixed
  4. Fixed
  5. Fixed
  6. Fixed
  7. Fixed

Re: #121

  1. Fixed
  2. Fixed
  3. Follow up?
  4. I'll let @Berdir answer this. :-)
Wim Leers’s picture

I think you're right — that should be a follow-up. Let's see if @Berdir agrees with that, then we can create that follow-up :)

Status: Needs review » Needs work

The last submitted patch, 122: 2669074-122.patch, failed testing. View results

kim.pepper’s picture

Status: Needs work » Needs review
FileSize
183.08 KB
841 bytes

#120.1 I'm re-adding this change, as we moved from a string to a Url object, and needed to update this line anyway.

$variables['link'] = Link::fromTextAndUrl($link_text, $url->setOptions($options));
Berdir’s picture

Thanks for the review, if that's all you found then that sounds like you're generally on board with the direction, specifically also all the cache context/output changes with relative/absolute URL's?

> See my reply to #110.3. Do we think that is realistic? If so, then all this complex code would only exist once instead of thrice!

I think we'd need to do some profiling/benchmark to see how much overhead it is to build a Url object from a string only to get the string again. With render caching maybe a bit less so on average, but this function is called quite often.

I'll also try to count how many times we call each function now, to have an idea on how common it is, although a lot of the calls are in tests in core.

Personally, I don't think having a bit more code is that relevant, it should be pretty well covered with tests, I'd rather have a good DX for developers using this API. And in this regard, I think the question is what's easier: having a single method but having to call ->toString() and sometimes absolute, although fewer and fewer times, or multiple methods, so you need to figure out which is easier to use. Again, I'll go through the usages to figure out how a single-method approach would look like. I'll also see if someone else has an opinion on this.

Now that it works, I could also try to see if it helps to reuse some parts as protected methods, that would also help if we go the route of having decorators instead of the hook (I agree that a follow-up makes sense, only downside is that we'd then no longer need the injected service later on, but it is last in the constructor and easy to remove then), as having those helper methods might help alternative implementations too. Certainly for the decorator approach a single method would be better, otherwise it is a lot of work ot override them all.

goodboy’s picture

    $scheme = $request->getScheme();
    $port = $request->getPort() ?: 80;
    if (('http' == $scheme && $port == 80) || ('https' == $scheme && $port == 443)) {
      $http_host = $host;
    }
    else {
      $http_host = $host . ':' . $port;
    }

This code from transformRelative() potencially may set $http_host to 'https://example.com:80' (when $scheme is 'https' and getPort() returns NULL).
So, I propose replace this code to this one.

    $scheme = $request->getScheme();
    $port = $request->getPort();
    if (!$port || ('http' == $scheme && $port == 80) || ('https' == $scheme && $port == 443)) {
      $http_host = $host;
    }
    else {
      $http_host = $host . ':' . $port;
    }
Wim Leers’s picture

if that's all you found then that sounds like you're generally on board with the direction, specifically also all the cache context/output changes with relative/absolute URL's?

I missed that. 😊 I was focusing on the overall structure, the pattern of changes. Thanks for pointing it out! 🙏 (It's also not yet in the issue summary or change record. If we were to stop dealing with cache contexts altogether, that would be crucial information in both.)

  1. +++ b/core/lib/Drupal/Core/File/FileUrlGeneratorInterface.php
    @@ -0,0 +1,87 @@
    +   * Creates a root-relative web-accessible URL.
    ...
    +   * Creates a root-relative web-accessible URL.
    ...
    +   * Creates a root-relative web-accessible URL.
    

    It does not make sense for all three to have the exact same one-line description.

  2. generateAbsolute() would need to bubble the url.site cache context somehow. This is another reason for me to remove generate() (for relative file URLs) and generateAbsolute() (for absolute file URLs), and only keep generateUrl(). Because then $file_url_generator->generateUrl()->toString(TRUE) would result in a GeneratedUrl object without cache contexts, and $file_url_generator->generateUrl()->setAbsolute()->toString(TRUE) would result in a GeneratedUrl with the url.site cache context.

I think we'd need to do some profiling/benchmark to see how much overhead it is to build a Url object from a string only to get the string again. With render caching maybe a bit less so on average, but this function is called quite often.

Very good point :)

Personally, I don't think having a bit more code is that relevant, it should be pretty well covered with tests, I'd rather have a good DX for developers using this API.

+1

I think the question is what's easier

Agreed! This is exactly why I proposed this. Because it means that the DX of interacting with non-file URLs and file URLs would effectively become the same. Generating relative and absolute non-file and file URLs would use the same pattern.

Berdir’s picture

I missed that. 😊 I was focusing on the overall structure, the pattern of changes. Thanks for pointing it out! 🙏 (It's also not yet in the issue summary or change record. If we were to stop dealing with cache contexts altogether, that would be crucial information in both.)

I'm not sure we're talking about the same thing here. It's not an API change in the file url API, it's just that the new API allows us to create non-absolute Url objects, which in turn allows us to remove the url.site cache context in places that use it:

+++ b/core/modules/file/file.module
@@ -1468,13 +1469,10 @@ function template_preprocess_file_link(&$variables) {
 
-  // @todo Wrap in file_url_transform_relative(). This is currently
-  // impossible. As a work-around, we currently add the 'url.site' cache context
-  // to ensure different file URLs are generated for different sites in a
-  // multisite setup, including HTTP and HTTPS versions of the same site.
-  // Fix in https://www.drupal.org/node/2646744.
-  $url = $file->createFileUrl(FALSE);
-  $variables['#cache']['contexts'][] = 'url.site';
+  $file_entity = ($file instanceof File) ? $file : File::load($file->fid);
+  /** @var \Drupal\Core\File\FileUrlGeneratorInterface $file_url_generator */
+  $file_url_generator = \Drupal::service('file_url_generator');
+  $url = $file_url_generator->generateUrl($file_entity->getFileUri());
 

+++ b/core/modules/file/src/Plugin/Field/FieldFormatter/BaseFieldFileFormatterBase.php
@@ -59,17 +110,7 @@ public function viewElements(FieldItemListInterface $items, $langcode) {
           '#type' => 'link',
           '#title' => $view_value,
-          '#url' => Url::fromUri($url),
-          // @todo Remove the 'url.site' cache context by using a relative file
-          // URL (file_url_transform_relative()). This is currently impossible
-          // because #type => link requires a Url object, and Url objects do not
-          // support relative URLs: they require fully qualified URLs. Fix in
-          // https://www.drupal.org/node/2646744.
-          '#cache' => [
-            'contexts' => [
-              'url.site',
-            ],
-          ],
+          '#url' => $url,
         ];

+++ b/core/modules/file/src/Plugin/Field/FieldFormatter/FileUriFormatter.php
@@ -51,10 +51,7 @@ public function settingsForm(array $form, FormStateInterface $form_state) {
     $value = $item->value;
     if ($this->getSetting('file_download_path')) {
-      // @todo Wrap in file_url_transform_relative(). This is currently
-      // impossible. See BaseFieldFileFormatterBase::viewElements(). Fix in
-      // https://www.drupal.org/node/2646744.
-      $value = file_create_url($value);
+      $value = $this->fileUrlGenerator->generate($value);
     }

+++ b/core/modules/file/src/Plugin/views/field/File.php
@@ -64,13 +101,7 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
     if (!empty($this->options['link_to_file']) && $data !== NULL && $data !== '') {
       $this->options['alter']['make_link'] = TRUE;
-      // @todo Wrap in file_url_transform_relative(). This is currently
-      // impossible. As a work-around, we could add the 'url.site' cache context
-      // to ensure different file URLs are generated for different sites in a
-      // multisite setup, including HTTP and HTTPS versions of the same site.
-      // But unfortunately it's impossible to bubble a cache context here.
-      // Fix in https://www.drupal.org/node/2646744.
-      $this->options['alter']['path'] = file_create_url($this->getValue($values, 'uri'));
+      $this->options['alter']['url'] = $this->fileUrlGenerator->generateUrl($this->getValue($values, 'uri'));
     }

+++ b/core/modules/image/src/Plugin/Field/FieldFormatter/ImageFormatter.php
@@ -200,16 +216,9 @@ public function viewElements(FieldItemListInterface $items, $langcode) {
         $image_uri = $file->getFileUri();
-        // @todo Wrap in file_url_transform_relative(). This is currently
-        // impossible. As a work-around, we currently add the 'url.site' cache
-        // context to ensure different file URLs are generated for different
-        // sites in a multisite setup, including HTTP and HTTPS versions of the
-        // same site. Fix in https://www.drupal.org/node/2646744.
-        $url = Url::fromUri(file_create_url($image_uri));
-        $cache_contexts[] = 'url.site';
+        $url = $this->fileUrlGenerator->generateUrl($image_uri);
       }

all this stuff. So basically most if not all explicit references to #2646744: \Drupal\Core\Url does not accept root-relative (file) URLs, making it impossible to let LinkGenerator create root-relative file URL links. While this issue doesn't actually solve that problem, it makes it unecessary to solve for these use cases, because we know we longer generate a root-relative URL in the first place and we can reliable deal with it. I've come to the conclusion that solving that other issue generically is hard and unreliable, because it is literally impossible to know for "/admin/foo/bar" whether the /admin part is now the base path or part of the drupal internal route, it could theoretically be that Drupal is installed in /admin, so it would be admin/admin/content ;)

Still need to do what I said in my last comment, will also try to update the issue summary, althoug not quite sure yet about this part and how to document that.

Berdir’s picture

Reroll due to a few conflicts.

An earlier version reverted the cleanup of $file/$file_entity in template_preprocess_file_link() that happened in a separate issue, making that simpler again.

Now looking into the issue summary/usage of the new methods.

Berdir’s picture

Issue summary: View changes

Updated the issue summary with the option options and numbers.

Berdir’s picture

Ah, that was a more subtle merge conflict.

catch’s picture

Option 2 (only ::generateUrl()) does seem best here. I'm not a big fan of having to call ->toString() in general, but it's an issue we already have and could sort out later if we think of something better.

Berdir’s picture

Ok, then, here goes nothing.

This removes generate() and generateUrl() and updates all calls to them. Was able to do some regex based search & replace for the heavy lifting: file(.+)->generate\(([^\)]+)\) => file$1->generateUrl($2)->toString() and a similar one for generateAbsolute() worked for many cases, with some special cases that I updated manually.

Also went through all calls as part of that and found a few ones that I could change to $file->createFileUrl() instead, a few left-over transformRelative() calls and other cleanups. Will comment on some of them in a self-review after I post the patch.

Lets see how much I missed or incorrectly updated.

Berdir’s picture

Assigned: Berdir » Unassigned
  1. +++ b/core/includes/file.inc
    @@ -217,7 +217,7 @@ function file_stream_wrapper_uri_normalize($uri) {
     function file_create_url($uri) {
       @trigger_error('file_create_url() is deprecated in drupal:8.8.0 and will be removed before drupal:9.0.0. Use \Drupal\Core\File\FileUrlGenerator::generate() instead. See https://www.drupal.org/node/2940031.', E_USER_DEPRECATED);
    -  return \Drupal::service('file_url_generator')->generateAbsolute($uri);
    +  return \Drupal::service('file_url_generator')->generateUrl($uri)->setAbsolute()->toString();
    

    the absolute version is definitely quite verbose, but that's mostly used in tests as shown in the issue summary.

  2. +++ b/core/lib/Drupal/Core/File/FileUrlGenerator.php
    @@ -87,7 +87,7 @@ public function generateUrl($uri) {
             // file. Therefore, return the urlencoded URI.
             $options = UrlHelper::parse($uri);
    -        return Url::fromUri('base:', UrlHelper::encodePath($options['path'], $options));
    +        return Url::fromUri('base:' . UrlHelper::encodePath($options['path'], $options));
           }
    

    so much for test coverage...

  3. +++ b/core/lib/Drupal/Core/File/FileUrlGeneratorInterface.php
    @@ -14,42 +14,21 @@
      *
    - * Separate methods are provided to provide absolute and relative URLs as well
    - * as plain strings or Url objects, depending on the requirements. In general,
    - * it is recommended to always use relative URLs unless absolute URL's are
    - * required.
    + * Relative URLs:
    + * @code
    + * $file_url_generator->generateUrl($uri)->toString()
    + * @endcode
    + *
    + * Absolute URLs:
    + * @code
    + * $file_url_generator->generateUrl($uri)->setAbsolute()->toString()
    + * @endcode
    + *
    + * In general, it is recommended to always use relative URLs unless absolute
    + * URL's are required.
      */
     interface FileUrlGeneratorInterface {
    

    Updated the class docs like this. generateUrl() likely still/also needs some improvements now that it is the only method.

    Maybe this part could also be on the method now instead.

  4. +++ b/core/modules/media/src/Plugin/Filter/MediaEmbed.php
    @@ -112,21 +104,14 @@ class MediaEmbed extends FilterBase implements ContainerFactoryPluginInterface,
         $this->loggerFactory = $logger_factory;
    -    if (!$file_url_generator) {
    -      @trigger_error('Calling MediaEmbed::__construct() without the $file_url_generator argument is deprecated in drupal:8.8.0 and the $file_url_generator argument will be required in drupal:9.0.0. See https://www.drupal.org/node/2940031', E_USER_DEPRECATED);
    -      $file_url_generator = \Drupal::service('file_url_generator');
    -    }
    -    $this->fileUrlGenerator = $file_url_generator;
       }
     
       /**
    @@ -230,7 +215,7 @@ protected function renderMedia(MediaInterface $media, $view_mode, $langcode) {
    
    @@ -230,7 +215,7 @@ protected function renderMedia(MediaInterface $media, $view_mode, $langcode) {
       protected function renderMissingMedia() {
         return [
           '#theme' => 'image',
    -      '#uri' => $this->fileUrlGenerator->generate('core/modules/media/images/icons/no-thumbnail.png'),
    +      '#uri' => 'core/modules/media/images/icons/no-thumbnail.png',
           '#width' => 180,
           '#height' => 180,
    

    template_preprocess_image() expects a URI, not a generated URL, so there is actually no need to call generate here, so we can remove the service from this (was added in this pat ch). The constructor BC thing wouldn't have been necessary anyway as this is new in 8.8.

  5. +++ b/core/tests/Drupal/KernelTests/Core/Theme/ImageTest.php
    @@ -88,9 +88,7 @@ public function testThemeImageWithSrc() {
     
         // Make sure the src attribute has the correct value.
    -    /** @var \Drupal\Core\File\FileUrlGeneratorInterface $this->fileUrlGenerator */
    -    $this->fileUrlGenerator = $this->fileUrlGenerator;
    -    $this->assertRaw($this->fileUrlGenerator->generate($image['#uri']), 'Correct output for an image with the src attribute.');
    +    $this->assertRaw($this->fileUrlGenerator->generateUrl($image['#uri'])->toString(), 'Correct output for an image with the src attribute.');
    

    I don't even know what this was...

  6. +++ b/core/tests/Drupal/KernelTests/Core/Theme/ImageTest.php
    @@ -118,7 +116,7 @@ public function testThemeImageWithSrcsetMultiplier() {
     
         // Make sure the srcset attribute has the correct value.
    -    $this->assertRaw($this->fileUrlGenerator->transformRelative($this->fileUrlGenerator->generate($this->testImages[0])) . ' 1x, ' . $this->fileUrlGenerator->transformRelative($this->fileUrlGenerator->generate($this->testImages[1])) . ' 2x', 'Correct output for image with srcset attribute and multipliers.');
    +    $this->assertRaw($this->fileUrlGenerator->generateUrl($this->testImages[0])->toString() . ' 1x, ' . $this->fileUrlGenerator->generateUrl($this->testImages[1])->toString() . ' 2x', 'Correct output for image with srcset attribute and multipliers.');
       }
     
    

    the transformRelative() was a bogus left-over, generate() was already relative.

    Note that we still need transformRelative(), I think the only/main use case for it is \Drupal\image\Entity\ImageStyle::buildUrl(), which has no option to generate a relative URL.

Status: Needs review » Needs work

The last submitted patch, 134: 2669074-134.patch, failed testing. View results

Berdir’s picture

Status: Needs work » Needs review
FileSize
183.12 KB
183.12 KB
14.36 KB

Ok, part of those fails were because I forgot about mocking in unit test (needed to get a bit creative there..) and also some array_map callbacks.

Others were because because of URL parsing and encoding. The unfortunate thing is that the for managed files, we mostly rely on \Drupal\Core\StreamWrapper\StreamWrapperInterface::getExternalUrl() and that still returns an absolute, url-encoded string, and we have to convert that back into relative *and* url-decode it again.

Another thing is how much overhead this adds specifically for image style URL's:
* First, template_preprocess_image_style() calls \Drupal\image\Entity\ImageStyle::buildUrl()
* That calls \Drupal\Core\File\FileUrlGeneratorInterface::generateUrl(), which calls \Drupal\Core\StreamWrapper\PublicStream::getExternalUrl(), which encodes the URL, then back to generateUrl(), which makes it decodes it again and makes it relative again. Then we return it, call setAbsolute() on it and make it absolute again. Then we convert it to a string. That is set as uri for #theme image.
* Then we get to template_preprocess_image(), this calls *again* generateUrl(), this time with an absolute URL. That means we have to parse the URL *again*, decode it *again*, transform it to relative *again* (also just realized that we definitely have a bug here, if the URL really is external then this is going to create a nice mess, so we need to add another isExternal() check after transforming. Aaand then we again create a Url object, and then we create a string again.

That is positively crazy. Makes me think about creating a "this is madness! - No, this is Drupal!" meme) And makes me reconsider changing my opinion in regards to option B. Maybe we need to first clean up image style URL generation, the interplay between image_style and image templates and a relative version of getExternal() (See also #2867355: file_create_url() causes LogicException when used with certain stream wrappers ) before forcing everything 7 times through Url objects and back... We could go back to option a) and deprecate the alternatives in 9.something?

Anyway, here are the test fixes that I did so far.

Berdir’s picture

  1. +++ b/core/lib/Drupal/Core/Template/TwigExtension.php
    @@ -150,6 +150,7 @@ public function setDateFormatter(DateFormatterInterface $date_formatter) {
       public function getFunctions() {
    +    $file_url_genrator = $this->fileUrlGenerator;
         return [
    

    genrator, nice. is that some kind of dinosaur? :)

  2. +++ b/core/modules/editor/tests/src/Kernel/EditorFileReferenceFilterTest.php
    @@ -76,21 +76,22 @@ public function testEditorFileReferenceFilter() {
         $input = '<img src="llama.jpg" data-entity-type="file" data-entity-uuid="' . $uuid . '" />';
    -    $expected_output = '<img src="/' . $this->siteDirectory . '/files/llama.jpg" data-entity-type="file" data-entity-uuid="' . $uuid . '" />';
    +    // @todo Have a better public file URL in kernel tests that does not contain vfs:/.
    +    $expected_output = '<img src="/' . str_replace('vfs:', 'vfs%3A', $this->siteDirectory) . '/files/llama.jpg" data-entity-type="file" data-entity-uuid="' . $uuid . '" />';
         $output = $test($input);
    

    Also forgot about the fact that the weird vfs: in kernel tests URL's gets url-encoded as a result of this.

    As I said, I think we're not quite ready yet for Url-only..

Status: Needs review » Needs work

The last submitted patch, 137: 2669074-137.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

catch’s picture

Another thing is how much overhead this adds specifically for image style URL's:
* First, template_preprocess_image_style() calls \Drupal\image\Entity\ImageStyle::buildUrl()
* That calls \Drupal\Core\File\FileUrlGeneratorInterface::generateUrl(), which calls \Drupal\Core\StreamWrapper\PublicStream::getExternalUrl(), which encodes the URL, then back to generateUrl(), which makes it decodes it again and makes it relative again. Then we return it, call setAbsolute() on it and make it absolute again. Then we convert it to a string. That is set as uri for #theme image.
* Then we get to template_preprocess_image(), this calls *again* generateUrl(), this time with an absolute URL. That means we have to parse the URL *again*, decode it *again*, transform it to relative *again* (also just realized that we definitely have a bug here, if the URL really is external then this is going to create a nice mess, so we need to add another isExternal() check after transforming. Aaand then we again create a Url object, and then we create a string again.

lol what a mess!

We could go back to option a) and deprecate the alternatives in 9.something?

That's a good plan - let's open an issue for image style generation. If we can create a Url object once and pass it around that'd obviously be best but seems like we're very far from that right now.

Wim Leers’s picture

  • #129 Oooooooohh! I totally missed that! Thanks for spelling it out so clearly 🙏😊 Yes, I'm absolutely in favor of using relative URLs wherever possible. That's also what those @todos said, and I added them. Thanks for making this happen! 🥳
  • #131: Thanks for that excellent issue summary update, and especially for including your thought process and your evolving opinion 👍
  • I'm happy to see @catch agrees that option 2 (only generateUrl()) is preferable. Not having to learn many different APIs to generate and manipulate various kinds of URLs would be great. 👉 #2491981: There are too many ways to generate URLs and links 🙈🚨
  • #135.2 so much for test coverage... 😁
  • #137:
    1. +++ b/core/lib/Drupal/Core/File/FileUrlGenerator.php
      @@ -87,17 +87,19 @@ public function generateUrl($uri) {
      +      return Url::fromUri('base:' . $this->transformRelative(urldecode($options['path']), FALSE), $options);
      ...
      +      $url = Url::fromUri('base:' . $this->transformRelative(urldecode($wrapper->getExternalUrl()), FALSE));
      

      I think this should use rawurldecode(). Unlike urldecode() it doesn't decode + to the space character.

      See https://www.php.net/manual/en/function.rawurldecode.php vs https://www.php.net/manual/en/function.urldecode.php.

    2. +++ b/core/modules/editor/tests/src/Kernel/EditorFileReferenceFilterTest.php
      @@ -76,21 +76,22 @@ public function testEditorFileReferenceFilter() {
      +    // @todo Have a better public file URL in kernel tests that does not contain vfs:/.
      

      Yeah this has long bothered me too. It's great that we can use a virtual file system, but the way we've adopted it in our tests is too confusing.

    3. The unfortunate thing is that the for managed files, we mostly rely on [getExternalUrl]

      +1

    4. Another thing is how much overhead this adds specifically for image style URL's:

      I think the root cause here is the stream wrapper getExternalUrl() returning a string. If that would return a Url object, then the entire problem would disappear. This is similar but different to what you suggest later in your comment. Thoughts?

    5. Makes me think about creating a "this is madness! - No, this is Drupal!" meme)

      — Berdir

      🤣

Berdir’s picture

> I think the root cause here is the stream wrapper getExternalUrl() returning a string. If that would return a Url object, then the entire problem would disappear. This is similar but different to what you suggest later in your comment. Thoughts?

That's one part, the other is that ImageStyle::buildUrl() also needs to be able to return a relative URL/Url object. Both would need to be resolved to make only returning Url objects a sane option IMHO. And that wouldn't be completely enough, because we still have the problem that template_preprocess_image_style creates a URL that template_preprocess_image() then runs through generateUrl() again, so parsing and generating it again. Not sure what we can do there without BC breaks.

Also, as visible in the remaining test fails, there are still problems, one is that apparently, despite using base:, we get "update.php/" injected into the URL while running updates. Didn't check the others yet.

Wim Leers’s picture

the other is that ImageStyle::buildUrl() also needs to be able to return a relative URL/Url object. Both would need to be resolved to make only returning Url objects a sane option IMHO.

True.

template_preprocess_image_style() … template_preprocess_image()

True.

Not sure what we can do there without BC breaks.

I think we should try. We may need to add some annoying if-tests to detect the presence of a Url object instead of a string in a few places to make it happen.

Berdir’s picture

> I think we should try.

Yes we should try but IMHO not in this issue. This is already almost a 200kb patch. I will try to look at the remaining failures to figure out what else is broken when going through URL, but I do think we should do what I wrote above: "We could go back to option a) and deprecate the alternatives in 9.something?"

> We may need to add some annoying if-tests to detect the presence of a Url object instead of a string in a few places to make it happen.

That's exactly the point, if we have to add those checks then that implies that we broke BC? There are 3 layers to think through (getExternalUrl => ImageStyle::buildUrl() => templates/preprocess) the whole thing is going to be tricky and it doesn't seem viable to get done in 8.8, which means it will happen in 9.1+.

kim.pepper’s picture

Status: Needs work » Needs review
FileSize
182.11 KB

The deadline for 8.8.0-alpha1 is approaching, and this is one of the last remaining file.inc deprecations. Would be great to get it in!

Here's a re-roll of #137 to keep that ball rolling... 🏀

Status: Needs review » Needs work

The last submitted patch, 145: 2669074-145.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

voleger’s picture

Status: Needs work » Needs review
FileSize
185.17 KB
6.39 KB

Replaced last calls to the deprecated functions. Fixed CS issue.

Status: Needs review » Needs work

The last submitted patch, 147: 2669074-147.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

Berdir’s picture

Thanks for the re-rolls, but I think we first need a decision on the direction here. If we go with my suggestion (A and then work towards B in 9.x) then we'll need to go back to #132 and continue from there. IMHO that's the only chance to get this into D8.

Note that if this does in fact become the only non-deprecated function(s) in file.inc for D9, then I think we can just move them to common.inc IMHO. Would be nice, especially for the caching improvements, but we'll survive if it doesn't happen.

andypost’s picture

Status: Needs work » Needs review
FileSize
185.11 KB

Re-roll of #132 - looks this is only chance for d9 now

andypost’s picture

FileSize
3.31 KB
181.8 KB

there's no need for that now

Wim Leers’s picture

Thanks for the re-rolls, but I think we first need a decision on the direction here.

Who are we waiting for here?

IIRC and AFAICT @catch was also in favor of the approach in #137

IMHO that's the only chance to get this into D8.

Well in that case … #132 still is a step forward.

kim.pepper’s picture

As far as I can tell #151 is option a) and the approach in #132?

Berdir’s picture

Yeah, it is, per #151, and tests are passing.

So looks like what this needs is a hopefully finally review and someone bold enough to RTBC? there have been some reviews from Wim, but I'm not sure how in depth they were, it certainly was unusually short for a Wim-review, used to more nitpicks on documentation and stuff :)

That said, this is a 180kb patch, deprecates a pretty common API function (38 pages of results on http://grep.xnddx.ru/search?text=file_create_url, I *think* the theme with the core code has been removed by now?), so maybe before we try to push it, we could get info on whether this is still viable basically a day before the alpha commit deadline? On the plus side, it is the last thing in file.inc and isn't just a 1:1 deprecation for the sake of it, it provides solutions for some pretty ugly file_create_urlurl/Url object problems resulting in non-optimal caching. See #129. I'll ask in Slack.

larowlan’s picture

  1. +++ b/core/lib/Drupal/Core/Asset/CssOptimizer.php
    @@ -258,7 +280,23 @@ public function rewriteFileURI($matches) {
    +    if (!$this->fileUrlGenerator) {
    +      @trigger_error('Calling CssOptimizer::__construct() without the $file_url_generator argument is deprecated in drupal:8.8.0. The $file_url_generator argument will be required in drupal:9.0.0. See https://www.drupal.org/node/2940031', E_USER_DEPRECATED);
    +      $this->fileUrlGenerator = \Drupal::service('file_url_generator');
    +    }
    +    return $this->fileUrlGenerator;
    

    we set this in the constructor- under what cases would it not be set?

    are we allowing for subclasses that don't call the parent because it was previously empty?

  2. +++ b/core/lib/Drupal/Core/File/FileUrlGenerator.php
    @@ -0,0 +1,235 @@
    +      elseif (mb_substr($uri, 0, 1) == '/') {
    ...
    +      else {
    ...
    +    elseif ($scheme == 'http' || $scheme == 'https' || $scheme == 'data') {
    ...
    +    elseif ($wrapper = $this->streamWrapperManager->getViaUri($uri)) {
    

    none of these need to be elseif because we return, would simplify things a bit 🤔

  3. +++ b/core/lib/Drupal/Core/File/FileUrlGenerator.php
    @@ -0,0 +1,235 @@
    +      else {
    

    same here this else isn't needed because of the early return

  4. +++ b/core/lib/Drupal/Core/File/FileUrlGenerator.php
    @@ -0,0 +1,235 @@
    +    elseif ($scheme == 'http' || $scheme == 'https' || $scheme == 'data') {
    ...
    +    elseif ($wrapper = $this->streamWrapperManager->getViaUri($uri)) {
    

    and here

  5. +++ b/core/lib/Drupal/Core/File/FileUrlGenerator.php
    @@ -0,0 +1,235 @@
    +        $base_url = $this->requestStack->getCurrentRequest()->getSchemeAndHttpHost() . base_path();
    

    getCurrentRequest can return NULL (according to its docs), do we need to allow for that to avoid fatals?

  6. +++ b/core/lib/Drupal/Core/File/FileUrlGeneratorInterface.php
    @@ -0,0 +1,87 @@
    +   *   For a local URL (matching domain), a base-relative Url object containing a
    

    ubernit: > 80

  7. +++ b/core/lib/Drupal/Core/File/FileUrlGeneratorInterface.php
    @@ -0,0 +1,87 @@
    +  public function generateUrl($uri);
    

    should this be named generateUrlObject for clarity?

  8. +++ b/core/modules/media/tests/src/Kernel/MediaEmbedFilterTest.php
    @@ -104,7 +104,7 @@ public function providerTestBasics() {
    -          ->setCacheContexts(['url.site', 'user.permissions'])
    +          ->setCacheContexts(['user.permissions'])
               ->setCacheMaxAge(Cache::PERMANENT),
    

    out of scope?

  9. +++ b/core/modules/responsive_image/tests/src/Functional/ResponsiveImageFieldDisplayTest.php
    @@ -43,6 +50,8 @@ class ResponsiveImageFieldDisplayTest extends ImageFieldTestBase {
     
    +    $this->fileUrlGenerator = $this->container->get('file_url_generator');
    

    i was led to believe we were supposed to use \Drupal not $this->container in tests? 🤔 maybe there's other existing case in this test I can't see in the patch?

Berdir’s picture

Status: Needs review » Needs work

Thanks for the review! I'll take that, although I was just asking for this being feasible still for 8.8, but I guess that means... yes?

1. Yes, exactly, it's also a pretty special class that's e.g. in color.module instantiated directly, so who knows what kind of weird things people are doing with it. There is one subclass in s3fs for example: http://grep.xnddx.ru/search?text=extends%20CssOptimizer

2-4. Yeah, not quite sure what our standards are there, file_create_url() does the same basically, with fewer conditions though. Happy to change, I think sometimes it helps to make it explicit that they are all alternative cases.

5. This follows file_url_transform_relative(), which hides this behind \Drupal::request(). Wasn't a problem there but happy to add a check, probably just return the string then?

7. Don't care that much, UrlObject is I think a but less common, but exists too, e.g. \Drupal\Core\Menu\MenuLinkInterface::getUrlObject() (but \Drupal\link\LinkItemInterface::getUrl()).

8. Well.. technically, yes. But see #129 on how this new API allows us to generate relative Url's in various places now, and then removing the url.site cache context is the result of that and the tests have to adjust. This tests a rendered media through the embed filter, which in turn uses the standard image formatter. We would have to change all that to still generate absolute urls and make that change in a follow-up. But in a way, that is the most interesting part of this patch. But happy to remove all that so if you think that makes it easier to get in.

9. I don't know. I prefer that too, but I think also saw a comment from @alexpott that this->container is fine/better in kernel tests, although I don't quite get why it would be better :)

kim.pepper’s picture

I think @Berdir has addressed most of @larowlans feedback. Anyone brave enough to RTBC? :-)

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

larowlan’s picture

Re #156 thanks Berdir, that all makes sense

Berdir’s picture

Status: Needs work » Needs review

Ok, setting back to needs review then. There is the nitpick from point 6 but maybe that can be fixed on commit or together with things from another review. I know that Wim has been trying to review this yesterday, so maybe he won't be pulled it quite as many directions today.

Berdir’s picture

Issue summary: View changes
Wim Leers’s picture

Thanks for the issue summary update, @Berdir, that helps 🙏

Direction: "option 1"

I still think option 2 is much preferable over option 1, because it makes for a much more consistent, more easily learnable developer experience. But I agree that even option 1 is a big step forward, and that for reasons of patch scope and time pressure, shipping option 1 with Drupal 8.8 is much preferable than shipping option 2 in Drupal 9.1. Option 2 becomes more feasible to achieve in the future.

So +1 to latest patch wrt concept/direction.

But we're so close to the 8.8.0-alpha1 deadline! 😨

Yes, but … as @Berdir wrote in #154:

On the plus side, it is the last thing in file.inc and isn't just a 1:1 deprecation for the sake of it, it provides solutions for some pretty ugly file_create_urlurl/Url object problems resulting in non-optimal caching. See #129.

CDN integration module

The module most obviously impacted by this change is https://www.drupal.org/project/cdn, which I maintain. I rolled a patch to verify that the code introduced here allows that module to A) continue to work + pass tests, B) ideally allow it to use the service decorator pattern. Conclusion: ✅ and ✅:) See #3087413: [upstream] Validate new FileUrlGenerator API in a future Drupal core release for this. 🥳🥳🥳

Patch review

  1. 👍For now contrib/custom code transitioning from the alter hook to a service decorator is entirely optional, because hook_file_url_alter() is retained. I added that over a decade ago 🤯 in #499156: CDN integration: allow file URLs to be rewritten by hook_file_url_alter(), specifically to make the CDN module's use case possible at all. But if the CDN module can actually migrate away from that alter hook, then we know that other scenarios can also make this change. For the sake of minimizing disruption, I am fine with keeping that alter hook. In fact, because we are going with option 1 (with its three different file URL generation methods), I think this is the only sensible choice, because otherwise every override would have to be implemented three times. The follow-up issue (#3087434: [meta] Use FileUrlGenerator::generate() everywhere, then deprecate generateString() and generateAbsoluteString()) in which we make the transition from option 1 to option 2 should also deprecate hook_file_url_alter(). Which means the soonest it'll be removed is Drupal 10. 🤓
  2. +++ b/core/includes/file.inc
    @@ -207,59 +206,16 @@ function file_stream_wrapper_uri_normalize($uri) {
    +  @trigger_error('file_create_url() is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Core\File\FileUrlGenerator::generate() instead. See https://www.drupal.org/node/2940031', E_USER_DEPRECATED);
    +  return \Drupal::service('file_url_generator')->generateAbsolute($uri);
    

    👍 This deprecates file_create_url().

  3. +++ b/core/lib/Drupal/Core/File/FileUrlGeneratorInterface.php
    @@ -0,0 +1,87 @@
    + * Separate methods are provided to provide absolute and relative URLs as well
    + * as plain strings or Url objects, depending on the requirements. In general,
    + * it is recommended to always use relative URLs unless absolute URL's are
    + * required.
    

    This is what will change in that follow-up: #3087434: [meta] Use FileUrlGenerator::generate() everywhere, then deprecate generateString() and generateAbsoluteString(). (Thanks @Berdir for creating that!)

  4. +++ b/core/lib/Drupal/Core/File/FileUrlGeneratorInterface.php
    @@ -0,0 +1,87 @@
    +   * Creates a root-relative web-accessible URL.
    ...
    +  public function generate($uri);
    

    👍

  5. +++ b/core/lib/Drupal/Core/File/FileUrlGeneratorInterface.php
    @@ -0,0 +1,87 @@
    +   * Creates a root-relative web-accessible URL.
    ...
    +  public function generateAbsolute($uri);
    

    👎 Should be Creates an absolute web-accessible URL. ✅ Fixed.

  6. +++ b/core/modules/ckeditor/tests/modules/src/Form/AjaxCssForm.php
    @@ -14,6 +16,37 @@
    +   * Constructs a CssOptimizer.
    

    🤓 Übernit: AjaxCssForm. ✅ Fixed.

  7. +++ b/core/modules/color/color.module
    @@ -487,7 +487,7 @@ function color_scheme_form_submit($form, FormStateInterface $form_state) {
    -      $css_optimizer = new CssOptimizer();
    +      $css_optimizer = new CssOptimizer(\Drupal::service('file_url_generator'));
    

    👍 This is not a BC break. The argument is optional. This is just here to not trigger a deprecation error.

  8. +++ b/core/modules/file/file.module
    @@ -1495,13 +1495,9 @@ function template_preprocess_file_link(&$variables) {
    -  // @todo Wrap in file_url_transform_relative(). This is currently
    -  // impossible. As a work-around, we currently add the 'url.site' cache context
    -  // to ensure different file URLs are generated for different sites in a
    -  // multisite setup, including HTTP and HTTPS versions of the same site.
    -  // Fix in https://www.drupal.org/node/2646744.
    -  $url = $file->createFileUrl(FALSE);
    -  $variables['#cache']['contexts'][] = 'url.site';
    +  /** @var \Drupal\Core\File\FileUrlGeneratorInterface $file_url_generator */
    +  $file_url_generator = \Drupal::service('file_url_generator');
    +  $url = $file_url_generator->generateUrl($file->getFileUri());
    

    🥳

  9. +++ b/core/modules/file/src/Plugin/Field/FieldFormatter/BaseFieldFileFormatterBase.php
    @@ -47,9 +100,7 @@ public function viewElements(FieldItemListInterface $items, $langcode) {
    -      // @todo Wrap in file_url_transform_relative(). This is currently
    -      // impossible. See below.
    -      $url = file_create_url($items->getEntity()->uri->value);
    +      $url = $this->fileUrlGenerator->generateUrl($items->getEntity()->getFileUri());
    
    @@ -59,17 +110,7 @@ public function viewElements(FieldItemListInterface $items, $langcode) {
    -          '#url' => Url::fromUri($url),
    -          // @todo Remove the 'url.site' cache context by using a relative file
    -          // URL (file_url_transform_relative()). This is currently impossible
    -          // because #type => link requires a Url object, and Url objects do not
    -          // support relative URLs: they require fully qualified URLs. Fix in
    -          // https://www.drupal.org/node/2646744.
    -          '#cache' => [
    -            'contexts' => [
    -              'url.site',
    -            ],
    -          ],
    +          '#url' => $url,
    
    +++ b/core/modules/file/src/Plugin/Field/FieldFormatter/FileUriFormatter.php
    @@ -51,10 +51,7 @@ public function settingsForm(array $form, FormStateInterface $form_state) {
    -      // @todo Wrap in file_url_transform_relative(). This is currently
    -      // impossible. See BaseFieldFileFormatterBase::viewElements(). Fix in
    -      // https://www.drupal.org/node/2646744.
    -      $value = file_create_url($value);
    +      $value = $this->fileUrlGenerator->generate($value);
    

    🥳🥳🥳

  10. +++ b/core/modules/file/src/Plugin/views/field/File.php
    @@ -17,6 +19,41 @@
    +   * Constructs a Handler object.
    

    🤓 Übernit: "Handler" → "File" ✅ Fixed.

  11. +++ b/core/modules/file/src/Plugin/views/field/File.php
    @@ -64,13 +101,7 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
    -      // @todo Wrap in file_url_transform_relative(). This is currently
    -      // impossible. As a work-around, we could add the 'url.site' cache context
    -      // to ensure different file URLs are generated for different sites in a
    -      // multisite setup, including HTTP and HTTPS versions of the same site.
    -      // But unfortunately it's impossible to bubble a cache context here.
    -      // Fix in https://www.drupal.org/node/2646744.
    -      $this->options['alter']['path'] = file_create_url($this->getValue($values, 'uri'));
    +      $this->options['alter']['url'] = $this->fileUrlGenerator->generateUrl($this->getValue($values, 'uri'));
    

    🥳

  12. +++ b/core/modules/image/src/Plugin/Field/FieldFormatter/ImageFormatter.php
    @@ -200,16 +216,9 @@ public function viewElements(FieldItemListInterface $items, $langcode) {
    -        // @todo Wrap in file_url_transform_relative(). This is currently
    -        // impossible. As a work-around, we currently add the 'url.site' cache
    -        // context to ensure different file URLs are generated for different
    -        // sites in a multisite setup, including HTTP and HTTPS versions of the
    -        // same site. Fix in https://www.drupal.org/node/2646744.
    -        $url = Url::fromUri(file_create_url($image_uri));
    -        $cache_contexts[] = 'url.site';
    +        $url = $this->fileUrlGenerator->generateUrl($image_uri);
    
    @@ -227,7 +236,6 @@ public function viewElements(FieldItemListInterface $items, $langcode) {
    -          'contexts' => $cache_contexts,
    

    🥳

  13. +++ b/core/modules/media/tests/src/Kernel/MediaEmbedFilterTest.php
    @@ -104,7 +104,7 @@ public function providerTestBasics() {
    -          ->setCacheContexts(['url.site', 'user.permissions'])
    +          ->setCacheContexts(['user.permissions'])
    

    🥳 (This is a win thanks to \Drupal\image\Plugin\Field\FieldFormatter\ImageFormatter::viewElements() now being able to return a root-relative file URL! That means it's the same in all sites in a multi-site, and hence the need for this cache context disappears. This elegantly demonstrates that. 👍)

  14. +++ b/core/tests/Drupal/Tests/Core/Asset/CssCollectionRendererUnitTest.php
    @@ -33,12 +34,20 @@ class CssCollectionRendererUnitTest extends UnitTestCase {
    +   * The file URL generator mock..
    

    🤓 Überübernit: double period should be a single one… ✅ Fixed.

  15. +++ b/core/tests/Drupal/Tests/Core/Asset/CssCollectionRendererUnitTest.php
    @@ -294,57 +316,3 @@ public function testRenderInvalidType() {
    -/**
    - * Temporary mock for file_create_url(), until that is moved into
    - * Component/Utility.
    - */
    -if (!function_exists('Drupal\Tests\Core\Asset\file_create_url')) {
    -
    -  function file_create_url($uri) {
    -    return 'file_create_url:' . $uri;
    -  }
    -
    -}
    -
    -/**
    - * Temporary mock of file_url_transform_relative, until that is moved into
    - * Component/Utility.
    - */
    -if (!function_exists('Drupal\Tests\Core\Asset\file_url_transform_relative')) {
    -
    -  function file_url_transform_relative($uri) {
    -    return 'file_url_transform_relative:' . $uri;
    -  }
    -
    -}
    -
    -/**
    - * CssCollectionRenderer uses file_create_url() & file_url_transform_relative(),
    - * which *are* available when using the Simpletest test runner, but not when
    - * using the PHPUnit test runner; hence this hack.
    - */
    -namespace Drupal\Core\Asset;
    -
    -if (!function_exists('Drupal\Core\Asset\file_create_url')) {
    -
    -  /**
    -   * Temporary mock for file_create_url(), until that is moved into
    -   * Component/Utility.
    -   */
    -  function file_create_url($uri) {
    -    return \Drupal\Tests\Core\Asset\file_create_url($uri);
    -  }
    -
    -}
    -if (!function_exists('Drupal\Core\Asset\file_url_transform_relative')) {
    -
    -  /**
    -   * Temporary mock of file_url_transform_relative, until that is moved into
    -   * Component/Utility.
    -   */
    -  function file_url_transform_relative($uri) {
    -    return \Drupal\Tests\Core\Asset\file_url_transform_relative($uri);
    

    👏 GOOD RIDDANCE

Epic work, @Berdir & @kim.pepper! 🙏🙏🙏

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
  1. +++ b/core/lib/Drupal/Core/File/FileUrlGenerator.php
    @@ -0,0 +1,235 @@
    +  /**
    +   * {@inheritdoc}
    +   */
    +  public function transformRelative($file_url, $root_relative = TRUE) {
    ...
    +    // If this should not be a root-relative path but relative to the drupal
    +    // base path, cut off that part as well.
    +    if (!$root_relative) {
    +      $http_host .= $request->getBasePath();
    +    }
    
    +++ b/core/lib/Drupal/Core/File/FileUrlGeneratorInterface.php
    @@ -0,0 +1,87 @@
    +   * @param string $file_url
    +   *   A file URL of a local file as generated by
    +   *   \Drupal\Core\File\FileUrlGenerator::generate().
    +   *
    +   * @return string
    +   *   If the file URL indeed pointed to a local file and was indeed absolute,
    +   *   then the transformed, relative URL to the local file. Otherwise: the
    +   *   original value of $file_url.
    +   */
    +  public function transformRelative($file_url);
    

    This is missing the root relative flag. Also the inline documentation in the meoth about what the flag does takes more than second to get your head round (at least for me) so perhaps it could be improved.

  2. +++ b/core/lib/Drupal/Core/File/FileUrlGenerator.php
    @@ -0,0 +1,235 @@
    +    $url = NULL;
    ...
    +    return $url;
    +  }
    
    +++ b/core/lib/Drupal/Core/File/FileUrlGeneratorInterface.php
    @@ -0,0 +1,87 @@
    +   * @return \Drupal\Core\Url|bool
    +   *   For a local URL (matching domain), a base-relative Url object containing a
    +   *   URL that may be used to access the file. An Url object with absolute URL
    +   *   may be returned when using a CDN or a remote stream wrapper. Use
    +   *   setAbsolute() on the Url object to build an absolute URL. If a stream
    +   *   wrapper could not be found to generate an external URL, then FALSE is
    +   *   returned.
    +   */
    +  public function generateUrl($uri);
    

    We're returning a NULL here too. Also it's nice when the order on the interface matches the order of methods in the class.

  3. +++ b/core/lib/Drupal/Core/File/FileUrlGenerator.php
    @@ -0,0 +1,235 @@
    +    elseif ($wrapper = $this->streamWrapperManager->getViaUri($uri)) {
    +      // Attempt to return an external URL using the appropriate wrapper.
    +      return $this->transformRelative($wrapper->getExternalUrl());
    +    }
    +  }
    ...
    +    elseif ($wrapper = $this->streamWrapperManager->getViaUri($uri)) {
    +      // Attempt to return an external URL using the appropriate wrapper.
    +      return $wrapper->getExternalUrl();
    +    }
    +  }
    
    +++ b/core/lib/Drupal/Core/File/FileUrlGeneratorInterface.php
    @@ -0,0 +1,87 @@
    +   * @return string|bool
    +   *   For a local URL (matching domain), a root-relative string containing a
    +   *   URL that may be used to access the file. An absolute URL may be returned
    +   *   when using a CDN or a remote stream wrapper. If a stream wrapper could
    +   *   not be found to generate an external URL, then FALSE is returned.
    +   */
    +  public function generate($uri);
    ...
    +   * @return string|bool
    +   *   An absolute string containing a URL that may be used to access the
    +   *   file. If a stream wrapper could not be found to generate an external URL,
    +   *   then FALSE is returned.
    +   */
    +  public function generateAbsolute($uri);
    

    Atm both of these are returning NULL and not FALSE. Either we need to update the docs or add return FALSE;

  4. +++ b/core/lib/Drupal/Core/File/FileUrlGeneratorInterface.php
    @@ -0,0 +1,87 @@
    +   * Creates a root-relative web-accessible URL.
    

    Let's change this to Creates a root-relative web-accessible URL object. Thinking about this some more... doesn't this clash with Url::fromUri() (and other from methods). Are we sure about adding this? I'm not convince this method is necessary.

alexpott’s picture

Re #4. For me, it looks like we're adding the generateUrl() method only because File object has really poor Url object generation. There doesn't seem to be any usage of generateUrl() where we're not really dealing with a File object.

alexpott’s picture

So here's an implementation of what I'm talking about #2095771-176: alexpott's test issue I didn't post it here because I don't want to side track this completely if I'm heading down the wrong path.

For me one of the issues here is that file_create_url() is being used for many purposes. Sometimes it's dealing with a hardcoded path to a file like the default favicon and sometimes it is dealing with a File entity. Looking at the massive discussions on the issues it seems like people feel option 2 was the best solution - ie. we should be using Url objects everywhere. Then maybe we need to consider renaming the methods to ::generate() (which generates Url objects), ::generateString() and ::generateAbsoluteString() - as at some point we want to deprecate the latter two.

+++ b/core/lib/Drupal/Core/File/FileUrlGenerator.php
@@ -0,0 +1,235 @@
+      // Attempt to return an external URL using the appropriate wrapper.
+      $url = Url::fromUri('base:' . $this->transformRelative($wrapper->getExternalUrl(), FALSE));
...
+    elseif ($wrapper = $this->streamWrapperManager->getViaUri($uri)) {
+      // Attempt to return an external URL using the appropriate wrapper.
+      return $this->transformRelative($wrapper->getExternalUrl());
+    }

I think we should only do this for LocalStream. If it's not a LocalStream then I think we need to do:

$url = Url::fromUri($wrapper->getExternalUrl());
alexpott’s picture

Discussed with @Berdir and @chx and after that I'm coming round to the idea this issue should prep the ground so we can move to option 2. If we always use Url objects instead of strings the issues around root relative / absolute / context where the url string is being used / cache contexts get a little bit easier.

So for me the next steps are:

  1. to get the method names in a state to support option 2 which for me means:
    • generateUrl() -> generate()
    • generate -> generateString()
    • generateAbsolute -> generateAbsoluteString()
  2. We also need to fix the assumptions that the stream wrapper represents a local file as per the last part of #165.
  3. We probably should go through the documentation on FileUrlGeneratorInterface to make it clear to developers that generating Url objects is preferred and why.
  4. We need to fix the interface docs as per #163
Wim Leers’s picture

#166++

Thanks for the fresh perspective! I completely agree that the naming changes you propose (point 1) make the patch much easier to understand. It also paves the path more clearly for deprecating two of the three methods when we implement option 2 in #3087434: [meta] Use FileUrlGenerator::generate() everywhere, then deprecate generateString() and generateAbsoluteString(). Your point 3 helps minimize future code relying on the methods we want to remove in #3087434, great! Point 2 and 4 are good catches of bugs (I think I saw @Berdir write in Drupal Slack that we added test coverage for point 2 at some point, but we lost it along the way, would be good to restore that).

bradjones1’s picture

I pulled this up locally to do some work/validation akin to Wim's testing with CDN, and if this is still against 8.9.x it no longer cleanly applies in a number of places. Is this still targeting 8.9 or is this a 9.1 item now? I'd like to start addressing some of the code review items from comments 163+ but want to make sure I'm targeting the right branch.

bradjones1’s picture

Rerolled against 8.9.x. Didn't take too long. Still wondering about the correct target, but at least this is something.

bradjones1’s picture

Fixing some last merge conflict fuzz from #2888723: ckeditor_stylesheets cache busting: use system.css_js_query_string resulting from some missing schemes in a unit test - this should now be re-rolled for 8.9.x.

bradjones1’s picture

Version: 8.9.x-dev » 9.1.x-dev

As far as I can tell this is now 9.1.x as it missed 8.8.0. Feel free to change if I'm wrong. Deprecation notices will need to be updated but that can be done when it's basically RTBC.

Wim Leers’s picture

Yep, 9.1. 8.9 and 9.0 will not contain new features, and certainly not API changes.

Thanks for the reroll!

I think #166 still needs to be addressed though?

bradjones1’s picture

Thanks for the clarification. And yes this was just a reroll, I sat down to address the changes but only got so far as re-rolling to pass tests. I intend to work on this a little further though with an eye toward using it locally and some contrib.

mondrake’s picture

kim.pepper’s picture

Status: Needs review » Needs work

The last submitted patch, 175: 2669074-175.patch, failed testing. View results

kim.pepper’s picture

Status: Needs work » Needs review
FileSize
177.87 KB
2.06 KB

Fixes a couple of tests due to copy/paste fail.

kim.pepper’s picture

FileSize
178.24 KB
11.54 KB

The remaining fail is because a mock file url generator. I think this change is ok.

The last submitted patch, 177: 2669074-177.patch, failed testing. View results

kim.pepper’s picture

FileSize
180.18 KB
77.5 KB

This does all the renaming as per #166.1

Status: Needs review » Needs work

The last submitted patch, 180: 2669074-180.patch, failed testing. View results

kim.pepper’s picture

Status: Needs work » Needs review
FileSize
180.2 KB
1.79 KB

Fixed a couple of missing renames in mock objects.

Status: Needs review » Needs work

The last submitted patch, 182: 2669074-182.patch, failed testing. View results

kim.pepper’s picture

Status: Needs work » Needs review
FileSize
180.21 KB
1.67 KB

Fix another rename where a method was a string. That kinda thing is tedious. 😖

Status: Needs review » Needs work

The last submitted patch, 184: 2669074-184.patch, failed testing. View results

kim.pepper’s picture

Status: Needs work » Needs review
FileSize
180.21 KB
942 bytes

Hopefully this is the last one...

daffie’s picture

Status: Needs review » Needs work

Patch looks good, needs some work:

  1. +++ b/core/lib/Drupal/Core/File/FileUrlGenerator.php
    @@ -0,0 +1,235 @@
    +  /**
    +   * The file system service.
    +   *
    +   * @var \Drupal\Core\File\FileSystemInterface
    +   */
    +  protected $fileSystem;
    

    Why is this class variable added? It is not used anywhere!

  2. +++ b/core/lib/Drupal/Core/File/FileUrlGeneratorInterface.php
    @@ -0,0 +1,87 @@
    +   * @return string|bool
    ...
    +   * @return string|bool
    ...
    +   * @return \Drupal\Core\Url|bool
    

    Can we change the part "|bool" to "|false". The value TRUE is not possible.

  3. +++ b/core/lib/Drupal/Core/File/FileUrlGeneratorInterface.php
    @@ -0,0 +1,87 @@
    +   * Creates a root-relative web-accessible URL.
    ...
    +   * Creates a root-relative web-accessible URL.
    

    That is funny. Both methods do exactly the same thing! Shall we add " string" or " object" the description. Can we update the description of the method generateAbsoluteString() in the same way.

  4. +++ b/core/lib/Drupal/Core/File/FileUrlGeneratorInterface.php
    @@ -0,0 +1,87 @@
    +   *   For a local URL (matching domain), a base-relative Url object containing a
    

    Nitpick: Goes over the 80 character line.

  5. +++ b/core/lib/Drupal/Core/Asset/CssOptimizer.php
    @@ -17,6 +18,27 @@ class CssOptimizer implements AssetOptimizerInterface {
    +      @trigger_error('Calling CssOptimizer::__construct() without the $file_url_generator argument is deprecated in drupal:8.8.0. The $file_url_generator argument will be required in drupal:9.0.0. See https://www.drupal.org/node/2940031', E_USER_DEPRECATED);
    

    On multiple places: We can no longer deprecate things in 8.8. Please change to 9.1.

  6. +++ b/core/lib/Drupal/Core/Asset/CssOptimizer.php
    @@ -258,7 +280,23 @@ public function rewriteFileURI($matches) {
    +  /**
    +   * Returns the file URL generator.
    +   *
    +   * @return \Drupal\Core\File\FileUrlGeneratorInterface
    +   *   The file URL generator.
    +   *
    +   * @internal
    +   */
    +  protected function getFileUrlGenerator() {
    +    if (!$this->fileUrlGenerator) {
    +      @trigger_error('Calling CssOptimizer::__construct() without the $file_url_generator argument is deprecated in drupal:8.8.0. The $file_url_generator argument will be required in drupal:9.0.0. See https://www.drupal.org/node/2940031', E_USER_DEPRECATED);
    +      $this->fileUrlGenerator = \Drupal::service('file_url_generator');
    +    }
    +    return $this->fileUrlGenerator;
    

    This method is not necessary and can be removed. Please use the class variable: $this->fileUrlGenerator

  7. +++ b/core/modules/ckeditor/tests/modules/src/Form/AjaxCssForm.php
    @@ -14,6 +16,37 @@
    +  /**
    +   * The base path used by rewriteFileURI().
    +   *
    +   * @var string
    +   */
    +  public $rewriteFileURIBasePath;
    

    Why is this class variable added? Can it be removed?

  8. +++ b/core/lib/Drupal/Core/File/FileUrlGenerator.php
    @@ -0,0 +1,235 @@
    +class FileUrlGenerator implements FileUrlGeneratorInterface {
    
    +++ b/core/lib/Drupal/Core/File/FileUrlGeneratorInterface.php
    @@ -0,0 +1,87 @@
    +interface FileUrlGeneratorInterface {
    

    Can we put the methods in the same order in both files.

  9. I am missing deprecation message testing.
  10. When I do a code base search for "file_create_url(", I still find it in the wrong places like: "core/modules/file/tests/src/Functional/DownloadTest.php"
  11. When I do a code base search for "file_url_transform_relative(", I still find it in the wrong places like: "core/modules/image/src/ImageStyleInterface.php"
kim.pepper’s picture

Status: Needs work » Needs review
FileSize
183.7 KB
25.05 KB

Thanks for the review @daffie.

  1. Fixed
  2. Fixed
  3. Fixed
  4. Fixed
  5. Fixed. As you can see, we've been re-rolling this patch for a while :-)
  6. There was some discussion of this above. It is provided for BC as sub-classes may not call the parent constructor. I've added a comment as such to the method.
  7. Fixed
  8. Fixed
  9. Added
  10. Replaced mentions in comments
  11. Replaced mentions in comments
daffie’s picture

Status: Needs review » Reviewed & tested by the community

All my review points are addressed.
All code changes look good.
For me it is RTBC.

Berdir’s picture

Status: Reviewed & tested by the community » Needs review

Thanks for the reviews, I'd like to to check this again myself too. as far as I remember, I dentified some serious issues in the method that returns an URL object where we lost improvements and are missing test coverage since reverting back.

Wim Leers’s picture

Thanks for pushing this forward so much, @kim.pepper & @daffie! It's great to see this moving forward again :)

daffie’s picture

Talked to @Berdir on Slack about his review of the current patch and he said:

so what I want to look at specifically is that in #137, I made some fixes and improvements to the Url-returning method because tests were failing. Then some rerolls and smaller changes on top of that, and later in #150, we went back to the patch from #132, and lost those improvements. Tests are fine, but that's just because we no longer need generateUrl() in those previously failing tests. So we have bugs in there that aren't tested, so we need test coverage for that method and include those fixes again).

and

and also check the comments from #163-166, some of that is that he also discovered some of the problems in generateUrl() that I think were fixed in the earlier patch

daffie’s picture

This patch is a reroll, therefore no interdiff file.
There is one improvement from the patch from comment #132 to the method FileUrlGenerator::generate():

+++ b/core/lib/Drupal/Core/File/FileUrlGenerator.php
@@ -0,0 +1,134 @@
+      $options = UrlHelper::parse($uri);
+      return Url::fromUri('base:' . $this->transformRelative(urldecode($options['path']), FALSE), $options);

The improvement still needs testing.

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

Nono95230’s picture

Adapt the 2669074-193.patch patch for drupal version 9.0.9

daffie’s picture

Status: Needs review » Needs work

@Nono95230: The patch needs to apply to 9.2.x.

ravi.shankar’s picture

Assigned: Unassigned » ravi.shankar
ravi.shankar’s picture

Assigned: ravi.shankar » Unassigned
Status: Needs work » Needs review
FileSize
181.96 KB
40.18 KB

Added reroll of patch #193 for Drupal 9.2.x and added interdiff, the files which had conflicts.

Status: Needs review » Needs work

The last submitted patch, 198: 2669074-198.patch, failed testing. View results

ravi.shankar’s picture

Assigned: Unassigned » ravi.shankar
ravi.shankar’s picture

Assigned: ravi.shankar » Unassigned
Status: Needs work » Needs review
FileSize
182.94 KB
6.38 KB

Fixed failed tests of patch #198.

s.messaris’s picture

Patch 201 does not apply to 9.2.x, here is my reroll.

Status: Needs review » Needs work

The last submitted patch, 202: 2669074-9.2.x-202.patch, failed testing. View results

andypost’s picture

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

daffie’s picture

Status: Needs review » Needs work
Issue tags: +Needs reroll
Neslee Canil Pinto’s picture

Status: Needs work » Needs review
FileSize
183.24 KB
5.5 KB

Updated patch as per #207

daffie’s picture

Status: Needs review » Needs work
Issue tags: -Needs reroll

Patch looks good, but I do have some remarks:

  1. +++ b/core/lib/Drupal/Core/Asset/CssCollectionRenderer.php
    @@ -16,14 +17,28 @@ class CssCollectionRenderer implements AssetCollectionRendererInterface {
    +      @trigger_error('Calling CssCollectionRenderer::__construct() without the $file_url_generator argument is deprecated in drupal:9.1.0 and will be required before drupal:10.0.0. See https://www.drupal.org/node/2549139.', E_USER_DEPRECATED);
    
    +++ b/core/lib/Drupal/Core/Asset/CssOptimizer.php
    @@ -17,6 +18,27 @@ class CssOptimizer implements AssetOptimizerInterface {
    +      @trigger_error('Calling CssOptimizer::__construct() without the $file_url_generator argument is deprecated in drupal:9.1.0. The $file_url_generator argument will be required in drupal:10.0.0. See https://www.drupal.org/node/2940031', E_USER_DEPRECATED);
    

    The deprecation will not happen in Drupal 9.1. Please change to 9.3. Multiple locations in this patch.

  2. +++ b/core/lib/Drupal/Core/File/FileUrlGenerator.php
    @@ -0,0 +1,233 @@
    +  public function generateString($uri) {
    
    +++ b/core/lib/Drupal/Core/File/FileUrlGeneratorInterface.php
    @@ -0,0 +1,87 @@
    +   * @return string|false
    +   *   For a local URL (matching domain), a root-relative string containing a
    +   *   URL that may be used to access the file. An absolute URL may be returned
    +   *   when using a CDN or a remote stream wrapper. If a stream wrapper could
    +   *   not be found to generate an external URL, then FALSE is returned.
    +   */
    +  public function generateString($uri);
    

    This method can return FALSE, only I do not see where in the method that is done. Can we also add some testing for it.

  3. +++ b/core/lib/Drupal/Core/File/FileUrlGenerator.php
    @@ -0,0 +1,233 @@
    +  public function generateAbsoluteString($uri) {
    
    +++ b/core/lib/Drupal/Core/File/FileUrlGeneratorInterface.php
    @@ -0,0 +1,87 @@
    +   * @return string|false
    +   *   An absolute string containing a URL that may be used to access the
    +   *   file. If a stream wrapper could not be found to generate an external URL,
    +   *   then FALSE is returned.
    +   */
    +  public function generateAbsoluteString($uri);
    

    This method can return FALSE, only I do not see where in the method that is done. Can we also add some testing for it.

  4. +++ b/core/lib/Drupal/Core/File/FileUrlGenerator.php
    @@ -0,0 +1,233 @@
    +  public function generate($uri) {
    
    +++ b/core/lib/Drupal/Core/File/FileUrlGeneratorInterface.php
    @@ -0,0 +1,87 @@
    +   * @return \Drupal\Core\Url|false
    +   *   For a local URL (matching domain), a base-relative Url object containing
    +   *   a URL that may be used to access the file. An Url object with absolute
    +   *   URL may be returned when using a CDN or a remote stream wrapper. Use
    +   *   setAbsolute() on the Url object to build an absolute URL. If a stream
    +   *   wrapper could not be found to generate an external URL, then FALSE is
    +   *   returned.
    +   */
    +  public function generate($uri);
    

    This method can return FALSE, only I do not see where in the method that is done. Can we also add some testing for it.

  5. +++ b/core/lib/Drupal/Core/File/FileUrlGenerator.php
    @@ -0,0 +1,233 @@
    +    // If this should not be a root-relative path but relative to the drupal
    +    // base path, cut off that part as well.
    +    if (!$root_relative) {
    +      $http_host .= $request->getBasePath();
    +    }
    

    The comment says we are cutting something off and the code is adding something???
    Is there testing for this piece of code?

  6. +++ b/core/modules/image/src/Plugin/Field/FieldFormatter/ImageFormatter.php
    @@ -199,16 +215,9 @@ public function viewElements(FieldItemListInterface $items, $langcode) {
    -      $cache_contexts = [];
    ...
    -        // @todo Wrap in file_url_transform_relative(). This is currently
    -        // impossible. As a work-around, we currently add the 'url.site' cache
    -        // context to ensure different file URLs are generated for different
    -        // sites in a multisite setup, including HTTP and HTTPS versions of the
    -        // same site. Fix in https://www.drupal.org/node/2646744.
    -        $url = Url::fromUri(file_create_url($image_uri));
    -        $cache_contexts[] = 'url.site';
    +        $url = $this->fileUrlGenerator->generate($image_uri);
    
    @@ -226,7 +235,6 @@ public function viewElements(FieldItemListInterface $items, $langcode) {
    -          'contexts' => $cache_contexts,
    

    I am not sure about this change and similar like this one. To me it is a bit out of scope for this issue. Lets do them in https://www.drupal.org/node/2646744.

@neslee: Thank you for the reroll.

Berdir’s picture

6. Unsure about that. It might help to reduce the scope and patch size of this issue, it also helps to validate that things are working correctly. I'll think about it.

kim.pepper’s picture

KapilV’s picture

fixed Custom Commands Failed.

SpadXIII’s picture

Quick note: I think there's a couple of typos here:

// in \Drupal\Core\File\FileUrlGenerator::generate
+      else {
+        // If this is not a properly formatted stream, then it is a shipped
+        // file. Therefore, return the urlencoded URI.
+        $options = UrlHelper::parse($uri);
+        return Url::fromUri('base:', UrlHelper::encodePath($options['path'], $options));
+      }

UrlHelper::encodePath only takes a single parameter, so options is not used.
Perhaps the closing bracket was in the wrong place, but Url::fromUri used only 2 parameters and not 3.
Perhaps it should be 'base:' . UrlHelper... ?

kim.pepper’s picture

Looks like I missed a diff in core/modules/file/tests/src/Functional/DownloadTest.php

@KapilV your other changes are unrelated to this issue.

Addresses the 9.1 => 9.3 deprecation notices in #209.1

kim.pepper’s picture

Re: #209.2 there are a number of existing tests in \Drupal\KernelTests\Core\File\UrlRewritingTest however none of them are testing for a missing streamwrapper and returning FALSE.

I think we can add a test to cover the FileUrlGenerator class more specifically.

Status: Needs review » Needs work

The last submitted patch, 214: 2669074-213.patch, failed testing. View results

kim.pepper’s picture

Status: Needs work » Needs review
FileSize
185.19 KB
5 KB
  • #209.1 Missed a core version in a deprecation notice. Fixed.
  • #209 2,3 and 4: I've added a return FALSE at the end of these methods and a new KernelTest to test that behaves correctly.
  • #209 5: @Berdir added this chunk in #99 and the issue of the missing flag in the interface and confusing comment was raised by @alexpott in #163 but was never addressed. Maybe @Berdir can clarify?
  • #213 Nice find! I've fixed that issue.
daffie’s picture

Status: Needs review » Needs work

The changes made in #217 look good to me. Just the unused use statement needs to be removed.

The points #209.5 and #209.6 are still open.

KapilV’s picture

Status: Needs work » Needs review
FileSize
185.14 KB
495 bytes

Removed Unused use statement.

kim.pepper’s picture

Re: #209.5 the reason we add the base path to $httphost is that we strip that from the url to create the final url. So, the comment is correct. Maybe it could be:

// If this should not be a root-relative path but relative to the drupal
// base path, add it to the host to be removed from the URL.

?

I'm not sure how I can set the basepath to test that feature.

daffie’s picture

Status: Needs review » Needs work

@KapilV Thank you for removing the unused use statement.

The points #209.5 and #209.6 are still open.

Berdir’s picture

Looks like #220 found the answer to #209.5 and just needs a comment update?

On #209.6, maybe get the opinion of catch or alexpott or so whether they would prefer to split that from this issue. I think those parts are more or less the only functional usages of the new Url-based method and as shown by #213, test coverage of that part is.. not so great :) Maybe the new unit test helps a bit.

That's what I meant earlier that trying to switch over fully to this method did show that there are bugs and I fixed some but then we went back to earlier patches and lost those fixes. See #135.2 :) Maybe double-check the changes I did on generateUrl() in those comments to see if anything else should be included? That was the only relevant change in #134. But if you check #137, there are a few more changes, around url encoding for example, nasty stuff. Test fails of #134 might give some hints on what tests we need for that.

kim.pepper’s picture

Status: Needs work » Needs review
FileSize
185.18 KB
629 bytes

Fixes comment for #209.5

I ran a coverage report for FileUrlGeneratorTest and it only covers 29% :-|

kim.pepper’s picture

FileSize
184.16 KB
20.71 KB

I moved all the tests from

core/tests/Drupal/KernelTests/Core/File/UrlRewritingTest.php

to core/tests/Drupal/KernelTests/Core/File/FileUrlGeneratorTest.php as they were all testing FileUrlGenerator.

I also refactored out the common code from generateString and generateAbsoluteString to a new function generateRelative.

This has bumped up the FileUrlGeneratorTest code coverage to a more respectable 74%.

kim.pepper’s picture

FileSize
184.1 KB
2.72 KB

Revert some of the code formatting changes as requested by @daffie

kim.pepper’s picture

  • Refactored common code out of generateString() and generateAbsoluteString().
  • Simplified tests that check for missing streamwrapper
  • Added missing tests for generate()
kim.pepper’s picture

Fix code style errors.

Status: Needs review » Needs work

The last submitted patch, 227: 2669074-227.patch, failed testing. View results

kim.pepper’s picture

Status: Needs work » Needs review
FileSize
185.07 KB
717 bytes

Interesting. Adding the string parameter type for $uri caused these failures. Looks like something is passing null. Let's see what happens by removing them.

daffie’s picture

Status: Needs review » Needs work

For me just 1 nitpick left. After that it is RTBC for me.

  1. +++ b/core/tests/Drupal/KernelTests/Core/File/FileUrlGeneratorTest.php
    @@ -83,38 +134,74 @@ public function testPublicManagedFileURL() {
    +    $url = $this->fileUrlGenerator->generate($filepath);
    ...
    +    $url = $this->fileUrlGenerator->generate($filepath);
    ...
    +    $url = $this->fileUrlGenerator->generate($filepath);
    ...
    +    $url = $this->fileUrlGenerator->generate($filepath);
    

    We are testing the same thing 4 times. Maybe we should be using a dataprovider.

  2. +++ b/core/lib/Drupal/Core/File/FileUrlGenerator.php
    @@ -0,0 +1,226 @@
    +  protected function generateRelative($base_url, $uri) {
    

    Adding this helper method was a great idea!

  3. For #209.6 Lets keep them in this patch. @Berdir Thank you for your argumentation!
Berdir’s picture

  1. +++ b/core/tests/Drupal/KernelTests/Core/File/FileUrlGeneratorTest.php
    @@ -5,21 +5,70 @@
    +  /**
    +   * The streamwrapper used for testing.
    +   *
    +   * @var \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface
    +   */
    +  protected $streamWrapperManager;
    +
    +  /**
    +   * The request stack used for testing.
    +   *
    +   * @var \Drupal\Core\Http\RequestStack
    +   */
    +  protected $requestStack;
    +
    

    these aren't used anymore I think?

  2. +++ b/core/tests/Drupal/KernelTests/Core/File/FileUrlGeneratorTest.php
    @@ -5,21 +5,70 @@
    +   * @covers ::generateAbsoluteString
    
    @@ -83,38 +134,74 @@ public function testPublicManagedFileURL() {
    +  /**
    +   * @covers ::generate
    +   */
    +  public function testGenerateURI() {
    +    // Disable file_test.module's hook_file_url_alter() implementation.
    

    I'd suggest to include some edge cases that require url decoding in here, similar to what we have above with the existing file_create_url() tests.. query arguments, fragments, ...

  3. +++ b/core/tests/Drupal/KernelTests/Core/File/FileUrlGeneratorTest.php
    @@ -83,38 +134,74 @@ public function testPublicManagedFileURL() {
    +    $this->assertEquals('base:' . $filepath, $url->getUri());
    +
    +    // Absolute file.
    +    $filepath = 'https://www.example.com/core/assets/vendor/jquery/jquery.min.js';
    +    $url = $this->fileUrlGenerator->generate($filepath);
    +    $this->assertEquals('base:' . $filepath, $url->getUri());
    +
    

    this doesn't look right? an absolute path shouldn't be prefixed with base? that would make it localdomain.com/https://... then.

kim.pepper’s picture

Status: Needs work » Needs review
FileSize
185.07 KB
5.6 KB

Thanks for the reviews!

Fixes all feedback in #230 and #231.

#231.3 Yeah this was just wrong!

daffie’s picture

Status: Needs review » Needs work
+++ b/core/tests/Drupal/KernelTests/Core/File/FileUrlGeneratorTest.php
@@ -183,25 +164,51 @@ public function testGenerateURI() {
+  public function dataProvider() {

Can we give this dataprovider method a more sensible name like "providerGenerateURI()" or something like similar.

kim.pepper’s picture

Status: Needs work » Needs review
FileSize
185.08 KB
1.18 KB

Fix code style errors and rename data provider for #233

Status: Needs review » Needs work

The last submitted patch, 234: 2669074-234.patch, failed testing. View results

kim.pepper’s picture

@Berdir I have a feeling the change in #232 caused these failures.

mondrake’s picture

  1. +++ b/core/lib/Drupal/Core/File/FileUrlGeneratorInterface.php
    @@ -0,0 +1,87 @@
    +  public function generateString($uri);
    ...
    +  public function generateAbsoluteString($uri);
    ...
    +  public function generate($uri);
    ...
    +  public function transformRelative($file_url);
    

    This is a new interface, can we add typehints to arguments and return values? Also, are we sure we want to return string|false? Wouldn't it be better to be strict on return type, and throw exceptions in case of a condition that leads to returning false instead?

  2. +++ b/core/modules/image/src/Plugin/Field/FieldFormatter/ImageFormatter.php
    @@ -64,11 +72,18 @@ class ImageFormatter extends ImageFormatterBase {
    +  public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, $label, $view_mode, array $third_party_settings, AccountInterface $current_user, EntityStorageInterface $image_style_storage, FileUrlGeneratorInterface $file_url_generator = NULL) {
    

    Question: since we are not promising BC on constructors and we are touching this one, can we fully typehint the arguments?

  3. +++ b/core/modules/image/tests/src/Functional/ImageAdminStylesTest.php
    @@ -343,8 +343,11 @@ public function testStyleReplacement() {
    +    $this->assertRaw($file_url_generator->transformRelative($style->buildUrl($original_uri)));
    
    @@ -360,7 +363,7 @@ public function testStyleReplacement() {
    +    $this->assertRaw($file_url_generator->transformRelative($style->buildUrl($original_uri)));
    

    assertRaw() is long deprecated, since we are touching the line can we convert to $this->assertSession()->responseContains()?

  4. +++ b/core/modules/node/tests/src/Functional/NodeRSSContentTest.php
    @@ -110,7 +112,7 @@ public function testUrlHandling() {
    +    $this->assertRaw($file_url_generator->generateAbsoluteString('public://root-relative'));
    

    same

  5. +++ b/core/modules/responsive_image/tests/src/Functional/ResponsiveImageFieldDisplayTest.php
    @@ -295,10 +304,10 @@ protected function doTestResponsiveImageFieldFormatters($scheme, $empty_styles =
    +      $this->assertRaw('data:image/gif;base64,R0lGODlhAQABAIABAP///wAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw== 1x, ' . $this->fileUrlGenerator->transformRelative($thumbnail_style->buildUrl($image_uri)) . ' 1.5x');
    ...
    +      $this->assertRaw($this->fileUrlGenerator->generateString($image_uri) . ' 3x');
    
    @@ -307,7 +316,7 @@ protected function doTestResponsiveImageFieldFormatters($scheme, $empty_styles =
    +      $this->assertRaw($this->fileUrlGenerator->transformRelative($medium_style->buildUrl($image_uri)) . ' 220w, ' . $this->fileUrlGenerator->transformRelative($large_style->buildUrl($image_uri)) . ' 360w');
    

    same

  6. +++ b/core/tests/Drupal/KernelTests/Core/File/StreamWrapperTest.php
    @@ -98,12 +99,14 @@ public function testUriFunctions() {
    +    assert($file_url_generator instanceof FileUrlGeneratorInterface);
    

    Why not $this->assertInstanceOf()?

kim.pepper’s picture

Status: Needs work » Needs review
FileSize
185.76 KB
9.26 KB

Re: #237

  1. Added the parameter types. I suspect this will uncover some bugs where we are calling these methods with NULL

    As for return values, we are only returning FALSE when StreamWrapperManager returns FALSE for a missing streamwrapper. I think this is a bigger change and one we should fix in StreamWrapperManager as a separate issue
  2. The plugin definition is a 'mixed' type. I don't think we can support that with our minimum PHP supported version?
  3. Fixed
  4. Fixed
  5. Fixed
  6. Ah, I think this helps typehints for PHPStorm etc.
daffie’s picture

Status: Needs review » Needs work
+++ b/core/lib/Drupal/Core/File/FileUrlGenerator.php
@@ -0,0 +1,226 @@
+  public function transformRelative($file_url, $root_relative = TRUE) {

+++ b/core/lib/Drupal/Core/File/FileUrlGeneratorInterface.php
@@ -0,0 +1,87 @@
+  public function transformRelative(string $file_url): string;

The class does not implement the interface correctly.

Berdir’s picture

1. Don't know what our rules are on adding type hints like that. sounds a bit scary ;) at the very least we need to ensure BC in the old functions somehow? not sure what happens now if you pass in null or so, maybe return false?
2. yes, adding type hints on the existing constructor arguments is definitely out of scope here, I don't want to do that just for one plugin.

mondrake’s picture

Testing a patch with a fix for #239 and stricter typehinting out at #3170403-104: Ignore, testing issue.

Berdir’s picture

Yes, #230 removed the transformRelative(), I'm not sure why you did that? we need that to return a base:url for those, that's an important behavior. There are also no cases in your test for that, you want to test for an input like 'public://path/to/file.jpg' to have explicit test coverage for that that it does return a base: URL. As mentioned, see my changes in #137 that should help with making this work correctly.

kim.pepper’s picture

Status: Needs work » Needs review
FileSize
191.7 KB
9.47 KB

@Berdir it all gets very confusing. That's why the tests help!

  1. Reverted the parameter typehinting. I think this will blow out an already big issue with trying to handle BC. Can be a followup?
  2. Added back the transform relative for a public:// scheme as well as a test case
  3. I added urldecode() to these as I'm not sure why they were removed after #137?
  4. Added descriptions to the dataProvider array to make it easier to identify failing tests.
Wim Leers’s picture

Wow, amazing progress here! 🤩

Berdir’s picture

> I added urldecode() to these as I'm not sure why they were removed after #137?

Because #137 was part of an attempt to only provide the generate() method and no string versions but that attempt was aborted due to complexity and overhead of converting everything 3 times between relative and absolute. We will need to improve the underlying API's first to avoid that before we can eventually only use that. It wasn't actively removed, it was just not added again when I went back to a previous patch.

mondrake’s picture

Added typehints to the new class, and fixed a couple of places where the current code would fail. One was particularly nasty to find. Introduced an exception that is thrown when there's an invalid URL scheme, instead of returning FALSE. What's not added is BC for the legacy procedural functions - that can be added with try..catch blocks.

kim.pepper’s picture

Looks good. Added a try/catch in file_create_url(). Not sure if this needs to be done anywhere else? 🤔

daffie’s picture

Status: Needs review » Needs work

I have updated the CR.

Some minor points left:

  1. +++ b/core/includes/file.inc
    @@ -47,58 +46,21 @@
    + *   Use \Drupal\Core\File\FileUrlGenerator::generate().
    ...
    + * @see \Drupal\Core\File\FileUrlGeneratorInterface::generateString()
    + * @see \Drupal\Core\File\FileUrlGeneratorInterface::transformRelative()
    ...
     function file_create_url($uri) {
    ...
    +  @trigger_error('file_create_url() is deprecated in drupal:9.3.0 and is removed from drupal:10.0.0. Use \Drupal\Core\File\FileUrlGenerator::generate() instead. See https://www.drupal.org/node/2940031', E_USER_DEPRECATED);
    ...
    +    return \Drupal::service('file_url_generator')->generateAbsoluteString($uri);
    

    Which new method now replaces the function file_create_url()? The method generateAbsoluteString() is called, only the documentation is saying something else.

  2. +++ b/core/includes/file.inc
    @@ -109,38 +71,23 @@ function file_create_url($uri) {
    - *   A file URL of a local file as generated by file_create_url().
    + *   A file URL of a local file as generated by
    + *   FileUrlGeneratorInterface::generateString().
    

    The function file_create_url() has been replaced by FileUrlGeneratorInterface::generateAbsoluteString() and not by FileUrlGeneratorInterface::generateString().

Berdir’s picture

> Which new method now replaces the function file_create_url()? The method generateAbsoluteString() is called, only the documentation is saying something else.

> The function file_create_url() has been replaced by FileUrlGeneratorInterface::generateAbsoluteString() and not by FileUrlGeneratorInterface::generateString().

Maybe we can just say replaced by the methods on that Interface/service or something. Because all of them are possible replacements, depending on the use case. If you look at core, the most common use case is file_url_transform_relative(file_create_url(()), and that is generateString() or generate() if it makes sense in the context.

kim.pepper’s picture

Status: Needs work » Needs review
FileSize
194.86 KB
2.74 KB

Changed the deprecation message to "Use the appropriate method on \Drupal\Core\File\FileUrlGeneratorInterface instead."

daffie’s picture

Status: Needs review » Reviewed & tested by the community

@kim.pepper: Thank you for the compromise change.

All code changes look good to me.
There is a lot of testing for the new code.
The IS and the CR are in order.
The 2 functions are deprecated and have deprecation message testing.
For me it is RTBC.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
+++ b/core/lib/Drupal/Core/Asset/CssOptimizer.php
@@ -258,7 +280,25 @@ public function rewriteFileURI($matches) {
+  protected function getFileUrlGenerator() {

This should be private and then we can remove it at some point in the future even in D10 when we remove the deprecation on the constructor - also as new code should have a return type hint.

alexpott’s picture

Adding issue credit for rerolls and review comments.

alexpott’s picture

Status: Needs work » Reviewed & tested by the community
FileSize
950 bytes
194.98 KB

#252 is such a minor change I can do it myself.

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Committed 3e4b17a and pushed to 9.3.x. Thanks!

  • alexpott committed 3e4b17a on 9.3.x
    Issue #2669074 by kim.pepper, Berdir, gaydabura, andypost, ravi.shankar...
mstrelan’s picture

FileSize
2.13 KB

We should document that most file links will use relative URLs now. I've started a draft change record.

Also a couple of phpdoc nitpicks in the attached patch that I'm not sure are worth a follow up issue.

Berdir’s picture

Published that, looks good to me. We can always improve it.

Not sure, but I would recommend to open a separate issue for those fixes. Issues tend to get confusing if we reopen them and it won't get committed as long as the issue status is still fixed.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

Wim Leers’s picture

Started using this in the CDN module over at #3103682-22: CDN module 4.x: no new features + drop BC layers + drop now irrelevant headers + PHP >=8.1 + Drupal >=9.4. Was a pretty painless upgrade! 😊 One less hook implementation, one less reason for a *.module file 🤓

Thanks to everyone who helped make this happen!

Chi’s picture

That's confusing. The FileUrlGenerator is not available in 9.2.x. Right? How do contrib modules can update their code without dropping support for current stable Drupal version (9.2)?

bradjones1’s picture

Wim's tests are running against 9.3.

catch’s picture

Issue summary: View changes
Issue tags: +9.3.0 release notes
catch’s picture

I've added the release note to the 9.3.0-alpha1 release notes where it should've been in the first place, seems more accurate than adding it to a later release.

Bohus Ulrych’s picture

FYI such change from absolute to relative URLs can surprise - in my case it broke the Views data export used as source for the Feeds import :-(
Image paths on external server became relative => no longer accessible from the target on different server/domain.