Background
- #1588422: Convert contact categories to configuration system brought contact categories to the new era. Now it's time to modernize contact messages/submissions.
Goal
- Make a contact form submission a defined entity with defined properties and entity class methods: a Contact Message.
- Turn contact form categories into bundles of contact messages.
- Leverage entity form controller for the contact message forms.
- Make contact forms/messages fieldable. Without storage. Just fieldable.
Reasoning
-
I'm coming here from a Mollom module maintainer perspective:
The module needs to implement a lot of special code in order to allow to protect the contact forms against spam. That is, because the contact message form is not an entity. For the Mollom module, this essentially translates into "allow protect arbitrary forms". The entire rest of its code only integrates with entities, which is standardized and much more simple to work with.
Hence, by eliminating this special case, I expect to be able to cut away a very large chunk of integration code for arbitrary forms in the Mollom module, possibly even more than 50% of the module's code. Essentially, I want to be able to say: "Mollom can protect all of your entity forms." And that's it. :)
Possible future improvements/follow-ups
Intentionally not part of this patch:
-
Storing contact messages/form submissions is a different can of worms, as it inherently means listing, viewing, editing, permissions, etc.pp.
This is purposively not part and not within the scope of this issue. (see follow-up issues below)
-
The default "Subject" and "Message" form fields could be converted into actual Field API fields.
This patch here only exposes them as extra fields.
-
Separate entity view modes for
'mail'
and'copy'
, so field values can be hidden from the mail copy that is sent to the sender. -
Turn the user contact form into a (locked) category/bundle, so it can be fully configured like any other contact category (e.g., form title, fields, auto-reply message, etc.).
Follow-up issues
- #1849158: Expose contact category-specific forms and/or their URLs somewhere
- #1856556: Convert user contact form into a contact category/bundle
- #1856560: Store and render contact messages (and add Views support)
- #1856562: Convert "Subject" and "Message" into Message base fields
- #1856564: Split contact message 'mail' view mode into 'mail' and 'copy'
Contained bug fixes
Major bugs in contact form submission handlers that need to be fixed separately if this patch is not committed:
- Cookie is saved including "(not verified)" suffix.
global $user
is not cloned before manipulation.- Auto-reply is sent with
From: implode(', ', $category->recipients)
, which is not only invalid, but also exposes the category recipients e-mail addresses in theFrom
header of the auto-reply mail.
Related issues
- #1848874: Clean up and simplify user cancel methods and processing
- #1845372: Deleting a field from a non-node entity bundle results in an Entity Field Query Exception
- #412688: Integrate the contact form with fields (duplicate)
- #1816540: Convert both contact forms to FormControllers (vague duplicate)
- #183678: Select Category via URL in Contact Form
- #599770: Clean up the contact forms listing UI: Allow to set the default category and weights on the listing page — Move the default category + weight configuration to the category listing page.
Comment | File | Size | Author |
---|---|---|---|
#74 | drupal8.contact-messagelc.74.patch | 632 bytes | sun |
#56 | contact.entity.56.patch | 45.87 KB | sun |
#56 | interdiff.txt | 5.26 KB | sun |
#50 | contact.entity.50.patch | 48.81 KB | sun |
#50 | interdiff.txt | 1.03 KB | sun |
Comments
Comment #1
sunContains a few nice-to-have code clean-up todos, but this shit works.
Comment #3
sunFixed Field API integration.
Proof:
Comment #5
sunFixed typos and removed stale/obsolete @todos.
Fixed strange constructions in user cancel forms.
Comment #7
sunComment #8
sunThe good news: Contact messages are fully working entities. :)
Even better news: This work revealed a range of bugs in many different spots.
What still needs work:
Field API blows up if there are actual fields configured for the message. Configuration of the fields works fine. Only when visiting /contact, the system bails out with a "::getWidget() called on a non-object" fatal error (or similar).
As with the other adjustments in this patch, this almost certainly means that Field API assumes an entity ID to exist somewhere. Most likely, it would magically start to work, if the contact_message would define an 'id' entity key in its entity info (even though that wouldn't ever be populated). In turn, there are most probably two possible routes here; the soft/lame one (add a fake ID key + property), or the hard one (bending/teaching Field API to stop presuming that an ID would exist). ;)
Comment #9
sunComment #11
sunUpdated for entity type plugins.
Comment #13
sunThis blows up on the new Entity Field Query though... — it looks like that added an assumption to Field API that all fields would be attached to entities that are stored (and have a 'base table')... :(
Comment #14
sunAlso created #1848874: Clean up and simplify user cancel methods and processing and will copy those changes into that issue.
Comment #15.0
(not verified) CreditAttribution: commentedUpdated issue summary.
Comment #15.1
sunUpdated issue summary.
Comment #15.2
sunUpdated issue summary.
Comment #15.3
sunUpdated issue summary.
Comment #15.4
sunUpdated issue summary.
Comment #16
sunOverall, this looks relatively complete to me now and could use some reviews - will fix the test failures now.
Comment #17
sunUpdated ContactSidewideTest for new categories.
Comment #19
dagmar#17: contact.entity.17.patch queued for re-testing.
Comment #20
andypostAwesome patch, I think a name "Message" would conflict with popular message module entity.
Suppose better name it as contact_message according a contact_category
Comment #21
sunNo worries:
The internal entity type name is properly namespaced to Contact module already. :)
Comment #22
dagmarQuestion, could we have an option just don't provide a tab for the category?
This could allow to have independent contact forms, for example for different companies and users will not see all the other tabs.
Comment #23
sunWhat we could do is to add a property + config option to each contact category that indicates whether to expose it as a tab - would that resolve that use-case? Sounds rather trivial to me.
That said, I'd prefer to do that in a follow-up issue, since this patch is large enough already, and actually this patch does not introduce any additional features on its own - it focuses on the entity + bundle + fieldable conversion only.
Comment #24
dagmarI reeeeeally like this patch. I tested the version provided in #7 few days ago, and I happy to see #17 works much better. As far I can see is working fine, I can create and reorder the fields of a Contact Category. However I found some issues:
1) Before this patch, the default Recipients for the "Website feedback" category was automatically set to the site email. This is not configured automatically anymore:
2) When I try to delete a field from a Contact Category I see this error, but the field is deleted after go to the UI again.
As @sun recommended in #23 I created two follow-ups for this issue:
#1849158: Expose contact category-specific forms and/or their URLs somewhere
#1849164: Provide templates for contact forms
Comment #25
dagmarGoing back to needs review due those issues were not introduced in this patch
Issue 1: was introduced by #1496542: Convert site information to config system and of course, when the the contact module is installed, I didn't defined the site mail yet. Anyway this is can be fixed in a follow up if there is a solution for this...
The Issue 2 is already covered in #1845372: Deleting a field from a non-node entity bundle results in an Entity Field Query Exception
Comment #26
andypostAlso related UI fix to allow sorting of categories via overview #599770: Clean up the contact forms listing UI: Allow to set the default category and weights on the listing page
Comment #26.0
andypostUpdated issue summary.
Comment #26.1
sunUpdated issue summary.
Comment #27
sunThanks for testing! :)
I've added a few more things that could possibly be improved in follow-ups to the issue summary.
I consider this patch pretty much complete (and done if testbot approves). :)
Comment #29
sunWhoopsie - Fixed contact message mail view is not rendered.
Comment #31
sunFixed only variables can be passed by reference.
Hm. I'm not able to reproduce the Drupal\comment\Tests\CommentLanguageTest test failure locally.
Comment #32
sunSlightly adjusting issue title to clarify that this is not a "conversion" issue like all the others, which are refactoring stuff, whereas this issue is a new feature.
Also, I think this feature is ready. Some parts of the new code could be simplified and improved later on, but that will require to rethink and refactor some Entity/Field API internals first.
I think this is a very good and important step into the right direction, not only for Contact module's future, but also to harness and demonstrate the power of our entity/field systems. Lastly, for many sites, this means that they do not necessarily have to install Webform module anymore.
Comment #33
sunFinal reviews, anyone? :)
Please note that this patch still contains user cancel method changes, which have been split out into #1848874: Clean up and simplify user cancel methods and processing already, but are required for this patch to work (since it dynamically switches the contact message author/sender form elements from input fields into #type 'item').
Comment #34
dagmarAwesome, I tested this patch and besides #1845372: Deleting a field from a non-node entity bundle results in an Entity Field Query Exception is working fine.
As a really small improvement, it would be nice to see a 'View contact form' inside the operations of each category.
Comment #35
sunA "View contact form" operation on the category listing page sounds like a good idea for #599770: Clean up the contact forms listing UI: Allow to set the default category and weights on the listing page
Also note that #1848874: Clean up and simplify user cancel methods and processing is still part of this patch. I don't mind for it to get committed as part of this patch here though.
Comment #36
andypostThere's similar contrib module EntityForms
Comment #37
BerdirSome comments, nothing blocking I think, so not changing the status.
I think it should be possible to use entity_page_label() for this, by relying on load arguments in the router.
Will need a EntityNG follow-up if there isn't one already. That might allow to change the recipients thing below into a custom field that can load the recipient on demand or so... ?
Not sure I understand this correctly, but this isn't a problem that's specific for this, is it? I don't think we have a single non-config entity in core that does *not* require a custom storage controller ;)
That's a bit nasty...
Ah, that's why the user cancel tests are so slow, nice! :)
Comment #38
sunThanks for your review, @Berdir!
Hm. That function sounds like its purpose is to supply an entity's label for the page title...? This callback here is about the menu link title though - the page title is detached from that.
I think that's a separate discussion, which we should definitely have for D8. All of the entity type providing modules need to implement way too many callbacks right now, and I think that should be unified and simplified - perhaps even on the Entity class itself (e.g.,
label()
+blockTitle()
... or similar).Yeah, probably. That said, I can't really make sense of the current EntityNG situation — it looks like we're continuing to convert everything to Entity right now... and will have a giant Entity to EntityNG conversion later on, ultimately eliminating EntityNG?
The recipient is actually an external data source that needs to be passed in (for the user contact form only). It may be comparable to how the parent/context entity is passed into the new, decoupled comments - but I didn't have time to check and review how the new comments are doing that.
Technically, I guess it's a user reference property/field, but it only applies to the user contact form — as mentioned in the issue summary, there's room for improving the situation and exploring whether that should be a category/bundle in follow-up issues.
As long as it is not a bundle, that's also the reason for why the entity needs to disable 'fieldable'.
As mentioned, I'm happy to investigate possible ways to improve the user contact form handling. However, given the size of this patch, I'd really prefer to do that in separate issues. :)
Comment #39
catchThe general approach here is interesting and I quite like it.
A few things came up, the only things I really had trouble with were:
- relying on no base table being set to indicate no storage. Dpesn't feel right although I don't have a great suggestion.
- putting this straight into entity info means that patches like the entity reference one are going to list it as something which could be referenced, which of course it can't be. This is strongly linked to the no storage issue.
Both of those need more thought here, so marking CNW.
On the other hand it's interesting that there's only a couple of one-line changes to support entities with no ID and that seems pretty harmless, although I could be missing something.
I think the coding style is without the forward slash.
I have no idea what this is doing nor why it is needed.
Does AccessDeniedHttpException let you pass a custom message?
Like berdir said, we're not at the point with the entity/storage controllers where core entities can get away with out them. Would be good if they could, but they're all still drastically different and doing their own weird things a lot of the time.
When does an entity get created without a category?
I'm not sure about relying on the lack of base table, that seems a bit fragile. There could be a null storage controller but then we currently allow people to swap that out.
Comment #40
BerdirI have a contrib module that exposes data from a remote CRM system as entities in Drupal which obviously don't have a base table. They do, however, have a "storage" (which is using http requests). It even provides (incomplete and hackish but working) EFQ and with that and efq_views, views integration.
So, I think this is a valid question, considering things like the hardcoded check in field_has_data(). Not sure what the answer is :)
To answer your questions, entity_page_label() might sound misleading, but we specifically kept it for title callbacks where you can't call the label method.
More or less. For content entities, the main issue is currently the comments conversion, which is currently containing things like a BC layer (which we need so that we can convert entities to NG without touching fields and once everything is converted convert fields and then throw the BC layer out) and performance improvements to not be that much slower on a page with 300 comments (comments being a nice example of that use case). So most of the other things are blocked on that. Like, e.g., the aggregator feed item as entity issue, where I'm already converting to EntityNG directly.
Comment #41
sunThe actual reason for that exact condition is that the new Entity Query throws an exception on the exact same condition. I've clarified the comment.
The new Entity/Field Query generally seems to have much more limitations that the old EFQ did not have. One of those limitations is the aspect we're facing here: Technically, Field API should perfectly be able to query the field data tables only, without any kind of dependency on the entity storage - I do not understand why this dependency exists in HEAD. I don't know whether that was an intended design choice for whether it's an architectural bug — the new EFQ is fairly complex, so I didn't have time to study it yet.
Apparently, earlier patches in this issue worked just fine without any kind of tweak to Field API for that. Only after the new EFQ landed, those exceptions started to happen. That is why I do not see that detail to be a problem that needs to be figured out in this issue. I think this will have to be addressed in #1845372: Deleting a field from a non-node entity bundle results in an Entity Field Query Exception and/or related issues (I already commented over there).
Well, I see two points there:
1) The issue summary already links to a follow-up issue that might introduce an actual storage of contact messages (which would be nice to have). If that won't happen for D8 core, then I guess that a contrib module would actually be able to plug it in. Therefore, I do not think it is not guaranteed that contact message entities will never be stored.
2) We have the same issue with config entities, and we have a very similar issue with regard to generating entity tokens and all entity types that cannot be rendered.
I think this overall issue needs to be addressed in a much larger scope. At least it doesn't make sense to me to implant a workaround for the contact message entity type only, when there is no sign of a solid idea or conclusion on how to resolve the overall problem space. That said, I'm fairly sure that the final solution will be based on entity type metadata (entity info property/ies), so I think that the contact message entity being introduced here can be brought in line very easily.
It has a $message argument and so I tried, but its value is not used. I'm not sure whether it's supposed to be used for public/user-facing messages though, as it's documented with "internal message." We'll have to figure that out more generally - I'd agree that it would be most natural to just pass a message along with it — OTOH, an internal message (for logs) sounds helpful to me, too. (But the current $message does not appear anywhere.)
Changes:
Comment #43
sun#41: contact.entity.40.patch queued for re-testing.
Comment #44
sunI'm not able to reproduce that test failure.
Looks like we have yet another random test failure:
Comment #45
sunReplaced contact_menu_title_category() with entity_page_label().
With that, I think we should be back to RTBC here.
My stance on the two major issues that have been raised:
1) The question of which entity types can be referenced by an Entity Reference field and other things can reasonably not be addressed or solved by this issue/patch. Due to taxonomy vocabularies, that's a pre-existing problem space in HEAD. All this issue/patch is doing is to add another entity type to the existing stack of entity types. In turn, I don't see that as something to be fixed here.
2) The EntityNG conversion does not seem to be documented and could also involve risks regarding performance and other topics - in general, that API still appears to be in flux and does not appear to be stable enough in order to base major features onto it. There are other major feature requests in the queue that are not based on EntityNG yet. Therefore, I think that conversion should happen in a follow-up issue.
So I hope we can move forward here. :)
Comment #46
sunTentatively moving back to RTBC, since I think all review issues have been either fully addressed by code comments or issue comments.
Comment #47
andypost+1 RTBC
Some follow-ups needs to be filed:
- views support
- settings to change a tabs/menu-items/dropdown for listing of categories if front
- upgrade path, the introduced UI change could break sites (tabs is not a coomon navigation
I think that we need a some settings for that, probably better to have config screen to change tabs/menu-items for that
Comment #48
webchickLooks like catch has got this one.
Comment #49
andypostPatch needs re-roll for already commited part #1848874: Clean up and simplify user cancel methods and processing
Comment #50
sunComment #50.0
sunUpdated issue summary.
Comment #50.1
sunUpdated issue summary.
Comment #50.2
sunUpdated issue summary.
Comment #50.3
sunUpdated issue summary.
Comment #51
sunI've added all known follow-up issues to the issue summary.
Comment #52
catchI still don't see why this is necessary. Are you doing this to avoid looping over the contact entities in hook_menu()?
If so there should be two simpler ways to do it:
- do the expansion in hook_menu(), it's ugly creating all those router items but it's considerably simpler and it's what we do for block/theme configuratoin for example.
- have an admin page with a list instead of tabs such as we do for menus and content types. This will also allow the UI to scale to some extent if someone defines 200 contact categories so seems like the better option.
I opened #1856936: Consistently define 'no storage' or 'external storage' for entity types for the storage / no storage / base table stuff.
Comment #53
sunThe tab expansion happens for the public/user-facing
/contact
path, not the administration pages.Almost identical code is contained in the following three issues:
#1067408: Themes do not have an installation status — expands tabs on
admin/structure/blocks/%
andadmin/appearance/%
#1668292: Move simplified Profile module into core — expands tabs on
admin/user/edit/%
#1825044: Turn contact form submissions into full-blown Contact Message entities, without storage — expands tabs on
contact/%
The technical reasoning for this code is:
There is only one route, which receives a dynamic argument. It is not correct to duplicate route definitions, if the routes are factually identical and only vary by request parameter. Doing so would not only harm overall system performance, but would also make it harder to override the routes.
In all of these cases, the dynamic request parameter is solely used to expose a link as local task that has a valid value for the parameter filled in. All of the routes are identical, only a path parameter varies. The router/routing system translates inbound request parameters into routes. However, it is not its job to generate routes. The link expansion happens in a different layer of the system, the presentation layer. Which also means that the expansion only happens when actually needed.
Expanding all possible arguments into separate routes in
hook_menu()
requires and adds a huge chain of dependencies to the router rebuilding. Furthermore, it also adds the reverse: the route definitions depend on a certain, undefined state of the dynamic parameters. Therefore, whenever a contact form category is added, edited, or deleted, the entire router would have to be rebuilt. For no good reason at all, since all of the routes are identical. We don't want and we don't need more of these improper route definitions in core. Quite the contrary, we want and need less of them. Ideally, we eliminate all of them for D8. They caused us nothing but harm in the past, proven by a dozen of major bugs in the core queue.Instead of rejecting patches like this that do things properly, I'd rather recommend that we should move forward with the tab expansion in all of the mentioned issues above. Once they are in place, we will have a better idea of how this algorithm could be abstracted and simplified for implementing modules. And even if that generalization won't happen for D8, we'd at least do things properly. However, given the new routing system in D8, I'm fairly confident that we will have to do significant changes to
hook_menu()
anyway.Lastly, note that there is a follow-up issue filed already that asks for a simple configuration option for contact categories to control whether they are exposed as tabs on /contact or not, so they can be linked to and accessed via custom/individual links instead. If the routes were directly expanded in the router, then we'd unnecessarily have to create all the routes, but hide them, so they do not appear as tabs. That logic would make little sense, as it would mean to just add things in order to hide them. Instead, it makes much more sense to only generate and add what should actually be added. The router is the wrong place for that.
Comment #54
catchLet's just remove the tab functionality for contact forms then. I agree dynamic hook_menu() items are ugly and would also like to kill them, but that tab expansion functionality just does not belong in contact module.
Comment #55
sunUm? How do you access the non-default contact category forms as a user/visitor without tabs?
Comment #56
sunI'm still not sure I understand, but if I interpret #54 correctly, then the direction is to
1) Remove the exposure of categories entirely for now, and instead,
2) Discuss and evaluate how to allow users to expose links to category-specific forms in a separate follow-up issue?
3) And in the worst case, simply accept that site admins will need to manually create links to specific category forms?
I hope I got that right — works for me.
Changes:
Comment #57
sunFeedback, anyone? :)
Comment #58
moshe weitzman CreditAttribution: moshe weitzman commentedI'm not sure what the problem is with dynamic hook_menu implementations but anyway, catch's feedback has been addressed so back to RTBC.
Comment #59
catchSo the problem with dymamic hook_menu() implementations is they bloat the router table (although not as much as the millions of configuration forms under /admin), and we issue lots of uncacheable queries against the router table - mainly to find the current router item for the request. Having a huge router table is also part of the reason why we have our own, custom router storage in Drupal 8 for the new Symfony-based router rather than using theirs (although there's other reasons too, but Symfony's is designed for a couple of dozen router items and Drupal sites can have a couple of thousand).
I think it's fine to remove the dynamic links to contact category forms and add those back in a follow-up, could we do two follow-up issues then:
- add the contact links back somehow
- add this dynamic tab-expanding feature to the menu system (even if it takes a couple of specific implementations to get that right, I still don't think contact module is the place to start).
Comment #60
sunSpin-off: #1864066: Simplify hook_menu_local_tasks() and MENU_DEFAULT_LOCAL_TASK usage
Comment #61
sun#56: contact.entity.56.patch queued for re-testing.
Comment #63
sun#56: contact.entity.56.patch queued for re-testing.
Comment #65
sun#56: contact.entity.56.patch queued for re-testing.
Comment #66
sunJesus, we have a dozen different random test failures in HEAD currently. :-/
Comment #67
Dries CreditAttribution: Dries commentedLooks like this is ready to go now. Committed to 8.x.
Comment #68
sunSummary of follow-up issues
Menu system:
#1864066: Simplify hook_menu_local_tasks() and MENU_DEFAULT_LOCAL_TASK usage
#1866998: Introduce tab expansion facility for local tasks that are based on configuration
Entity/Field system:
#1845372: Deleting a field from a non-node entity bundle results in an Entity Field Query Exception
Other:
#1866978: Leverage new #input facility of #type 'item' for author name in comment form
Contact module:
#1849158: Expose contact category-specific forms and/or their URLs somewhere
#1856556: Convert user contact form into a contact category/bundle
#1856560: Store and render contact messages (and add Views support)
#1856562: Convert "Subject" and "Message" into Message base fields
#1856564: Split contact message 'mail' view mode into 'mail' and 'copy'
#599770: Clean up the contact forms listing UI: Allow to set the default category and weights on the listing page
Comment #68.0
sunUpdated issue summary.
Comment #69
sunAlso: #1867030: Contact message preview appears at random form position after sorting fields in Manage fields (which could use technical/architectural feedback on entity previews, if anyone has ideas)
Comment #70
yched CreditAttribution: yched commentedSo this results in $message->bundle() === NULL on "personal user contact form" messages, which is a problem, bundle() should always be set I think.
This breaks #1852966: Rework entity display settings around EntityDisplay config entity when trying to view the result posting a user contact message, since the patch over there does:
In short, an empty value in one of those will cause headaches down the road, and we don't want to have to bullet-proof for those - every entity should have a bundle.
In order to get back that patch to RTBC, I'll remove those consistency checks for now with a @todo to add back when #1856556: Convert user contact form into a contact category/bundle is done.
Current tests should be ok because nothing currently tries to save display settings for the 'personal user contact' messages.
Comment #72
larowlanAdded #1881856: Contact module uses deprecated hook_entity_info, perhaps should be hook_entity_info_alter? followup.
Comment #73
andypostThere's no such file at all!
Comment #74
sunThanks. Not sure how that happened.
Comment #75
tstoecklerYup. +1
Comment #76
webchickHm. I can commit that of course, but why on earth did something not blow up hugely about this? Seems like we're lacking test coverage for list controllers? I searched the issue queue and didn't find an issue about that, but let's either add the tests here or file a follow-up (most likely major task, I guess) for them first.
Comment #77
sunIt does not blow up, because that particular meta info is not used anywhere. It's just simply needlessly registered information.
Contact messages are not stored, and thus they are not listed anywhere. Hence, there's no EntityListController.
We do not need to test code that is not used anywhere and which shouldn't exist in the first place.
Comment #78
webchickOk cool, thanks for the explanation.
Committed and pushed to 8.x. Thanks!
Comment #79.0
(not verified) CreditAttribution: commentedUpdated issue summary.