Coming from #1419016-10: Allow text fields to enforce a specific format
In there we want to be able to reduce the available text_formats in a given Field API text field. I think that could be a general use-case where you want certain textareas on your site to have different available text formats. I.e. you want your content editors to be able to use HTML in your nodes but in comments, that would really be overkill. I think this would be something worth supporting.
User access to all formats should of course always be respected no matter what.
I do not plan to work on this personally, but it shouldn't be too hard, actually. Looking at filter_process_format() the relevant part is this:
// Get a list of formats that the current user has access to.
$formats = filter_formats($user);
That would become something like:
// Get a list of formats that the current user has access to.
$formats = filter_formats($user);
// The list of available formats might have been reduced up-front.
if (isset($element['#formats'])) {
$formats = array_intersect($element['#formats'], $formats);
}
(Untested, though.)
Sorry if this is a dupe, but I didn't find anything.
Comment | File | Size | Author |
---|---|---|---|
#77 | 1423244-77-allowed-formats-d7.patch | 20.64 KB | tstoeckler |
#68 | 1423244-58.patch | 28.21 KB | lokapujya |
Comments
Comment #1
wjaspers CreditAttribution: wjaspers commentedAlthough I think introducing the
#formats
property is great, it might be better to name it#allowed_formats
. This will help avoid confusion with the #format property, and make it easier to understand what the property is for.Comment #2
Crell CreditAttribution: Crell commentedThere is Drupal 7-targeted contrib code in #1295248: Allow per-field-instance configuration of allowed formats that I welcome and encourage people to steal for Drupal 8. This is an important feature for any moderately complex Drupal site.
Comment #3
sunLet's keep this issue focused on the API side of the functionality. A possible UI for this should be tackled in a separate follow-up issue.
I don't see any problems with the feature request itself, but I do have some concerns the implementation will have to address:
We should investigate what the actual real world use-cases of this functionality are.
Background: From a code and data handling perspective, I'm questioning whether it wouldn't it be easier for modules to only specify what they do not want. Because, precisely specifying what you want means that you have to keep track of all available formats on your own, and implement your own logic to handle the cases of when a new format has been created, or an existing is deleted.
This also includes the separate case when the currently assigned #format (for some existing text) is no longer available.
The current widget handles this already, but exclusively on the assumption that these cases mean that the user does not have access to the assigned #format. Reducing the formats as proposed here has nothing to do with user access privileges to formats and the security considerations involved.
Thus, we not only need expectations, but we also need to implement an appropriate behavior for those cases.
Comment #4
Crell CreditAttribution: Crell commentedUse case:
- Forum post nodes and comments should allow only a super-restricted set of HTML, with Markdown support. That is true for all users regardless of role.
- Page nodes should allow a somewhat more permissive set of tags, but still not img or table. However, users in the Privileged Admin role should also have a second format available that allows img, table, etc.
- Major Event nodes allow the same two formats as Page noes, with the same role-based restriction, but it has WYSIWYG enabled for both formats.
- Major Event also has an "about the location" field that only allows certain HTML tags. This set of allowed tags is different than what forum posts or pages/events allow.
Note: This is not a contrived example. I originally wrote filterbynodetype in Drupal 5 and Drupal 6 because I had very nearly the use case above.
Comment #5
David_Rothstein CreditAttribution: David_Rothstein commentedCross-linking this issue with #784672: Allow text field to enforce a specific text format, which has some patches and previous discussion already.
In theory, it would make sense to create the API first (in this issue) and then consider using it later (in the other issue) - currently the code in the other issue is a bit of a mix of both. So we could postpone that issue on this one, except all the code is currently over there.
Comment #6
floretan CreditAttribution: floretan commentedDoing some research in the issue queue, there are a lot more use cases where this functionality is badly needed. With this minor API change, all these use cases could be implemented in contrib can be done without hacky form manipulations.
Here's a patch with test covering the essential cases and a few minor modifications to fix warnings. As sun mentioned in #3, this patch focuses only on the API-level functionality.
Example usage:
The logic inside filter_process_format() has also been updated to function similarly when the default format is not in the allowed formats as when the default format has been deleted.
Comment #7
Crell CreditAttribution: Crell commentedComment #8
Crell CreditAttribution: Crell commentedComment #9
tstoecklerCode/logic looks really nice!!! Some minor comments below:
Trailing whitespace.
Tabs.
The last one should also be TRUE not true.
Comment #10
floretan CreditAttribution: floretan commentedSorry for the whitespace stuff, I hadn't set things up properly in a new environment. Here's the patch rerolled with both changes.
Comment #11
webflo CreditAttribution: webflo commentedReroll
Comment #13
tstoecklerHere we go.
This should pass.
I will comment on the patch below.
Comment #14
tstoecklerI found the 'that the user can select' part a bit confusing, because that's not necessarily TRUE depending on access to the formats.
I just found it makes sense to initialize the element properly so we can avoid the check below.
I moved the $format_allowed check to where all of the other booleans are being initialized.
I tried to explain in the comment why I disagree with the previous code.
The default format needs to depend on the available options of the select. Also the = NULL allows to skip isset() checks below (and is semantically correct as well).
So instead of processing the form array manually I turned this into an actual web test. We cannot cleanly unit test filter_process_format() anyway, and in this case I think it makes sense to check that the correct markup makes it's way on to the page as this is very much access-related.
The test coverage is conclusive in terms of the configuration of #type 'text_format'.
It is not conclusive, yet IMO sufficient in terms of testing different permission combinations.
It also does not test the 'always_show_fallback_choice' setting. I think we're good here, though, we can always improve.
Damn... :-(
Oops
Comment #16
tstoeckler13: 1423244-13-text-format-allowed-formats.patch queued for re-testing.
Comment #18
tstoecklerThe test fails in FilterAPITest were introduced by the default config I added to filter_test module.
Comment #19
tstoecklerBtw, this blocks #2144413: Config translation does not support text elements with a format and thus any sensible translation of configuration that involves formatted text. This includes empty/footer/header text from views, for example.
Also this is only an API addition, not a BC-break in any way.
Comment #20
tstoecklerComment #21
tstoecklerComment #23
tstoecklerThis should pass. This time I ran all filter tests.
Comment #24
tstoecklerThis comment is no longer accurate.
The comment should explain that this is equivalent to calling filter_default_format()
This comment should probably explain *why* we are doing this.
This could use a comment.
Oh no!
Comment #25
tstoecklerBefore I re-roll for that, though, this could really use some reviews on the architecture/approach!
Comment #26
dawehnerYeah
In general I totally agree that the basic usecase is to limit the available formats, instead of disallow what you don't want.
While people want to have maybe something more HTML-ish/BB-ish/markdown-ish in a forum, they do not need that in a contact form.
One an architectual perspective it could help a module like better_formats to have the separate process method to deal with the #allowed_formats but given
the amount of logic in the lower part of the function, this does not seem to be feasible.
Does that automatically falls back to the fallback format again, or in other words the comment should not say what the code is doing.
Oh I see you commented the same, removed the other ones which aren't really helpful.
It is a bit odd to have the drupalLogin calls outside of the methods which talk about working "asAdmin"/"asNonAdmin" but I cannot think about a better way without hardcoding the path in the actual test method.
We could think about moving up assertRequiredSelect and assertOptions, they seem to be quite useful.
Comment #27
Crell CreditAttribution: Crell commentedThis is very likely for a separate issue, but the filter logic is just begging to be moved to PHPUnit tests.
Or maybe is it relevant now. Can we not test the filter selection logic at the class level? If not, it means the class is not properly written. We should be able to just poke it and check the arrays that come back, no?
Thank you for picking this up; I have wanted per-node-type or per-field-instance format selection since Drupal 4.7. :-) This gets us a step closer to that.
Comment #28
tstoecklerThanks for the reviews, yay!!
#26.1 Any suggestions for a better comment? :-)
#26.2 Yeah, was unsure about that as well, will move those inside.
#26.3 I totally agree, but I'd like to discuss that in a follow-up so as to avoid a bikeshed.
#27.1 In core we only ever test classes with PHPUnit. It's of course possible to test functions as well, but...
#27.2 Again, what class? Also, since this is security-related I think it makes sense to have "integration testing" that the form element shows up as expected. But of course, in theory there should be unit tests as well.
Comment #29
Crell CreditAttribution: Crell commentedDur. Crap, you're right, I was reading too fast; The filter system still needs to be OOPified, but that's out of scope for this issue.
Comment #30
tstoecklerHere's a re-roll. Fixes #24 and #26. I quickly tested that this works with editor.module as well. I.e. if there is only a single format, CKEditor gets instantiated properly.
Comment #32
tstoecklerReading those error messages, you just gotta love our config schema system. This should fix that.
Comment #35
tstoecklertstoeckler--
Comment #36
tstoecklerYay, anyone want to RTBC so we can finally knock this sucker out?
Comment #37
tim.plunkettIn HEAD, there was no way to get past these lines without an $element['#format'] being set.
Now with this patch, I could have
$element['#allowed_formats'] = array();
, and then $element['#format'] would be empty.One solution would be to add
+= array(filter_default_format($user))
here. At least I think that would work.Um, okay.
These are entirely unnecessary here, but if you're going to bother with @var, at least use the interface not the class.
Why bother changing this? It makes me uncomfortable to mess with a test called "testSkipSecurityFilters" unless there is a good reason.
Comment #38
tstoeckler1. That's how it's supposed to work. If you set #allowed_formats to an empty array, there will be no way to edit the text. ...because now formats are allowed. How else could it work?!
2. See above. The whole point of this issue is that you can decide for yourself which formats you want and we respect it and do not override it.
4. Updated to use the interface. I'm using getPermissionName() so the typehint is useful, actually.
5. So all of the filter tests currently are a littel schizophrenic as filter_test installs some text formats upon install and then they add additional ones in their test classes. This makes things incredibly hard to grok as it's very unclear which formats exist at which point. Therefore I moved all of the filter formats to default config of the filter_test format. That at least makes it consistent. It surely could be refactored to be better, but it's certainly an improvement over head.
This has the effect that FilterSecurityTest used to have a filter format that allowed < em > tags, but now it has a similar one, which allows < p >. We could just as well use all other tests to use < em > instead of < p >, but I really don't see the point, TBH. If you look at the actual assertions it's very clear what's happening there. It's asserting that unallowed markup gets stripped out. The only difference is that the allowed markup used to be < em > and is now < p >.
Comment #39
tstoecklerOops, empty patch file.
Comment #41
tstoecklerThe test fail is due to #2234771: drupal_get_profile() should not fall back to 'standard' so this is now blocked on that :-/
Comment #42
tstoecklerComment #43
tstoecklerUploading a combined patch to (hopefully :-)) verify my claim.
Comment #44
tstoecklerDear testbot, please test my patch?!
Comment #45
tstoecklerMaybe re-uploading helps?
Comment #46
tstoecklerYup, I thought so.
Comment #47
tstoecklerRe-uploading #39. That should now pass as the linked issue got in in the meantime.
Comment #48
Wim LeersMarked #1693286: Allow administrator to specify available text format on per-field basis. as a duplicate.
Comment #49
Wim Leers47: 1423244-38-allowed-formats_0.patch queued for re-testing.
Comment #50
Wim LeersNice work! :)
Manual testing revealed no regressions compared to D8 HEAD. However, tests do NOT pass locally, so requeued for testing.
I have found a lot of small things. The typos I fixed in this reroll, but still quite a few for you to address
Why is this necessary?
It is indeed equivalent. But AFAICT, as of this patch, we don't have any uses of
filter_default_format()
left. Why not remove it? :)Any reason why this can't be a DUTB test?
"Text format form element"?
'filter_test'?
I found it *very* confusing that for the previous cases, you would call
assertOptions()
, but in this case, you'd callassertRequiredSelect()
, but not assert the options. After looking at the implementation details, it turns out that does happen, but the name is just a bit misleading. I think renaming it toassertRequiredSelectAndOptions()
would clear that up completely :)'filtered_html'?
'filter_test'?
This should then still assert that the
input[type=hidden]
for the text format has a value of "filter_test".Here too, there should be a verification of the input[type=hidden] value!
Wrong comment.
basic_html
is undefined;filter_test
only creates thefiltered_html
andfull_html
text formats, so you'll want to change this tofiltered_html
.Plus, I found one part of
filter_process_format()
that I think needs some extra attention:Particularly:
count($formats) > 1
. Thanks to#allow_formats
,$formats
can easily contain only one text format, which would make this kick in at undesired moments. I suggest changing the outer if-condition to:Comment #52
Gábor HojtsyQueuing new patch for testing too.
Comment #54
Wim LeersSame failures, as expected :)
Comment #55
tstoeckler@WimLeers: Thanks for the excellent review. Very much appreciated!
The test failures were because the patch still added some default text formats to the
config
directory instead of theconfig/install
. Should be green now.Regarding your remarks:
1. Added an inline comment. Hope it's sufficient.
2. PHPStorm still found 15 usages for me, so I'd rather not tackle that here.
3. It uses
drupalGet()
,xpath()
andgetAllOptions()
which are all onWebTestBase
. In a perfect world, yes, this would be a DUBT, but currently I don't see how that would be easily possible.4.-8. Fixed
9.-10. Sorry, I don't know which hidden input element you are referring to. I checked both the code and the actual output and couldn't find anything related.
11.-12. Fixed
Regarding the fallback format stuff: You are correct that this was problematic. I went with your suggestion. I expanded and reformatted the comment, however. Hope you like it. :-)
Comment #56
Gábor HojtsyThis is required to #2144413: Config translation does not support text elements with a format which is required to provide complete and definite schema support for views, so this not a feature, its a blocker task.
Comment #57
Wim LeersAlmost there :)
9 & 10: on a fresh D8 install, grant the anonymous user permission to post comments, then go to the comment form as an anonymous users and inspect the comment form HTML. You'll find a
input[type=hidden]
with the text format.I can't find this usage below?
I don't see this condition expressed in the if-test.
Is this wrong, or is it implied but am I overlooking it?
Comment #58
tstoecklerRe #57:
0. I followed your steps exactly and there's no
input[type=hidden]
. See the attached screenshot for proof. I really would have been surprised to see it, too, because I can't see it being generated anywhere in the code?!1. It's not in the patch, it's in line 585 with the patch applied. You can actually trigger an Undefined index error in 8.x as well. So strictly speaking this is an unrelated bug fix / DX improvement. I'd like to keep it, but if you insist, I can rip that line back out.
2. That's the
$element['#format'] !== $fallback_format
part in the innerif ()
-statement.Comment #59
Wim Leers0. I can't reproduce this myself. I must have confused this with something else.
1. Ok.
2. You're right. Thanks for clarifying — that should've been documented before, but it wasn't — my fault.
I don't have any other complaints. Especially because 90% of this patch is test coverage that we should've already had, and after carefully reviewing all added tests, I could not find any flaws. Even if this were to introduce a regression, the tests will make it trivial to fix.
Great work — thanks! :)
Comment #60
David_Rothstein CreditAttribution: David_Rothstein commentedThis looks great. Any reason not to backport it? I consider this a security improvement since people are going to try to modify the list of options anyway, and without this it's very difficult to do correctly (see #1295248: Allow per-field-instance configuration of allowed formats).
It would be good to add something to the documentation to make clear that including a text format in #allowed_formats has no effect unless the user also has access to it (i.e. that it can only be used to restrict the default list, not add to it).
The tests look really good overall. They partially duplicate some of the existing tests in FilterFormatAccessTest.php (so ideally they'd be combined), but the method of testing for these new ones looks a bit cleaner, and a bit of duplicate testing doesn't hurt anyone :)
Comment #61
David_Rothstein CreditAttribution: David_Rothstein commentedMeant to add the backport tag (optimistically assuming we can backport it).
Comment #62
tstoecklerWell you're the gatekeeper, so if you say we can, we can. :-)
Technically, filter_process_format() hasn't changed that much so shouldn't be very hard actually.
Comment #63
xjmReroll for #2247991: [May 27] Move all module code from …/lib/Drupal/… to …/src/… for PSR-4.
Comment #64
alexpottShould we not also be asserting on enabled text areas? Also the final
$formats = array('filtered_html');
creates a variable that is never used - are we testing what we think we're testing?Comment #65
tstoecklerThanks for catching the unused variable. Removed that. The asserts are correct, though. It is just that I started each batch of assertions with
$formats = ...
and then forgot to remove it in the one place where we actually don't need it (because no select is shown).Also added a bunch of
$this->assertEnabledTextarea()
. More assertions can never hurt.Two interdiffs because I merged 8.x in between.
Comment #66
Gábor HojtsyAll the changes look good to me, and address all of @alexpott's points.
Comment #67
alexpottNeeds a reroll
Comment #68
lokapujyaRerolled.
Comment #69
lokapujyaShould the return value be documented or is it obvious enough?
Comment #70
Wim LeersThanks for the reroll! Same number of insertions and deletions + looks the same + green, so back to RTBC per #66.
Comment #72
alexpottCommitted 7922db6 and pushed to 8.x. Thanks!
Let's handle the assert return value in a new followup issue.
All assertSomething() functions should return a bool indicating pass or fail.
Comment #73
lokapujyaI created that followup: #2284383: All assertSomething() functions should return a bool indicating pass or fail.
Comment #74
tstoecklerBackported this to D7.
Had to change the following things:
plain_text
) is not removed in D7 so that hunk is no longer relevant. This also means that some assertions in the test had to be changed to also includeplain_text
.hook_menu()
and a form function instead of*.routing.yml
and a form class.fieldset
instead ofdetails
in the test form.assertRequiredSelectAndOptions()
for the fact that required select elements do not actually receive arequired
attribute in D7 (??!?!)Comment #75
tstoecklerOh this also includes my patch from #2284383-9: All assertSomething() functions should return a bool indicating pass or fail.. Forgot to mention that.
Comment #76
tstoecklerOops wanted to link #2256317: Remove static caching from WebTestBase::checkPermissions() above. (In the 6th bullet point.) Hit Save a bit too early.
Comment #77
tstoecklerNow with (hopefully) 100% more PHP 5.2 compatibilty and 100% less incorrect comments.
As I managed to majorly break this apparently, this needs to be manually tested on a PHP 5.2 environment. Specifically it should be verified that
FilterFormTest
comes back green.Comment #78
Liam Morland