Looks like an instance of 🐛 Parameter "arg_0" must match "[^/]++" Needs work .
freelock → created an issue.
That issue does sound very similar. One difference is that we were seeing the issue with limited cardinality, too -- although a cardinality of 6... but I'm guessing if ECA is replacing the form_state for some reason that might do it...
We are hitting this issue on a bunch of sites -- or rather 🐛 Integrity constraint violation when trying to add multiple blocks in a complex IEF field Active , which I did not think ECA had anything to do with until someone left a comment there about ECA. All of the affected sites have ECA enabled. Here's the gist of what we're doing:
1. Install IEF, ECA Form modules
2. Create a "featured block" entity reference field on a content type, e.g. "Basic Page", referencing content blocks.
3. Set the field to use the complex IEF widget
4. Create a new page, and create a new block using the IEF widget.
If you save now, everything is fine. If you edit the page and add another block, it works fine -- so long as you only ever add one block at a time.
5. If you add a second block without saving the node, it appears correct -- it adds another row to the IEF widget, and you can keep adding more.
6. But if you save, you get the Integrity Constraint Violation, duplicate entry 'xxx' for key 'node__field_uuid_value'.
This is affecting node and taxonomy term references as well as block references -- anywhere the IEF complex widget is used with multiple cardinality, if eca_form module is enabled. Doesn't matter if there's an ECA model using it.
Turns out it's a known issue of ECA - 🐛 ECA Form breaks complex IEF widget Active . Leaving this open in case the fix is better handled here in IEF.
Oh interesting, we're using ECA on the affected sites -- we're hitting this issue on at least 4 different sites, and ECA is active on all of them.
Commented in Slack #ECA channel with a link to an old blog post where I created a custom price resolver.
So what I'm wondering now is if it's possible to alter a price resolver's priority -- or have a price resolver derivative that allows a price resolver instance (or multiple instances) to get created with a specified priority. Wondering if this might be possible with a Symfony Decorator, if there's not a more Drupal-specific way?
As discussed in #ECA slack, I think we could implement a PriceResolver service with a high priority, that would return the desired price. This could then be set through an ECA plugin.
@ankondrat4 sorry to miss this, our client that we built this for hasn't been all that active. I'm not sure I'll be in here much, so I've added you with some basic roles -- if you're active on this, I can increase those, reach out to me on Slack!
Committed, thanks!
Fixed. Thanks for the contribution!
Fixed.
freelock → created an issue.
It's not the first time I've hit circular stuff with commerce -- all the way back to Ubercart, seems like commerce events fire a lot more often than I would think necessary. My answer is idempotence -- make sure if it's called multiple times, the result is always the same.
This is why there's a quantity field in the plugin -- each time the plugin runs, if the item is already in the cart, it updates the quantity to whatever value is in that field (which accepts a token), so you if you create a token for quantity based on other products in the cart, it can always do the right thing -- set the correct quantity on one order item.
If the "right thing" is to add additional order items with the same sku, this plugin won't do that. It won't even detect if there is more than one order item on the order -- it just sets the quantity on the first matching one it finds (but you can specify elsewhere that multiple order items with the same sku should always be combined).
@nicxvan what circular logic do you see?
freelock → created an issue.
Merged -- not fully tested, so making an Alpha release. Any issues found should be created as new issues!
Hi,
This action requires either a sku or a purchasable entity (product variation) to add, and an order (which is what a cart is under the hood). So you do need to load an existing order/cart to pass it in.
I used this event: https://ecaguide.org/plugins/eca/commerce/events/eca_commerce_cart_entit... which provided a cart (order) and a product variation entity. I could then check details for the product, and if it matched, add a different product to the cart by sku.
It does require the commerce cart and commerce order modules to be active, and the order -- so you need to get that somehow. Loading it by entity type/id should be fine -- is the one you're loading owned by the user who's executing your model? You often need to switch users if you're trying to run from drush or cron...
Cheers,
John
Hi,
The best way to send an Easy Email programmatically is to use ECA → . Both Easy Email and ECA are in Drupal CMS, so this is getting a lot more widespread use.
On the linked issue in #7, I had started a "Scheduled Message" module to do this, but we've switched over entirely to ECA.
Three of my recent Advent Calendar posts include this:
https://www.freelock.com/advent/2024/remind-customers-abandoned-carts
https://www.freelock.com/advent/2024/automatically-track-documentation-r...
https://www.freelock.com/advent/2024/send-roster-event-attendees-staff
... the gist is, create a view that collects the people you want to email. In ECA, you use a Views Query to get all of them, and send that list to a custom event (entity aware), which ECA will then call for each person on the list. From that event create a new entity of type easy_email, and the email template you want to send. You can then associate entity reference fields to your email with the views result, and use tokens in your template as appropriate.
Yes, it's working for us...
Added new action plugin.
Note that during testing, the "Add entity to cart" event got executed multiple times (twice) -- so in my initial cut, it was adding the extra product twice.
To avoid this, I made it check the existing cart for a matching order item, and if found, it sets the quantity of that item instead of adding a new one.
This works fine for my needs. I'm not sure if there is a good way to avoid this problem otherwise.
freelock → created an issue.
Similar question on the ECA queue - 💬 A honest comparison / contrast of ECA vs Rules? Active .
I Just turned most of my response into a blog post, here: https://www.freelock.com/blog/john-locke/2025-01/ask-freelock-eca-vs-rules ... would love any comments people have on that, especially from people who have used Rules recently.
Comments there are now auto-moderated by AI -- if you leave a relevant comment, it should get automatically published within 5 minutes!
@w01f I wonder if you could get a reasonable starting place on a comparison table by asking Claude or something for one...
Just turned most of my response into a blog post, here: https://www.freelock.com/blog/john-locke/2025-01/ask-freelock-eca-vs-rules ... would love any comments people have on that, especially from people who have used Rules recently.
Comments there are now auto-moderated by AI -- if you leave a relevant comment, it should get automatically published within 5 minutes!
Hi,
Not sure there's a good person to comment on this -- I just became aware that Rules had a stable release earlier today, answering a client question. I used Rules extensively in Drupal 7, but found it too buggy and cumbersome to use broadly ever since Drupal 8 -- one of the key bugs we hit is still not fixed, after I created a patch for it 8 years ago: https://www.drupal.org/project/rules/issues/2816033 🐛 Rules registers no listeners on rare occasions. Needs review
... that said it looks like there's a decent amount of activity on Rules in the past 6 months, after very, very little for most of the past 8 years.
I started using ECA a little over 2 years ago, and I have not found anything I could do in Rules that ECA doesn't do, and it usually does it better. ECA has the advantage of starting in the Drupal 9 architecture -- Rules seems to still have a legacy architecture that requires info hooks to register plugins, and a lot of refactoring to do to be as streamlined, effective, and powerful as ECA.
This means ECA plugins are really simple to create. ECA also supports just about every event Drupal dispatches, and supports core action plugins as well as ECA action plugins -- this means there's a lot of existing code that "just works" with ECA even though they were built with other systems in mind. That means you can do more with ECA!
Aside from code, I see one significant architectural decision that seems different: Rules seems tightly integrated with Typed Data, whereas ECA is tightly integrated with Token. I see on the Rules page that they changed their tokens from Token module syntax to Twig syntax, calling out that this allows them to leverage the TypedData system to support any data property on the various objects involved, as long as the TypedData is set up correctly -- and point out that Token is generally limited to strings.
ECA on the other hand has expanded its use of tokens to include objects and lists, as well as wrapping them in "DTO" - Data Transfer Objects. So this basically means the two systems have made a very different decision on how to solve the same problem -- passing structured data around. Both of these seem legitimate to me -- the typed data approach might lead to better (or at least easier) validation of data objects, but less flexibility. So that means it might be easier to find the data elements you need when working with Rules, compared to ECA where you're using the Token Browser which doesn't always show exactly the token you need.
That said, I think there's a lot more support for tokens than TypedData, so that's another area where ECA might have the edge in terms of pure breadth of available "stuff" you can do with it, while possibly being harder to discover exactly how to hook it all up.
The other differences that come to mind, take my feedback with a grain of salt -- we've been all in on ECA for over 2 years, and I have not looked at Rules since we switched over -- but here are my informed opinion of ECA vs uninformed opinion of Rules:
- Lists -- ECA has a "list" token built in that allows loops, entire subroutines acting on each item of a list (or row of a view). Loops in Rules are cumbersome and confusing -- and "Rules Components" which allow you to do subroutines are on entirely different admin pages
- BPMN -- I would not minimize how powerful it is to be able to visualize the flow of events, particularly when talking with stakeholders and less involved people.
- Documentation -- there's still a lot of room for improvement here, a lot of stuff that's missing -- but Jurgen has done a fantastic job of setting up a documentation site, making it so all actions/events/conditions link to a doc page that lists tokens, etc. and is full of examples. He also did a series of videos that walk through a lot of different scenarios. But it's not just documentation of ECA -- in the BPMN modeler, it's easy for you to document your models. This is huge, particularly when you come back years later to change some functionality. There's a documentation field in each action, flow, and event where you can put notes, and you can attach freeform notes anywhere you want in the model. The Rules documentation seems woefully out of date -- but on the other hand when I last looked at it it was quite similar to Rules in Drupal 7, so maybe there's some old docs that still apply?
- Performance - original versions of ECA might've carried a small performance penalty because it had code called for every event in the system which had to then check if there was any ECA model to execute. That's why there are so many submodules so you could turn off swaths of functionality. Now that's no longer the case -- ECA manages its event handlers so they are only run if the event exists in a model. I haven't seen any benchmarks of either ECA or Rules, and I don't know what sort of performance impact Rules 4.x has.
Regarding stability, the move from 1.x to 2.x was a bit rocky -- a bunch of our models broke, silently. 2.x does have a lot better validation, and the event performance improvements which I think were really the cause of the breakage -- in 2.x the event registration is centralized and standardized, which meant a lot of the tokens that were defined in the original event plugins now need to be properly declared. These were easy to fix, but caught us by surprise. On the other hand, I'm seeing lots of issues in Rules that still seem to block a bunch of scenarios, and it wasn't marked stable for nearly a decade -- is it really stable, or just enough pressure to declare it so that it (finally) starts getting security team coverage and enterprise adoption (which is not meant as a slam, that seems totally legitimate to me). I think ECA has had so much more attention and excitement from the active Drupal community than Rules in the past couple years.
So all in all, I think ECA wins in just about every category - the one thing Rules has that ECA doesn't is that tight integration with Typed Data.
I'm quite curious to hear what others have to say...
Proposed fix in MR.
We hit an issue where we had multiple events that use the "Determining Entity Access" plugin, and hit this issue. After reviewing, I think the solution proposed by @ergonlogic is incorrect -- I do think the result should get initialized with AccessResult::neutral(). However, if there is more than one of these events in your site, the last one wins because of this reset.
In our case two access checks applied to a particular action:
node:application, update
node:*, view,update
The first was setting an AccessResult::allowed, the second AccessResult::neutral. After processing the ECA event, AccessResult::neutral was the final result, and access ended up being denied.
I think the correct solution is to make use of AccessResult::orIf(). In the event plugin, setAccessResult() method, I think it should see if it already has an access result set, and if it does, replace it with $this->accessResult->orIf($result).
This allows all the access rules to get checked, and reasons are available for all of the models.
The logic for the orIf() call is to return AccessResult::forbidden if either the existing or new accessresult is forbidden; return AccessResult::allowed if either the existing or new result is allowed, and return AccessResult::neutral if both are neutral.
The previous solution is still subject to the "last check wins" if multiple checks return something other than neutral.
freelock → created an issue.
I think Key module is the appropriate place for it. It is a plugin, so it can live really in any module -- was putting this out as a feeler for whether you'd accept it here.
Will work on this when I get a chance...
Finally got a chance to test this, and spent hours trying to figure out why it didn't work.
I'm not exactly sure why, but the "data_yaml" config form setting is not saving correctly on my instance. It always comes up blank, and if I set it to "yes" it remains 0 in the underlying config.
I'm trying to track this down, because I don't see any obvious issues -- the form element is set to be a checklist but it's rendering as a select with yes/no values, so I think something is overriding the form rendering for this element for some reason. Will file an issue when I know what it is...
@dbdrupal that seems like a good new issue to create -- make the ckeditor dependency optional, so you can still use the views functionality?
Merged.
freelock → made their first commit to this issue’s fork.
Merged.
Merged into 2.0.5.
Thanks for the report!
freelock → changed the visibility of the branch 1.0.x to hidden.
freelock → made their first commit to this issue’s fork.
freelock → created an issue.
Fixed.
freelock → created an issue.
Options doesn't work for files, at least not binary ones.
If there's unrecognizable characters, the Yaml parser throws an exception, and you're done.
Guzzle takes one of:
- A file pointer (created by fopen)
- A stream wrapper
- The raw file contents
I don't see any way of getting the first 2 into a token, and the third chokes the Yaml parser.
Ok, I'm not exactly sure how I messed up the MR, but you can at least see the direction I'm going. With this, I'm setting the data value to:
file: "[entity:field_door_image:entity:path]"
description: "[entity:field_door_image:alt]"
and it's successfully posting an image.
For more, see the post that's automatically publishing in a few hours: https://www.freelock.com/advent/2024/automatically-post-mastodon-or-othe...
freelock → created an issue.
All that needs to happen for this is creating some plugins -- and probably not many.
Event plugin -- Webhook received
Action plugin -- Send webhook
... since Webhooks are entities, much of the normal entity ECA plugins should already work. Are there any other plugins we need?
New action plugin -- this is working on a site in a custom module -- only difference is updating the namespace to eca_cache and updating the plugin id.
I'm a bit rushed at the moment, but I created an action module for a client to get this done:
namespace Drupal\eca_cache\Plugin\Action;
use Drupal\Core\Access\AccessResultInterface;
use Drupal\Core\PageCache\ResponsePolicy\KillSwitch;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\eca\Plugin\Action\ConfigurableActionBase;
/**
* Provides a Page Cache Kill Switch action.
*
* @Action(
* id = "page_cache_kill_switch",
* label = @Translation("Page Cache Kill Switch"),
* description = @Translation("Kill the page cache for this page")
* )
*/
final class PageCacheKillSwitch extends ConfigurableActionBase {
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
$self = parent::create(
$container,
$configuration,
$plugin_id,
$plugin_definition
);
$self->KillSwitch = $container->get('page_cache_kill_switch');
return $self;
}
/**
* {@inheritdoc}
*/
public function execute(): void {
$this->KillSwitch->trigger();
}
}
freelock → created an issue.
Unfortunately the code in the MR is not working for me...
Regarding 4th level menu items, if you add them as a 4th level do they just not show up? If so, seems like that would be the right solution -- they are there when the upstream adds that functionality...
Cheers,
John
freelock → created an issue.
Hey I upgraded a ckeditor module that had a dialog in it, it's a bit of setup to do but not too hard. I have other priorities and don't have a client to fund this (yet) but I'd be willing to work on this in the next month or two...
+1 to this -- our media image bundle has a field for "source" meant to be a link to the image source, and copyright notice. I think this should be part of any media automation.
This automator is really cool! But I have no idea who shot the specific images it selects...
freelock → created an issue.
+1 on this -- please make the 2.x version have a bigger number than the downgraded 3.x version!
Ok this just bit me again... the fix does not fix my installation, at least not under PHP 8.1. The DependencySerializationTrait does not appear to be adding the Time service to the _serviceIds[] property of the serialized object, so it does not add it back to the rehydrated Task object.
This has led to thousands of unprocessed queue items...
Can we re-open this issue? The original MR did fix it on my installation -- reapplying that as a patch is fixing it for me. DependencySerializationTrait did not fix it. A simple __unserialize() or __wakeup() method that adds this back might also work -- although it loses any benefit of dependency injection (if a different class was injected in the first place).
freelock → created an issue.
@phenaproxima (#4) ECA is very easy to add support for, just need to implement plugins as desired.
For events, the easiest thing to do is just use the normal event system, and then an ECA event plugin can subscribe to that.
If everything is in config entities, there may not be anything special needed to work with ECA -- ECA has plugins for entity events, fields, etc.
For this proposal, are you thinking the entire checklist and its state is stored as a config entity? And it contains a list of "task" datatype? Or is there a separate place -- e.g. field data on a field attached to some content entity for the checklist state?
I like that second architecture -- I would like to have a "template" checklist that can be reused a bunch of times. However, I do want to be able to customize the checklist each time it is used. I'm thinking the ideal model for what I'm going for might look something like this:
1. Config entity for the base checklist, containing a set of default tasks
2. Field attached to an entity, which has a "checklist override", and "checklist state"
... the checklist override could suppress specific tasks on the checklist, and add additional as appropriate.
The checklist state would track each task's status -- checked or not, user who checked it, time it was checked, notes, link.
A task (without its state) should also have - label, help text, optional link to instructions.
Is that inline with what's being planned here?
Cheers,
John
I'm extremely interested in this. We have a custom "checklist field" I've been using for a while, which is currently a front end built in Vue.js that allows nestable checklist items, each item with the ability to have a note completed by the person using it, a link that may point to a reference page for more info about the task, the checklist state, as well as the "result" - (completed, not completed, not applicable), and the user who checked the box and the time it was checked.
Right now we load the entire thing in a custom field type, which has a single database column -- this serializes the entire checklist as Yaml, which the field formatter loads into the Vue widget, and then uses JSONAPI to update (right now with a save button that re-serializes the entire list and posts it back to the entity). The field widget is just a text area so we can copy/paste entire lists from one instance to another.
We use this for a QA checklist when reviewing new sites, as well as a launch checklist. I also have it on a personal site where I use it for a camping checklist.
The "edit checklist" functionality right now is just edit raw Yaml -- our current UI is ok for actually running down a checklist, taking notes as you go, and outputting a report...
Just providing some ideas of extra requirements to consider for the data model...
This would be huge -- and I see it as the single biggest advantage of Gutenberg over other Drupal solutions...
freelock → created an issue.
Yikes, this is extremely confusing -- is the 2.x branch going to get deprecated? That's certainly the impression from the project page... is there an upgrade path from 2 to 3? Should anybody upgrade from 2 to 3?
I think the smarts incorporated in 2 are a big improvement over 1 -- if 3 is just about adding Drupal core compatibility I wonder why it didn't stay on the 1.x branch?
Almost seems like there should be two different modules.
Ok addressed the issues with the test failures.
Regarding removing from the buildEventData, seems like we should do that -- I was wondering if the annotations in the PHP attributes should be copied up to the getData ones? Specifically it has classes, and properties on the flaggings annotation -- are these correct, things we should add?
Not sure if this is the correct approach -- but it's working at least for getting "entity" passed. I'm not seeing "flag" or "flagging" showing up in the logs -- however, using [flag:name] did appear to resolve and work correctly.
I did try wrapping these in DataTransferObject::create(), but that did not work.
This does appear to fix my models?
Looks like in https://git.drupalcode.org/project/eca_flag/-/commit/8ba776511c935bb0032... , the tokens used to be defined in a BeforeInitialExecutionEvent hook, which added them directly to the token service -- this code got removed/moved into a BuildEventData() method on the event plugin.
I'm not exactly sure how this is supposed to be different -- do we need to add a getData() method, and return a DTO for the request key?
freelock → created an issue.
Just applied to another site... changes look and work fine to me!
FWIW, Reroute Email uses this pattern, as do a bunch of commerce module -- a global "enable" config that can act as a kill switch. It's a really handy pattern.
Config Split introduces a ton of complexity and confusion to managing configuration -- which means opportunities to make mistakes. We highly prefer a global config with specific environment overrides -- this allows us to have sane settings in the database for all dev environments, and hard-code specific overrides for production in a production-specific environment file.
Even better is being able to set these with environment variables, but that's an entirely different topic...
The only concern that comes to mind, if the exception is not temporary but permanent, e.g. due to a misconfigured ECA model, then the item remains in the queue forever, and we don't have any mechanism, to clean up such an issue. Should there be something that controls the max number of retries?
Hmm there are existing mechanisms for clearing out the queue -- drush, queue ui module.
Are the exception classes that were already being rethrown indicate things that we should pass along as is, instead of using a requeue exception?
I do think another consideration might be whether the user/developer considers the queue to be a reliable one or not -- if it's reliable, we need to make sure items get requeued regardless of what else happens... if not, it's not as big a deal to absorb the exception and continue.
We're often using queues so that API calls to remote services are reliable on otherwise unreliable connections (or unreliable services).
See https://www.php.net/manual/en/function.unserialize.php - after recreating the object, the __wakeup or __unserialize method on the object is called, if one exists.
Hi,
First cut at a fix for this -- it turns out that if we use a try/catch block on the event plugin, it never catches an exception because the EcaAction object catches it first.
Not wanting to hard-code a list of events to bypass exception handling, I was looking at the plugin tags to see if we could leverage that, not something I see implemented anywhere other than just tags on events.
So I ended up extending the Event plugin interface and base class with a new method, "handleExceptions", which the EcaAction can use to determine whether or not to rethrow exceptions.
For now I picked the DelayedRequeueException with a 10 minute delay -- this could certainly get more configurable with other exception types and more handling, but this solves what I need for a few different sites already.
Does this seem like an appropriate solution? Are there event plugins that do not extend the EventBase class?
I think it does fix queue processing to be reliable, at least more so than ECA does today.
Cheers,
John
Removing myself from assigned -- the site in question is now on 2.x.
Ok that should at least not break anything else...
Took a look at the other two classes in the chunk of code related to the fix.
Looks like the "FilterVariationsEvent" class does use getVariations() -- the other two classes use getProductVariation().
Fixed in MR........
freelock → created an issue.
Fix for product variation method name. I have not checked that the method name is the same in the other event classes -- this might need more work if the method name varies depending on which event class is used...
freelock → created an issue.
Sorry to mess up the branches -- I created two issue branchs, because the first one was against 1.x, not 2.x. So I created the second one.
It looks like @lukas_w added to the 1.x branch -- but it also looks like the same code is present already on 2.x, presumably for 📌 Add support for cache collector for state in core Active .
So I un-hid the branch with the fix, this looks like just be a backport of the other issue.
freelock → changed the visibility of the branch 3460404-event-eca-cron to active.
Note that this was introduced in the 2.x branch, does not affect 1.x.
MR created.
freelock → created an issue.
freelock → changed the visibility of the branch 3460404-event-eca-cron to hidden.
Can confirm it's an issue with jobs starting with cron. I'm seeing 📌 Add support for cache collector for state in core Active , which I initially thought might mean it's fixed in 2.0? But this looks like only affects eca_state plugins, which I'm not using in the affected module.
This is causing major issues on one of our sites -- I'll be figuring out a fix shortly (if changes in 2.x didn't fix it already).
MR contains my patch for allowing this config to get upgraded.
freelock → created an issue.
Setting to NR.
Hi,
I just ran into this issue on a site -- the Structure, Configuration, and Reports menu items were all gone from the admin toolbar, making it difficult to administer the site. This happened on all our site copies after updating to 10.3.0.
I went through in a debugger and found that it was checking the menu items to see if they were enabled, and if they were not, it was throwing an access denied (403) error.
Turns out this site had a bunch of unpublished menu items in the admin menu, which duplicated the ones that actually had children -- and the check was failing on these unpublished menu items.
I'm not sure if this is the right behavior -- seems like in some cases this could block admin access to unpublished pages. But the fix for this particular site was simple -- just delete the inactive duplicate menu links, and then suddenly the admin items reappeared. I think this is the fix for what everybody on this thread is seeing -- if you have an inactive logout link, that might make the logout link invisible on other menus...
Updated patch for 10.3.0.
Hi,
Was just going through this module -- we have an automatic importer that creates an event every day -- but also a "restrictions" system that allows admins to create date restrictions. We were trying to hook this up using ECA to find events that already exist when a restriction is added, close registrations for that event, and notify the admin if there were any existing registrations. And found this just about impossible!
I added our previous code from ✨ Integrate with Rules module including host entity methods Active -- but that gets you a token for registration_settings (host entity) from a registration, NOT from the entity that the host entity is attached to. So I dug in trying to find how to get a host entity/registration settings from a node it's attached to --only to find it doesn't exist until somebody registers or changes the settings. Am I understanding this correctly?
If so, I'm thinking this is something we need -- a method added to the RegistrationManager that fetches an existing registration_settings entity if it exists, or creates it from the entity type/field settings if it doesn't. I'm thinking if this is added as a service, it can be maintained through whatever changes come from ✨ Allow admins to change host entity for existing registration Active and ✨ Establish host variations as an architectural concept Active , and would be generally useful for anyone trying to integrate this from other modules.
And then add an ECA action plugin that calls it to load a RegistrationSettings entity that could then be interacted with using normal content entity conditions/actions.
Are there other scenarios that need to be considered? I'm thinking what we're talking about would be
- Action: Load Registration Settings from Entity -- I guess this could also just be a token, [node:registration_settings] - or [node:field_registrations:entity] or whatever makes the most sense.
- Token: [registration:host_entity] - entity that a registration is associated with
- Token: [registration:registration_settings] - registration settings entity associated with a registration
There could certainly be other convenience actions and conditions, but really all that's required at a baseline are making some tokens work...
Thoughts?
freelock → created an issue.
We created this plugin for a client, and have had it in production for a year now!
namespace Drupal\qcyc_roster\Plugin\Action;
use Drupal\Component\Utility\DiffArray;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\eca\Plugin\Action\ConfigurableActionBase;
/**
* Provides a Load Diffs action.
*
* @Action(
* id = "qcyc_roster_load_diffs",
* label = @Translation("Load Diffs"),
* description = @Translation("Compare 2 entities and return a list of fields that differ"),
* type = "entity"
* )
*
*/
class LoadDiffs extends ConfigurableActionBase {
/**
* {@inheritDoc}
*/
public function defaultConfiguration(): array
{
return [
'token_name' => '',
'compare' => null,
'return_values' => FALSE,
'exclude_fields' => [],
'include_fields' => [],
] + parent::defaultConfiguration();
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state): array {
$form['token_name'] = [
'#type' => 'textfield',
'#title' => $this->t('Name of token'),
'#default_value' => $this->configuration['token_name'],
'#description' => $this->t('Provide the name of a token that holds the new list.'),
'#weight' => -60,
];
$form['compare'] = [
'#type' => 'textfield',
'#title' => $this->t('Compare'),
'#description' => $this->t('Provide the name of a token that holds the original entity.'),
'#default_value' => $this->configuration['compare'],
'#weight' => 30,
];
$form['return_values'] = [
'#type' => 'checkbox',
'#title' => $this->t('Return values'),
'#default_value' => $this->configuration['return_values'],
'#description' => $this->t('If checked, the list will return values. If unchecked, it will only return the different machine names of changed fields.'),
];
$form['exclude_fields'] = [
'#type' => 'textarea',
'#title' => 'Exclude fields',
'#description' => $this->t('List field machine names to remove from difference/ignore'),
'#default_value' => implode("\n", $this->configuration['exclude_fields']),
'#weight' => 40,
];
$form['include_fields'] = [
'#type' => 'textarea',
'#title' => 'Include fields',
'#description' => $this->t('List field machine names to include in difference -- all others will be ignored.'),
'#default_value' => implode("\n", $this->configuration['include_fields']),
'#weight' => 40,
];
return $form;
}
/**
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state): void
{
$this->configuration['token_name'] = $form_state->getValue('token_name');
$this->configuration['compare'] = $form_state->getValue('compare');
$this->configuration['return_values'] = $form_state->getValue('return_values');
$this->configuration['exclude_fields'] = explode("\n", $form_state->getValue('exclude_fields'));
$this->configuration['include_fields'] = explode("\n", $form_state->getValue('include_fields'));
parent::submitConfigurationForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
/** @var ContentEntityInterface $object */
$access = $object->access('view', $account, TRUE);
return $return_as_object ? $access : $access->isAllowed();
}
/**
* {@inheritdoc}
*/
public function execute($entity = NULL) {
$compare = $this->tokenServices->getTokenData($this->configuration['compare']);
$diff = DiffArray::diffAssocRecursive($entity->toArray(), $compare->toArray());
$exclude_fields = $this->configuration['exclude_fields'];
if (count($exclude_fields)) {
foreach ($exclude_fields as $field) {
unset($diff[$field]);
}
}
$include_fields = $this->configuration['include_fields'];
if (count($include_fields)) {
$included = [];
foreach ($include_fields as $field) {
if (isset ($diff[$field])) {
$included[$field] = $diff[$field];
}
}
$diff = $included;
}
if (!$this->configuration['return_values']) {
$diff = array_keys($diff);
}
$this->tokenServices->addTokenData($this->configuration['token_name'], $diff);
}
}
freelock → created an issue.
freelock → made their first commit to this issue’s fork.
freelock → created an issue.
My comment in #15 turned out to actually be a bug in Smart Date module, which is fixed in their version 4.1.0.
freelock → created an issue.