-
Notifications
You must be signed in to change notification settings - Fork 2.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix handling of uploading of font files #6407
base: trunk
Are you sure you want to change the base?
Conversation
Add params to wp_handle_upload() and wp_upload_bits() instead.
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the Core Committers: Use this line as a base for the props when committing in SVN:
To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
Test using WordPress PlaygroundThe changes in this pull request can previewed and tested using a WordPress Playground instance. WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser. Some things to be aware of
For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation. |
@matiasbenedetto Sorry for the ping but do you remember why the (temp) filter |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's too late do do this for the font directory, the best time do have done something like this would have been to log an enhancement mid last year when the feature was been worked on. No such enhancement was logged.
This breaks backward compatibility for developers removing the font directory filter in the simplest way to avoid an infinite loop:
add_filter( 'upload_dir', function ( $ul ) {
remove_filter( 'upload_dir', '_wp_filter_font_directory' );
return $ul;
}, 5 );
As there has been talk of moving the font directory WordPress needs to maintain backward compatibility for site owners removing the filter, either because:
- file system issues
- deployment scripts using rsync
You mean it is too late to fix some poor code in WP? How come?
Sorry but what backwards compatibility is broken by completely removing the filter that was causing the infinite loop? The code above still works as expected as there is no filter to remove and no infinite loop. If you can still trigger an infinite loop after the changes in this PR, please provide an example of how to do it. Also, as I mentioned earlier at https://core.trac.wordpress.org/ticket/60835#comment:9, hiding an infinite loop caused by improper use of a filter (a developer error) is not a good thing to do. Frankly I think that not telling developers that they are doing something wrong is a bad thing. Would have been much better to warn them about that possibility here instead of quietly hiding it and leaving them in the dark. |
The backward compatibility break is this:
Prior to the changes I committed during the RC cycle, an infinite loop was triggered by using the filter in the exact manner for which is was designed: See the dev-note. Furthermore WordPress itself was using an anti-pattern/coding standards violation to work around rather than fix it (a closure preventing the removal of a filter when another solution was available).
I don't understand why you where happy with a filter for the six or so months you worked on this feature but have suddenly decided that the approach is no longer appropriate. Why didn't you open an enhancement at the time? |
Re The backward compatibility break: that's in reference to the code sample I provided earlier. |
I see. This is actually an interesting question. Plugins should not use "private" functions (private in quotes as the functions are just marked as private in the docs, but still accessible). If a plugin uses a private function it implies it is "doing it wrong". But what if a plugin "disables" a private function, or bypasses it in some way (when this is not intended)? Should that also be considered "doing it wrong"? I'm not sure if removing the In general it seems this is a "pretty bad behaviour" as that will also remove the Luckily this is a "brand new" functionality and there are no plugins that are doing anything like this: https://wpdirectory.net/search/01HVWE9SGPTSR5TATTWGXTCCRZ. Thinking that preventing the code from the example would actually be an enhancement and should not be blocked. This seems to be not only unexpected use, but also harmful to other plugins and the overall integrity of the WP API there.
Yes would have been a lot better/easier if that change was done in time. I wasn't working on this, was just helping from time to time when needed. Unfortunately I didn't review the code thoroughly (like most other committers) and saw this at approximately the same time you saw it :( |
Thinking more about the above example: it seems it would be a lot better to consider this use case, and make it easier for plugins to implement it. Seems a very straightforward way would be to add the
Then it will be very easy for a plugin to change anything, and will not need to do another |
When I documented the function as private, I was trying to indicate that it shouldn't be called directly, ie These comments were subsequently removed despite this been mentioned prior to the commit. |
Don't think a private function should be used by plugins, ever. This is just "bad behaviour". What's the point of having private functions then? :) Adding docs about how to use private functions seems to defeat the purpose. I'm actually thinking if/how WP can warn developers that they are using something they shouldn't be, even maybe throw error and exit when WP is in developer mode. I'll open a trac ticket with more details, If you disagree with this please lets take the conversation there. |
In this case, it's probably best to remove the private delegation. WordPress is really stuck with the filtering approach now that it's been introduced. The WordPress importer is going to need to use the filter to support the importing of the font face post type. With the talk of defining what constitutes first and second class objects and uploads, and subsequently the addition of new first class objects it's premature to lock the overrides in to an architecture that may or may not be suitable long term. To do so risks adding further complications to uploads/sideloads and the various associated functions. |
Stuck? Why? Nothing is using this "bad behaviour" at the moment, and if a plugins tries to use it I think it should be considered as breaking the requirement for not interfering with other plugins. Seems such plugins should not be hosted by WP and should be removed from the plugins repo when detected. This is similar to a plugin that is disabling/deactivating other plugins...
Which filter are you talking about? The
Not sure I understand exactly what you mean here, but there is a filter that is designed for WP and plugins to use when determining where the /fonts directory is located. That filter is Yea, I agree it is not a good design to add a filter that is in the callback to another filter. Unfortunately this was made quite worse after https://core.trac.wordpress.org/ticket/60652 and https://core.trac.wordpress.org/changeset/57868. Seems that ticket doesn't seem to fix anything but only makes things worse. It not only hides the eventual developer error, but also makes it possible for plugins to "behave badly" and stop a WP filter from running completely (i.e. mangle the WP API). Frankly I think only this would be enough of a reason to fix that code. |
Makes it a bit faster/easier for plugins to reuse the uploads dir for fonts (although it seems as a bad idea to mix them with images/other uploads).
Not all plugins are in the plugin repo. The plugins are using the filter in the intended fashions, as seen on WordCamp (since reverted following updates in Core), Pantheon and WP VIP. As I've explained before, this approach of calling Importantly, the defensive coding is for future maintainers of WordPress rather than for plugin authors.
The importer will need to use the filter in order to support WordPress 6.5 and to store uploads in the correct location for future versions of WordPress. You seem intent on making the font library code more difficult to maintain rather than proceeding with the discussions that came out of the entire frustrating experience. Before making any changes to how the font library works the following discussions need to occur:
Both of these discussions affect how the introduction of an upload context can be managed. Should the context be added up the upload/sideload functions alone or should it also be added to to |
Hm, thinking we may be misunderstanding one another. I'm not talking about plugins that use the This code will break all of the above mentioned plugins as it removes the
So instead of fixing the error in the docs, and explaining what not to do to the plugins and themes developers, the actual code has to be changed to hide that error? I mean, this particular infinite loop may not happen after the patch from #6211, however if a developer makes the error that causes it, chances are that their code will not work properly and now it is quite harder to see and fix it. Don't think that's good. Imho a proper fix in that situation would have been to fix the docs, and add inline comments explaining why an infinite loop could happen or fix the core code to prevent such loops in the first place. Not change the code to interrupt the loop.
Exactly the opposite. The current code is very hard to maintain and also makes future development there much harder. What would happen when a plugin disable the add_filter( 'upload_dir', function ( $ul ) {
remove_filter( 'upload_dir', '_wp_filter_font_directory' );
return $ul;
}, 5 ); and another plugin or eventually core wants to use the add_filter( 'upload_dir', function ( $ul ) {
if ( ! has_filter( 'upload_dir', '_wp_filter_font_directory' ) ) {
add_filter( 'upload_dir', '_wp_filter_font_directory' );
}
return $ul;
}, 9 ); Of course, this will break the first plugin that disabled the Don't think this is a good and proper way for WP core to work? Requiring plugins to "step on one another's toes" and pretty much break one another. So thinking that the possibility for this misuse of the current functionality should be fixed asap, even maybe in a dot release. |
@azaozz I recalling going back and forth on whether the filter, the override, or both were needed while developing the endpoint. At one point we had only the filter, but it seemed that the override was also needed to restrict the file types. I don't know if we checked removing the filter and only using the override, but probably not. I agree now that it seems only the override is needed, as I can see the override value takes precedence a over the filter value when |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is working in my testing
- ✅ Unfiltered font uploads place font files in
wp-content/uploads/fonts
- ✅ Can use the
font_dir
filter to change the location where fonts are uploaded (such aswp-content/fonts
) - ✅ Can use the
wp_get_upload_dir()
function when filteringfont_dir
without an infinite loop
One unfortunate back compatibility issue I found is with font_dir
filtering.
In trunk, a function like this works to change the font directory (notice the lack of changing basedir
and baseurl
). The path will be created if it does not exist.
function alter_wp_fonts_dir( $defaults ) {
$defaults['path'] = WP_CONTENT_DIR . '/fonts';
$defaults['url'] = WP_CONTENT_URL . '/fonts';
return $defaults;
}
add_filter( 'font_dir', 'alter_wp_fonts_dir' );
Using that same filter with this PR, the font upload will fail if the directory doesn't already exist. I haven't tracked down why this works in trunk (seems like it shouldn't), but I can see the change causing breakage because of wp_font_dir newly depending on basedir
to create the font directory when it doesn't exist.
@creativecoder Yep, same here. I'll try to look a bit more if it makes sense to keep
Thanks for testing! Yea, I can't see why using the Quickly tried it here (again) and seems to be working as expected, but will try to "break it", with and without this patch. |
@creativecoder Found the cause. It was creating the /fonts dir from |
@peterwilsoncc It's been more than a month since you "requested changes" for this patch, but there is still no code showing how this can be improved and what changes you're actually requesting. Are you going to suggest some changes or maybe you've reconsidered? |
Thanks for the review. Replies are inline. |
Perhaps, but that doesn't mean the plugins that are not hosted on WordPress.org should behave badly. I think it is much better to prevent any possibilities of "bad behavior" as soon as possible, before anybody starts doing it :)
Not exactly. The "broken" part is that other plugins (and eventually core) cannot run properly as the Another reason for not storing fonts in the standard uploads sub-directories (year/month) is that it will probably interfere with future functionality. For example there were several requests for fonts to be "dropped in" the /fonts directory and automatically recognized and set up by WP, similarly to how plugins and themes work. This will be pretty much impossible to implement is fonts are stored in multiple sub-directories mixed up with all other kinds of files.
Right, but both plugins will be able to run. If the |
Oops didn't mean to close :( |
Regarding the refactor and deprecation of I can think of ways that multiple It's unfortunate that the development of the fonts API endpoints within Gutenberg led to placing an artificial limitation on how to set the |
Currently, a developer can choose to use date based upload directories in one of two ways: add_filter( 'upload_dir', function ( $ul ) {
remove_filter( 'upload_dir', '_wp_filter_font_directory' );
return $ul;
}, 5 );
/* OR */
add_filter( 'font_dir', 'wp_upload_dir' ); Either is legitimate and breaking backward compatibility will not prevent the latter from working. Whether to decision to allow this was intentional or an unfortunate mistake is of no consequence to the effect: the feature exists and has been released.
Sub-directories will still need to be accounted for, as Grant identifies, developers may filter the directory to store files in However, such a change would be a major architectural change as it would render the post types |
Hmm, no. The top one (
Right. The intent of this fix is to ensure the integrity of the WP plugins API. |
Right. The last plugin that uses the filter will "win". This is how filters work in the WP plugins API and it is expected. It is not expected for a filter to be "missing" though. This makes the plugins API inconsistent and unreliable. This PR fixes that. |
That's incorrect. Pretty much all the preflight filters, for example
In this case removing the core filter bypasses the need for potentially expensive file operations that may not be needed. In some case, literally expensive as the use of a bucket offloader can result in charges to site owners. |
Right but these are designed to work that way and are (mostly) in functions that output HTML. So the purpose/design there is for a plugin to replace the core function and output the HTML. The same is true for all functions in pluggable.php. Why do you think there are no new functions added to that file? Because it became apparent (very long tome ago) that this doesn't work well. However this is a bad idea for functions that are part of an API as they would make that API inconsistent. There is nothing worse than an inconsistent API, right? The design for the
Again, to short-circuit a function is a specific code design decision. This is unsuitable when building an API. It was never intended for plugins to be able to remove the
How can a filter be applied consistently if a plugin removes it? |
This may have been the intention but it's not, in fact, how it was designed. The sky doesn't fall if the r57868 was about avoiding infinite loops and reduced the need to remove the filter. It's unrelated to this discussion.
Prior to r57868 the If a plugin removes the filter, the filters still fire consistently between different operations. It just happens to be the case that the plugin author and site owner's intent is to upload files to a different location. While architectural disagreements are one thing, I am finding the apparent disregard for the issue of creating unrelated Even if this or a similar change is made, the issue of unnecessary file system writes needs to be addressed. |
Reset the error message that may be returned by `wp_pload_dir()` but honor it if it is set by a plugin using the `font_fir` filter. Co-authored-by: Peter Wilson <[email protected]>
Do not attempt to create the WP uploads directory even if it doesn't exist yet. It will be created by `wp_mkdir_p()` if needed. Co-authored-by: Peter Wilson <[email protected]>
Sorry but not sure what are you trying to say here? You mean the intend was not implemented when designing this code? I don't think so. As we both mentioned several times above this was modeled after
This is not what I meant. Let me explain again. I was giving an example of what would happen if other filters were removed by plugins, and how damaging that would be. Please try to imagine imagine what would happen if a plugin removes the
This is the commit that introduced this bug. And this PR fixes it. I don't see how it is unrelated?
If a plugin removes this filter it will break the normal operation of the WP plugins API. Do you really think this is a good way for plugins to work? I.e. hinder parts of an API and potentially break other plugins and maybe prevent future enhancements? I actually think this is getting out of hand. It's been more than six weeks and there is no progress here at all. Again, I did review #6211 and found the solution and the code unsatisfactory. Also there was a comment/review by @youknowriad that seems unsatisfactory too. However these reviews were ignored and #6211 was committed just few days before the WP 6.5 release with the promise that this code will be fixed in 6.6. So how are we fixing this code? |
|
||
$new_file = $upload['path'] . "/$filename"; | ||
$new_file = $upload_dir['path'] . "/$filename"; | ||
if ( ! wp_mkdir_p( dirname( $new_file ) ) ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function attempts to create the directory but it doesn't appear that _wp_handle_upload()
does (correct me if I am wrong).
Given these changes allow developers to define the uploads dir, I think it should either:
- attempt to create a directory similar to the sideloading function
- add a new error message if the directory doesn't exist, it can use the same string as in the other functions.
If the second approach is taken then the override docs will need to include a note that the directory needs to be created before attempting an upload.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch! Yes, you're right, there is some repetition in wp_upload_bits()
as the directory should be checked or created when calling wp_upload_dir()
, and is later checked again and eventually created with wp_mkdir_p()
. Thinking perhaps the use of wp_mkdir_p()
here is mostly to catch edge cases when the upload_dir
filter was used.
On the other hand _wp_handle_upload()
relies only on the call to wp_upload_dir()
to create the directory or ensure that it exists. That seems to be working well enough, been like that "forever" :)
Frankly I'm a bit unsure which fix would be better here.
- As you mention we can add a (well documented) requirement that the directory as passed in
$upload_dir
param must exist. This would give the developers a bit more freedom in how to check, and how to create it if needed. - As far as I see the other option is to use
wp_mkdir_p()
to confirm or create the directory, same as inwp_upload_bits()
. Think there was a slight difference with howis_dir()
works with wrappers (perhaps that was only in older PHP versions) so using it to confirm might not be advisable as it might return a different result.
Which do you think is better?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a bit unsure which fix would be better here.
Same to be 100% honest, I could go either way too. If the upload handlers attempt to create the directory then it seems redundant that wp_upload|font_dir()
does too, but this change would complicate that.
Let's sleep on it and hopefully our unconscious can decide for us overnight.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's sleep on it and hopefully our unconscious can decide
Great idea! Unfortunately my subconscious did not take that opportunity :)
Joking aside, I tend to very slightly prefer letting plugins handle creation and confirmation of the uploads directory. That would give them a little more freedom in how to do it and whether and how to cache it. Of course they can just use wp_mkdir_p()
. Thinking that checking the type and with is_dir()
, and adding a longer description there would be sufficient.
Also tried to figure out if there are any differences between PHPs mkdir()
and is_dir()
when using wrappers. Think there may have been in the past but seems now there isn't (or at least I wasn't able to find anything).
Remove @see annotations. Not needed there. Co-authored-by: Peter Wilson <[email protected]>
I don't think bypassing the However, you raise a very good point. The changes in this PR allow for plugin authors to bypass the On this branch I was able to bypass the filter with the following code:
|
Yep, the changes here would allow plugins to reuse the However plugins will not be able to change anything, or bypass the |
I think this change is far to broad and well beyond the scope of this ticket. It represents a significant change to the uploads API that risks allowing plugins to bypass protections, have their data removed by deploys using |
I think you misunderstand this change. What is the difference between a plugin using the None. There is no difference. There is no a "significant change" to anything, plugins have been able to do everything you mention for many years. The changes in this patch just makes things a little bit more convenient, and fix the bugs introduced in https://core.trac.wordpress.org/changeset/57868 which is a very inefficient fix for https://core.trac.wordpress.org/ticket/60652. Frankly I don't understand why anybody would be sooo much against fixing this stuff, furthermore after the promise that it will be fixed in WP 6.6? This is a relatively simple patch that fixes several things, among them an anti-pattern that is a really bad example and should have never be added to WP in the first place. |
Remove the use of nested filters. Add params to
wp_handle_upload()
andwp_upload_bits()
instead.Trac ticket: https://core.trac.wordpress.org/ticket/60835
This Pull Request is for code review only. Please keep all other discussion in the Trac ticket. Do not merge this Pull Request. See GitHub Pull Requests for Code Review in the Core Handbook for more details.