Add a confirmation form for all user triggered state changes; let the field formatter indicate if confirmation is needed or not. No need to differentiate transitions as originally suggested. The path for the confirmation form can be defined in an entity link template and will implement a default form handler defined by state machine. Optionally this can be overridden at entity level.

Entity annotation:

 * @ContentEntityType(
 *   ...
 *   handlers = {
 *     ...
 *     "form" = {
 *       "state-transition-confirm" = "Drupal\state_machine\Form\StateTransitionConfirmForm", // Optional
 *     },
 *   },
 *   ...
 *   links = {
 *     "canonical" = "...",
 *     ...
 *     "state-transition-form" = "/order/{commerce_order}/{field_name}/{transition_id}",
 *   },
 *   ...
 * )

The confirmation form class can be extended or altered via a form alter hook. The link template should most likely be based on the canonical link and becomes something like /order/123/state/accept.

Original issue

I was wondering whether it might be possible to add a confirmation dialog to certain 'dangerous' transitions. Use-cases could be transitions that trigger an email to a user or, that transitions cannot (easily) be reverted due to from/to constraints.

It could perhaps be integrated into #2786971: Support third-party settings on transitions, but I think it should have its own property in the transition. Something like this?

transitions:
    draft:
      label: Create
      from: [__new__]
      to: draft
    propose:
      label: Propose
      from: [__new__, draft, validated, in_assessment, proposed]
      to: proposed
>     confirm: true
    validate:
      label: Validate
      from: [__new__, draft, validated, proposed, deletion_request]
      to: validated
Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

Neograph734 created an issue. See original summary.

neograph734’s picture

Status: Active » Needs review
StatusFileSize
new5.15 KB

This is what I could quickly build. It is not perfect, as I think the confirmation should be a full page confirmation (like ConfirmFormBase), but that would require a route and passing entities between forms. So for now, one just has to press the same button twice.

Status: Needs review » Needs work

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

neograph734’s picture

Status: Needs work » Needs review
neograph734’s picture

I have been breaking my head on this. To further improve this, I felt a full page confirmation form (using ContentEntityConfirmFormBase) would be nice. In my own custom module I have implemented that like this:

 * @ContentEntityType(
 *   ...
 *   handlers = {
 *     ...
 *     "form" = {
 *       "state_transition" = "Drupal\module\Form\StateTransitionConfirmForm",
 *     },
 *   },
 *   ...
 *   links = {
 *     "collection" = "...",
 *     "canonical" = "...",
 *     ...
 *     "state-transition-form" = "/module/{entity}/{field_name}/{transition_id}",
 *   },
 *   ...
 * )

This leads to paths that look like /module/123/field_state/accept, which I think is neat. The confirmation form then takes the field name and transition ID from the path; builds the form and performs the state change. The entity is available via ContentEntityConfirmFormBase and upon completion the user is redirected back the the entity's canonical display.

I am however struggling to find a way to somehow do this for all entity types, and I am in fact wondering if I should make the assumption to add a default route to all entity types, let alone redirect them to the canonical display.

Perhaps using a per-entity link template is not so bad?

Any advice would be nice.

jsacksick’s picture

I'm in favor of implementing the confirmation form for all transitions... I don't see a harm in doing that and this allows us to skip the extra setting.

A per entity link template is ok I think, State machine can provide a form, and redirect to the canonical link if it exists?

Then the formatter can check if the link template is defined.

If both are defined, then it can proceed to building the confirm form, otherwise fallback to the existing, though it probably makes sense to add a formatter setting as well... Or even an alternate formatter that outputs link to the actual form.

  • Link template could be "state-transition-form"
  • Form handler could be "state-transition-confirm". (When not specified, we can provide a default one for entities);

We need a RouteSubscriber (similar to ContentTranslationRouteSubscriber) to automatically build the routes for entities providing the "state-transition-form" link template. We can provide a default form class and allow overriding it.

Thoughts?

neograph734’s picture

Assigned: Unassigned » neograph734

Thanks for the guidance!

Give me a few days to untangle what I had built in a separate module.

