Problem/Motivation

There are use cases where a form may need to be disabled to prevent submissions.
At present this requires deleting the form.

Proposed resolution

Add a status flag to the contact form third party settings and form.
Add an access controller for contact_form.view or add to the one added in #2724499: Allow disabling forms to prevent new submissions that controls access to view the form based on a status
Add a test

Remaining tasks

Patch
Tests

User interface changes

API changes

Data model changes

Comments

larowlan created an issue. See original summary.

larowlan’s picture

Issue tags: +drupalcon, +sprint
larowlan’s picture

Issue summary: View changes
Bambell’s picture

Assigned: Unassigned » Bambell
Bambell’s picture

StatusFileSize
new7.03 KB

Here we go. I also added a test.

Bambell’s picture

Status: Active » Needs review
berdir’s picture

Status: Needs review » Needs work
  1. +++ b/contact_storage.module
    @@ -155,6 +156,16 @@ function contact_storage_entity_operation_alter(array &$operations, EntityInterf
    + */
    +function contact_storage_entity_type_build(array &$entity_types) {
    +  /** @var $entity_types \Drupal\Core\Entity\EntityTypeInterface[] */
    +  $entity_types['contact_form']
    +    ->setLinkTemplate('enable', '/admin/structure/contact/view/{contact_form}/enable')
    +    ->setLinkTemplate('disable', '/admin/structure/contact/view/{contact_form}/disable');
    +}
    
    @@ -193,6 +204,27 @@ function contact_storage_entity_type_alter(array &$entity_types) {
    +  $entity_types['contact_form']->setFormClass('disable', 'Drupal\contact_storage\Form\ContactFormDisableForm');
    +  $entity_types['contact_form']->setLinkTemplate('disable-form', '/admin/structure/contact/manage/{contact_form}/disable');
    +  $entity_types['contact_form']->setFormClass('enable', 'Drupal\contact_storage\Form\ContactFormEnableForm');
    +  $entity_types['contact_form']->setLinkTemplate('enable-form', '/admin/structure/contact/manage/{contact_form}/enable');
    

    You have set the link templates twice now. entity_type_build() should not be needed.

  2. +++ b/contact_storage.module
    @@ -193,6 +204,27 @@ function contact_storage_entity_type_alter(array &$entity_types) {
    +function contact_storage_contact_form_access(\Drupal\Core\Entity\EntityInterface $entity, $operation, \Drupal\Core\Session\AccountInterface $account) {
    +  if (!$entity->status() && ($operation == 'view' || $operation == 'update')) {
    +    // Forbids view and update access, but leaves the option to delete a
    +    // disabled contact form and to re-enable it ('enable' operation).
    +    return AccessResult::forbidden();
    +  }
    

    not 100% sure about what we allow and don't allow yet. Possibly we want to only disallow view.

  3. +++ b/src/Tests/ContactStorageTest.php
    @@ -167,6 +167,18 @@ class ContactStorageTest extends ContactStorageTestBase {
    +    // Disable the form and verify that it can't be accessed.
    +    $this->drupalPostForm('/admin/structure/contact/manage/test_id_2/disable', NULL, t('Disable'));
    

    Clicking them is always a bit tricky in the last, but can we check that we see the expected enable/disable links as many times as we expect them on the overview page?

  4. +++ b/src/Tests/ContactStorageTest.php
    @@ -167,6 +167,18 @@ class ContactStorageTest extends ContactStorageTestBase {
    +    $this->assertText('Access denied');
    

    Lets add a assertResponse(403) / 200 as well.

Bambell’s picture

StatusFileSize
new7 KB
new4.28 KB

There, should be good. I'm now allowing update rights on disabled forms (why not, indeed).

Bambell’s picture

Status: Needs work » Needs review
larowlan’s picture

Status: Needs review » Reviewed & tested by the community
  1. +++ b/contact_storage.module
    @@ -193,6 +194,27 @@ function contact_storage_entity_type_alter(array &$entity_types) {
    +  if (!$entity->status() && $operation == 'view') {
    

    nit ===

  2. +++ b/src/Tests/ContactStorageTest.php
    @@ -167,6 +167,32 @@ class ContactStorageTest extends ContactStorageTestBase {
    +    $this->assertEqual(preg_match_all('/<li class="disable"><a.*>Disable<\/a><\/li>/', $this->content), $contact_form_count);
    

    fwiw you could use $this->cssSelect('li.disable a:contains(Disable)') here

Looks good to me

larowlan’s picture

Thinking more about this, I'm not convinced that an access-denied approach is what we want every-time here (yes I mentioned access controller above). Maybe instead we add the option of a configurable 'this form is closed' message to the contact-form type.
And then in the view-builder, instead of rendering the form, we render that message.

So for cases where the user is embedding the form in a node/block etc - the render would render the message and not the form.

E.g. Someone creates a node about 'Submit a tender' or 'Enter this competition' and embeds a form in it using the view builder and an ER field with rendered entity formatter.

After this change, the form would not be shown but with no indication of why.

If we allowed for a configurable message, this would show instead - so the site builder/admin could make it say 'Thank you submissions for this tender have closed' or 'This competition ended on May 15th'.

We could collect the text from the disable form. No text = access denied.

Thoughts?

andypost’s picture

Status: Reviewed & tested by the community » Needs review

Additionally to #11 we have following in contact_storage_entity_type_alter():

  // @todo Replace with access control handler when not enough.
  $entity_types['contact_message']->set('admin_permission', 'administer contact forms');

Introduction of contact_storage_contact_form_access() makes module less flexible cos there's no way to override this access control...

So I put this back to NR to at least implement access handler and continue discussion about meaning of "disabled vs prevent submissions"

My big +1 to introduce another third party setting to "close submissions"

+++ b/contact_storage.module
@@ -193,6 +194,27 @@ function contact_storage_entity_type_alter(array &$entity_types) {
+function contact_storage_contact_form_access(\Drupal\Core\Entity\EntityInterface $entity, $operation, \Drupal\Core\Session\AccountInterface $account) {
+  if (!$entity->status() && $operation == 'view') {
+    // Forbids view access to the contact form.
+    return AccessResult::forbidden();

+++ b/contact_storage.routing.yml
@@ -0,0 +1,15 @@
+    _entity_access: 'contact_form.disable'
...
+    _entity_access: 'contact_form.enable'

Suppose it's time to implement access control handler!

andypost’s picture

Status: Needs review » Needs work

NW to get rid of contact_storage_contact_form_access()

berdir’s picture

A forbidden can't be overridden, even less so in an access control handler. I don't see a reason to do that.

I thought about the message, but that would be quite a bit more complicated. We need to alter the route to be able to do that. That could conflict with custom code already doing that.

dawehner’s picture

Status: Needs work » Needs review
StatusFileSize
new9.02 KB
new8.23 KB

Here are some small fixes.

In general I'm a bit confused how the operations ever worked.

andypost’s picture

Looks great for me!

berdir’s picture

Status: Needs review » Reviewed & tested by the community

I still think we could do without our own access class, but works for me.

zerolab’s picture

Status: Reviewed & tested by the community » Needs review
StatusFileSize
new6.19 KB

Attaching a re-rolled patch against latest 8.x-1.x as the patch from #15 failed to apply because of recent commits.

Status: Needs review » Needs work

The last submitted patch, 18: contact_storage-disable-form-2724499-18.patch, failed testing.

Bambell’s picture

StatusFileSize
new9.13 KB
new1.52 KB

@zerolab, thanks for the patch. Tests are failing, though, so I re-rolled #15 and made the cssSelect change recommended in #10.

Bambell’s picture

Status: Needs work » Needs review
zerolab’s picture

Status: Needs review » Reviewed & tested by the community

Cheers!
Just looked at my patch, it looks like I failed to git add . and it was missing the ContactFormDisableForm and ContactFormEnableForm classes :-(

The tests are back to green and since it is a re-roll, RTBC?

larowlan’s picture

I still think we need to be able to show a message when the form is embedded instead of access denied.

Does anyone else feel this way?

andypost’s picture

I think that should be configurable

Bambell’s picture

Status: Reviewed & tested by the community » Needs review
StatusFileSize
new14.88 KB
new12.56 KB
new6.18 KB

Here we go with a configurable message instead of denying access.

berdir’s picture

Status: Needs review » Needs work
  1. +++ b/contact_storage.module
    @@ -39,6 +41,12 @@ function contact_storage_form_contact_form_form_alter(&$form, FormStateInterface
    +  $form['contact_storage_disabled_form_message'] = [
    +    '#type' => 'textfield',
    +    '#title' => t('Disabled contact form message'),
    +    '#description' => t("Message to display if the contact form is disabled."),
    +    '#default_value' => $contact_form->getThirdPartySetting('contact_storage', 'disabled_form_message', 'This contact form has been disabled.'),
    +  ];
       $form['contact_storage_preview'] = [
    

    I think above, @larowlan had the idea to (additionally?) show this on the disable confirm form. I think that would be quite nice, as you are reminded of the message when disabling the form.

  2. +++ b/contact_storage.module
    @@ -136,6 +145,30 @@ function contact_storage_entity_base_field_info_alter(&$fields, EntityTypeInterf
     /**
    + * Implements hook_entity_operation().
    + */
    +
    +function contact_storage_entity_operation(EntityInterface $entity) {
    +  $operations = [];
    +  /** @var \Drupal\contact\ContactFormInterface $entity */
    

    I thought those shouldn't be needed as we get them by default?

  3. +++ b/src/ContactFormViewBuilder.php
    @@ -89,16 +89,30 @@ class ContactFormViewBuilder implements EntityViewBuilderInterface, EntityHandle
    +      $form['disabled_form_error'] = array(
    +        '#type' => 'container',
    +        '#markup' => t($entity->getThirdPartySetting('contact_storage', 'disabled_form_message', 'This contact form has been disabled.')),
    +        '#attributes' => array(
    +          'class' => array('messages', 'messages--error'),
    +        ),
    +        '#weight' => -100,
    +      );
    

    This is a frontend/user facing output. We might want to give users more flexibility to configure this. Probably create a template and pass in the contact form and the message as variables?

    The default output can still be the same.

    weight is not needed I think as nothing else is on that page.

  4. +++ b/src/ContactFormViewBuilder.php
    @@ -89,16 +89,30 @@ class ContactFormViewBuilder implements EntityViewBuilderInterface, EntityHandle
    +    $this->renderer->addCacheableDependency($form, $entity);
    

    a comment for this would be good. Something like // Add required cacheability metadata from the contact form entity, so that changing it invalidates the cache.

  5. +++ b/src/Controller/ContactStorageController.php
    @@ -0,0 +1,42 @@
    +  /**
    +   * {@inheritdoc}
    +   */
    +  public function contactSitePage(ContactFormInterface $contact_form = NULL) {
    +    $config = $this->config('contact.settings');
    

    I think a comment here that explains why we override this would be good. Something like "This is an override of ContactController::contactSitePage() that uses the entity view builder. This is necessary to show the close message for disabled forms.

Bambell’s picture

Status: Needs work » Needs review
StatusFileSize
new18.3 KB
new6.8 KB

I made those changes, including adding a template for the disabled form message and adding the disabled message textfield to the form to disable the forms..

I thought those shouldn't be needed as we get them by default?

Nope. the links won't show without this.

berdir’s picture

Getting close I think, good work.

Lets check those operations tomorrow, please remind me when you work on this.

  1. +++ b/contact_storage.module
    @@ -309,6 +308,12 @@ function contact_storage_theme() {
    +      'template' => 'contact_storage_disabled_form',
    

    templates usually use -, not _. Then you don't need to specify it explicitly.

  2. +++ b/contact_storage.module
    @@ -324,3 +329,16 @@ function template_preprocess_swiftmailer__contact(&$variables) {
    +function template_preprocess_contact_storage_disabled_form(&$variables) {
    +  $contact_form = $variables['contact_form'];
    +  $variables['redirect_uri'] = $contact_form->getThirdPartySetting('contact_storage', 'redirect_uri', '');
    +  $variables['disabled_form_message'] = $contact_form->getThirdPartySetting('contact_storage', 'disabled_form_message', 'This contact form has been disabled.');
    

    I think it is easier to just provide both variables directly in the render array. You don't need a detached preprocess function then. Sometimes, those are useful (when using render element, for example), but for such simple things, just passing things in directly is easier to follow.

  3. +++ b/src/Form/ContactFormDisableForm.php
    @@ -42,7 +42,23 @@ class ContactFormDisableForm extends EntityConfirmFormBase {
    +      '#default_value' => $this->getEntity()->getThirdPartySetting('contact_storage', 'disabled_form_message', 'This contact form has been disabled.'),
    

    I think the fallback should use t(), also in the other place. So when your site is spanish, you get a spanish message by default.

  4. +++ b/templates/contact_storage_disabled_form.html.twig
    @@ -0,0 +1,32 @@
    +<div id="block-bartik-content" class="block block-system block-system-main-block">
    +  <div class="content">
    +    <div class="messages messages--error">
    +        {{ disabled_form_message }}
    +    </div>
    +  </div>
    +</div>
    

    Just the inner div is enough I think, the two outer ones are actually provided by the wrapper already, and you'd end up with them duplicated.

Bambell’s picture

StatusFileSize
new17.72 KB
new3.58 KB

Here's for now, still with contact_storage_entity_operation().

Bambell’s picture

StatusFileSize
new16.73 KB
new2.46 KB

Here we go !

larowlan’s picture

Looking great @Bambell

  1. +++ b/src/ContactFormViewBuilder.php
    @@ -89,16 +89,29 @@ class ContactFormViewBuilder implements EntityViewBuilderInterface, EntityHandle
    +        '#disabled_form_message' => $entity->getThirdPartySetting('contact_storage', 'disabled_form_message', 'This contact form has been disabled.'),
    

    Should we translate the default here?

  2. +++ b/src/Routing/RouteSubscriber.php
    @@ -0,0 +1,23 @@
    +      $route->setDefault('_controller', '\Drupal\contact_storage\Controller\ContactStorageController::contactSitePage');
    

    nit: we could use ::class here

Bambell’s picture

StatusFileSize
new16.78 KB
new2.54 KB

Thanks for the review @larowlan ! Here are the changes.

larowlan’s picture

Status: Needs review » Reviewed & tested by the community

Thanks!

Will commit this later in the week unless someone beats me to it.

chandeepkhosa’s picture

Assigned: Bambell » Unassigned
larowlan’s picture

Status: Reviewed & tested by the community » Fixed

Thanks

  • larowlan committed cd8ba9a on 8.x-1.x authored by Bambell
    Issue #2724499 by Bambell, dawehner, zerolab, larowlan, Berdir, andypost...

Status: Fixed » Closed (fixed)

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