jsacksick’s picture

@Neograph734: I'm thinking a formatter setting makes sense.

"requires_confirmation" with a label that says "Requires confirmation before applying the state transition". So that people can freely choose between using the current buttons vs links to the confirmation form.

neograph734’s picture

Merge request opened (though I accidentally pushed to 8.x-1.x too, ignore that one).

I think that in commerce you'll need to define a link template and set the field formatter setting to 'require confirmation' and you are done. Form handler is optional and otherwise automagically added.

Link template:
* "state-transition-form" = "/order/{commerce_order}/{field_name}/{transition_id}",
Could be worth documenting... :)

Perhaps #2912849: Require confirmation for state transitions is a good issue for follow-up in commerce?

neograph734’s picture

Assigned: neograph734 » Unassigned
Issue summary: View changes
jsacksick’s picture

Assigned: Unassigned » jsacksick

Assigning this to myself, going to wrap up the patch and add tests coverage.

jsacksick’s picture

@Neograph734: It's a good thing that I worked on adding tests coverage as there was some issues that I fixed after I noticed tests weren't passing.

One of the issues was:

        ->setRequirement('_entity_access', "$entity_type_id.edit")

instead of:

        ->setRequirement('_entity_access', "$entity_type_id.update")

Fixed several coding standard issues, renamed the route subscriber, made minor other changes, and I think this is good to go...

neograph734’s picture

@jsacksick, Thanks for your work, luckily most of my effort was useable :). For my use case I needed view access permissions, but since state machine originally required the update permission I realized I needed to change it (but did it wrong). I tested in my development environment and faced no issues though, but that is probably on my end...

I have one remark; I see that you also changed the confirmation question and removed the entity label. I think that is a bit weird. Expecially in commerce where the transitions are not named 'Validate' or 'Complete', but instead they are 'Validate order' and 'Complete order' (which is better for on a button I think). But then the full question will become "Are you sure you want to Validate order?" I think it makes a lot of sense to add the order number (entity label) to the question. "Are you sure you want to validate order 123?" perhaps also transform the transition label to lowercase?

Other random examples such as "Are you sure you want to release [Random node title]?" or "Are you sure you want to promote [Username]?" do not look that bad either?

UPDATE
One remark, you validate transition id by '[a-z]+'. What about transition ID's with underscores that used to be valid?

jsacksick’s picture

Well, the reason why I removed the ID is that the transition label doesn't contain 'the'.

It should in theory say "Are you sure you want to validate THE order 19", but "Are you sure you want to Validate order 19" doesn't sound right...

You're technically already in the context of an order... Either way it's not perfect, but we could always put it back, and probably make sense to lowercase the transition...

neograph734’s picture

I struggled with it too, for a while I've even used transition_id, entity_type_id and entity_label to build a sensible sentence... but faced issues with underscores there as well. So I agree there is no perfect solution.

jsacksick’s picture

Sample questions we could build:

  1. “Are you sure you want to Place order?’
  2. ‘Are you sure you want to place order?’
  3. ‘Are you sure you want to Place order 18?’
  4. ‘Are you sure you want to place order 18?’
  5. ’Are you sure you want to apply the ‘place’ transition for order 18?'

'Are you sure you want to apply the ‘place’ transition for order 18?' sounds ok... I guess.

Where "order" would be the entity type singular label and 18 the entity id.

The entity label doesn't always make sense... (If we'd use state machine for nodes, having a long node title in the question would sound weird IMO.

neograph734’s picture

You cannot use the entity ID in commerce. With the order numbers per store that will lead to lots of confusion and the numbers will not match, so you will have to use the label (or override the logic in commerce off course).

Option 4 still looks good enough to me, but I am not a native English speaker.
Option 5 would not work for me. I have an 'Accept on behalf of user' transition with id 'accept_on_behalf'. That transition will not pass anymore because of the underscores (mentioned in #15) and it will also look very weird in option 5.

I'd be inclined to say that the 'best' option would be to implement a transition suffix on the button, so you can name the transition 'Place' and still have 'Place order' on the buttons, but that will add complexity. Then you could ask ‘Are you sure you want to place the order?’

jsacksick’s picture

StatusFileSize
new14.75 KB

Reverted the previous question (but the transition is now displayed lowercase).

I'd say it's good enough for now.

So, if we agree on this, I'll commit the patch and tag a 1.1 release.

We can then update Commerce to require State machine 1.1 and make the proper changes.

  • jsacksick committed 46364c9 on 8.x-1.x authored by Neograph734
    Issue #3051503 by Neograph734, jsacksick: Allow requiring a confirmation...
jsacksick’s picture

Status: Needs review » Fixed

Went ahead and committed the patch, thanks @Neograph734.

jsacksick’s picture

Note that I changed the variable names in the question to "Are you sure you want to %transition_label @entity_label?".

neograph734’s picture

I was scanning over the code one last time, and overall I think this is a great improvement. But I just noticed that the field name was removed from the alterRoutes, hence my slow reply.

MY version:

          ->setRequirement('_entity_access', "$entity_type_id.edit")
          ->setRequirement($entity_type_id, '\d+')
          ->setOption('parameters', [
            $entity_type_id => ['type' => 'entity:' . $entity_type_id],
            'field_name' => ['type' => 'string'],
            'transition_id' => ['type' => 'string'],
          ]);

Your version:

        ->setRequirement('_entity_access', "$entity_type_id.update")
        ->setRequirement($entity_type_id, '\d+')
        ->setRequirement('transition_id', '[a-z]+')
        ->setOption('parameters', [
          $entity_type_id => ['type' => 'entity:' . $entity_type_id],
        ]);

Was this on purpose? The route does not make sense without a field name and the linkt template expects it.
And since the transition_id is regex, I was hoping to include underscores by changing it to '[a-z_]+'.

Apart from those nits, I am very happy for your fast repsonses as well.

jsacksick’s picture

->setOption('parameters', [

          $entity_type_id => ['type' => 'entity:' . $entity_type_id],
        ]);

This is used by param converters... For upcasting the entity ID to a full object.

I looked at all the routes defined in my codebase, I've never seen the following for string parameters.

            'field_name' => ['type' => 'string'],
            'transition_id' => ['type' => 'string'],

Regarding the regexp, it perhaps would have made sense to allow underscores, and also do the same for field names.

jsacksick’s picture

Well, maybe I should have skipped the regexp all together to not break transitions with underscores.

neograph734’s picture

I looked at all the routes defined in my codebase, I've never seen the following for string parameters.

Not sure where I've found it, but it did something; routes were invalid without.

Regarding the regexp, it perhaps would have made sense to allow underscores, and also do the same for field names.

Could you still commit it to dev? No need for a new tag (for me).

jsacksick’s picture

StatusFileSize
new10.63 KB
jsacksick’s picture

StatusFileSize
new666 bytes

Wrong patch

jsacksick’s picture

@Neograph734: Can you confirm the patch from #29 does the trick? And also confirm you're experiencing issues without it?

neograph734’s picture

I'm just shutting down for dinner, I'll have a look later this evening.
Thanks for taking it into consideration.

jsacksick’s picture

This patch allows underscores in the transition and field_name.
If you pass random strings, the page will crash... Wondering whether we should care, of create a custom access checker which ensures the transition you're trying to apply as well as the field name exists...

So that it returns a 403 if it isn't the case.

jsacksick’s picture

Status: Fixed » Needs review
StatusFileSize
new1.41 KB
new5.3 KB

I've been testing this more in depth and we do need an access checker... Otherwise, you can try applying transitions that are not allowed and the form will successfully load instead of returning a 403.

Additionally, passing an invalid field name or invalid transition name is going to crash....

jsacksick’s picture

Status: Needs review » Fixed

  • jsacksick committed a195b2e on 8.x-1.x
    Issue #3051503 followup by jsacksick: Ensure the state transition form...
neograph734’s picture

Awesome, just tested and everything from my end works as expected. Thanks so much for for fixing and improving this 2 year old feature in several hours!

Status: Fixed » Closed (fixed)

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