I'm trying to add some rules programmatically, I'm following this tutorial to manage different price list depending of the rules. To create the rules it usesa default_rules_configuration hook which will be executed "when the rules will be loaded".
1 - It's not really clear, when "rules are being loaded", apparently running the cron do it. Is it the only way to trigger it ?
2 - Is there a way to add rules programmatically, so rule can be added in the insert role hook, or is this default_rules hook the only way to do it ?
Thanks
1 - According to hook_default_rules_configuration() documentation:
This hook is invoked when rules configurations are loaded.
The function is actually called when you clear your cache as this is when Drupal rebuilds the default entities provided in code through entity_defaults_rebuild().
You can examine the full call stack as to how hook_default_rules_configuration function is called using debug_backtrace()
2 - To set a rule that reacts on inserting a role, you actually have to create a rule that reacts on a user insert action and then check the role saved to see if it matches the role that you're interested in reacting to.
I find it easier to do this via the UI. Here's an export of a rule that checks to see if the user is assigned the anonymous role and sends an email to admin if so:
{ "rules_role_change_rule" : {
"LABEL" : "Role change rule",
"PLUGIN" : "reaction rule",
"REQUIRES" : [ "rules" ],
"ON" : [ "user_insert" ],
"IF" : [
{ "user_has_role" : { "account" : [ "account" ], "roles" : { "value" : { "1" : "1" } } } }
],
"DO" : [
{ "mail" : {
"to" : "admin#website.com",
"subject" : "User role changed",
"message" : "User role has changed",
"from" : "drupal#website.com",
"language" : [ "" ]
}
}
]
}
}
You would still have to implement hook_default_rules_configuration but replace the rule in the tutorial with one that suits your needs.
Related
I'm using meteor-accounts and accounts-password in an application and would like users to be able to reset their passwords. At present there's no need for any customisation of any of the forms and so I've used a common layout with {{> atForm }} and a configuration file of /lib/config.js containing the following:
AccountsTemplates.configure({
showForgotPasswordLink: true,
enablePasswordChange: true,
sendVerificationEmail: true,
enforceEmailVerification: true,
confirmPassword: true,
showResendVerificationEmailLink: true,
continuousValidation: true,
privacyUrl: 'privacy',
});
Clicking on a 'reset password' link produces URLs like the following:
http://localhost:3000/#/reset-password/hMny_A8tdOpNubxtk8mC3BE0vYSJm35K80B2hwwV1CR
However, these are completely useless in that they redirect to the root URL for the application whilst apparently changing the password; users therefore can't log in after clicking on one of these links. A user account looks like this after clicking one:
{ "_id" : "LcQSCiG7ib5F49tPN", "createdAt" : ISODate("2017-03-04T21:33:57.050Z"), "services" : { "password" : { "bcrypt" : "<redacted>", "reset" : { "token" : "l4HdPzoKkeIUdUeUC5x9NmUiQMnRsY1MRLvYk6Wvqw1", "email" : "<redacted>", "when" : ISODate("2017-03-04T21:51:32.171Z"), "reason" : "reset" } }, "email" : { "verificationTokens" : [ { "token" : "K88HXjzI2UO8vARZv6l6Qf0mUJ1hstInnrJK-8hayzk", "address" : "<redacted>", "when" : ISODate("2017-03-04T21:33:57.072Z") }, { "token" : "NMGLelAWKcCFglRj7aQvZoP85N-_YdWJZ2FcPWu5U8D", "address" : "<redacted>", "when" : ISODate("2017-03-04T21:52:55.930Z") } ] }, "resume" : { "loginTokens" : [ ] } }, "emails" : [ { "address" : "<redacted>", "verified" : false } ] }
Everything else works (e.g. signing up with confirmation emails). I'm using Blaze templates and Flow Router including useraccounts:flow-routing.
I seem to be missing something here and would appreciate it if someone would be able to point me in the correct direction to get this working.
Based on your explanation, I think you are missing some keys things to get this working.
First, remember that useraccounts:flow-routing does not provide routes out of the box.
There are no routes provided by default, but you can easily configure routes for sign in, sign up, forgot password, reset password, change password, enroll account using AccountsTemplates.configureRoute
Given that info, you need to at least configure the default route for reset password.
The simplest way is to make the call passing in only a route code (available route codes are: signIn, signUp, changePwd, forgotPwd, resetPwd, enrollAccount).
Here is an example.
AccountsTemplates.configureRoute('resetPwd');
The default will route the user to the fullPageAtForm so they can re-enter a new password.
Take a look at the useraccounts:flow-routing readme for more details.
This question already has an answer here:
Firebase -- Bulk delete child nodes
(1 answer)
Closed 6 years ago.
With Firebase fan out data to different nodes and paths is recommended by Firebase like below example from Firebase sample:
{
"post-comments" : {
"PostId1" : {
"CommentID1" : {
"author" : "User1",
"text" : "Comment1!",
"uid" : "UserId1"
}
}
},
"posts" : {
"PostId1" : {
"author" : "user1",
"body" : "Firebase Mobile platform",
"starCount" : 1,
"stars" : {
"UserId1" : true
},
"title" : "About firebase",
"uid" : "UserId1"
}
},
"user-posts" : {
"UserId1" : {
"PostId1" : {
"author" : "user1",
"body" : "Firebase Mobile platform",
"starCount" : 1,
"stars" : {
"UserId1" : true
},
"title" : "About firebase",
"uid" : "UserId1"
}
}
},
"users" : {
"UserId1" : {
"email" : "user1#gmail.com",
"username" : "user1"
}
}
}
With multipath updates we can atomically update all the paths for a post, however if we want to delete a blog post in above kind of schema then how can we do it atomically? There is no multi path delete, I guess. If client losses network connection while deleting then only few paths would be deleted!
Also in case there is a requirement like when a user is deleted for all the post he has starred, we should remove the stars and unstar the post for that user. This becomes difficult as there is no direct tracking of what posts user has starred. For this do we need to fan out the starring of posts as well like have a node user-stars. Then while deleting we know what all activity the user has done and act on it while deleting user. Is there a better way of handling this?
"user-stars":{
"UserId1":{
"PostID1":true
}
}
In both cases the question on atomically or consistently deleting the data from multipaths (either all or nothing) is seemingly not available.
In that case the only option available looks to be putting the delete command in Firebase queue which will resolve the task in queue only if everything is deleted. That will be eventually consistent option but should be fine. But that is expensive option requiring server. Is there a better way?
You can implement a multi-path delete, by writing a value of null to the paths.
So:
var updates = {
"user-posts/UserId1/PostId1": null,
"post-comments/PostId1": null,
"posts/PostId1": null
}
ref.update(updates);
I had already answered this before: Firebase -- Bulk delete child nodes
It's also quite explicitly mentioned in the documentation on deleting data:
You can also delete by specifying null as the value for another write operation such as set() or update(). You can use this technique with update() to delete multiple children in a single API call.
Implementing an Android+Web(Angular)+Firebase app, which has a many-to-many relationship: User <-> Widget (Widgets can be shared to multiple users).
Considerations:
List all the Widgets that a User has.
A User can only see the Widgets which are shared to him/her.
Be able to see all Users to whom a given Widget is shared.
A single Widget can be owned/administered by multiple Users with equal rights (modify Widget and change to whom it is shared). Similar to how Google Drive does sharing to specific users.
One of the approaches to implement fetching (join-style), would be to go with this advice: https://www.firebase.com/docs/android/guide/structuring-data.html ("Joining Flattened Data") via multiple listeners.
However I have doubts about this approach, because I have discovered that data loading would be worryingly slow (at least on Android) - I asked about it in another question - Firebase Android: slow "join" using many listeners, seems to contradict documentation .
So, this question is about another approach: per-user copies of all Widgets that a user has. As used in the Firebase+Udacity tutorial "ShoppingList++" ( https://www.firebase.com/blog/2015-12-07-udacity-course-firebase-essentials.html ).
Their structure looks like this:
In particular this part - userLists:
"userLists" : {
"abc#gmail,com" : {
"-KBt0MDWbvXFwNvZJXTj" : {
"listName" : "Test List 1 Rename 2",
"owner" : "xyz#gmail,com",
"timestampCreated" : {
"timestamp" : 1456950573084
},
"timestampLastChanged" : {
"timestamp" : 1457044229747
},
"timestampLastChangedReverse" : {
"timestamp" : -1457044229747
}
}
},
"xyz#gmail,com" : {
"-KBt0MDWbvXFwNvZJXTj" : {
"listName" : "Test List 1 Rename 2",
"owner" : "xyz#gmail,com",
"timestampCreated" : {
"timestamp" : 1456950573084
},
"timestampLastChanged" : {
"timestamp" : 1457044229747
},
"timestampLastChangedReverse" : {
"timestamp" : -1457044229747
}
},
"-KByb0imU7hFzWTK4eoM" : {
"listName" : "List2",
"owner" : "xyz#gmail,com",
"timestampCreated" : {
"timestamp" : 1457044332539
},
"timestampLastChanged" : {
"timestamp" : 1457044332539
},
"timestampLastChangedReverse" : {
"timestamp" : -1457044332539
}
}
}
},
As you can see, the copies of shopping list "Test List 1 Rename 2" info appears in two places (for 2 users).
And here is the rest for completeness:
{
"ownerMappings" : {
"-KBt0MDWbvXFwNvZJXTj" : "xyz#gmail,com",
"-KByb0imU7hFzWTK4eoM" : "xyz#gmail,com"
},
"sharedWith" : {
"-KBt0MDWbvXFwNvZJXTj" : {
"abc#gmail,com" : {
"email" : "abc#gmail,com",
"hasLoggedInWithPassword" : false,
"name" : "Agenda TEST",
"timestampJoined" : {
"timestamp" : 1456950523145
}
}
}
},
"shoppingListItems" : {
"-KBt0MDWbvXFwNvZJXTj" : {
"-KBt0heZh-YDWIZNV7xs" : {
"bought" : false,
"itemName" : "item",
"owner" : "xyz#gmail,com"
}
}
},
"uidMappings" : {
"google:112894577549422030859" : "abc#gmail,com",
"google:117151367009479509658" : "xyz#gmail,com"
},
"userFriends" : {
"xyz#gmail,com" : {
"abc#gmail,com" : {
"email" : "abc#gmail,com",
"hasLoggedInWithPassword" : false,
"name" : "Agenda TEST",
"timestampJoined" : {
"timestamp" : 1456950523145
}
}
}
},
"users" : {
"abc#gmail,com" : {
"email" : "abc#gmail,com",
"hasLoggedInWithPassword" : false,
"name" : "Agenda TEST",
"timestampJoined" : {
"timestamp" : 1456950523145
}
},
"xyz#gmail,com" : {
"email" : "xyz#gmail,com",
"hasLoggedInWithPassword" : false,
"name" : "Karol Depka",
"timestampJoined" : {
"timestamp" : 1456952940258
}
}
}
}
However, before I jump into implementing a similar structure in my app, I would like to clarify a few doubts.
Here are my interrelated questions:
In their ShoppingList++ app, they only permit a single "owner" - assigned in the ownerMappings node. Thus no-one else can rename the shopping list. I would like to have multiple "owners"/admins, with equal rights. Would such a keep-copies-per-user structure still work for multiple owner/admin users, without risking data corruption/"desynchronization" or "pranks"?
Could data corruption arise in scenarios like this: User1 goes offline, renames Widget1 to Widget1Prim. While User1 is offline, User2 shares Widget1 to User3 (User3's copy would not yet be aware of the rename). User1 goes online and sends the info about the rename of Widget1 (only to his own and User2's copies, of which the client code was aware at the time of the rename - not updating User3's copy). Now, in a naive implementation, User3 would have the old name, while the others would have the new name. This would probably be rare, but still worrying a bit.
Could/should the data corruption scenario in point "2." be resolved via having some process (e.g. on AppEngine) listening to changes and ensuring proper propagation to all user copies?
And/or could/should the data corruption scenario in point "2." be resolved via implementing a redundant listening to both changes of sharing and renaming, and propagating the changes to per-user copies, to handle the special case? Most of the time this would not be necessary, so it could result in performance/bandwidth penalty and complicated code. Is it worth it?
Going forward, once we have multiple versions deployed "in the wild", wouldn't it become unwieldy to evolve the schema, given how much of the data-handling responsibility lies with the code in the clients? For example if we add a new relationship, that the older client versions don't yet know about, doesn't it seem fragile? Then, back to the server-side syncer-ensurerer process on e.g. AppEngine (described in question "3.") ?
Would it seem like a good idea, to also have a "master reference copy" of every Widget / shopping-list, so as to give good "source of truth" for any syncer-ensurerer type of operations that would update per-user copies?
Any special considerations/traps/blockers regarding rules.json / rules.bolt permissions for data structured in such a (redundant) way ?
PS: I know about atomic multi-path updates via updateChildren() - would definitely use them.
Any other hints/observations welcome. TIA.
I suggest having only one copy of a widget for the entire system. It would have an origin user ID, and a set of users that have access to it. The widget tree can hold user permissions and change history. Any time a change is made, a branch is added to the tree. Branches can then be "promoted" to the "master" kind of like GIT. This would guarantee data integrity because past versions are never changed or deleted. It would also simplify your fetches... I think :)
{
users:[
bob:{
widgets:[
xxx:{
widgetKey: xyz,
permissions: *,
lastEdit...
}
]
}
...
]
widgets:[
xyz:{
masterKey:abc,
data: {...},
owner: bob,
},
...
]
widgetHistory:[
xyz:[
v1:{
data:{...},
},
v2,
v3
]
123:[
...
],
...
]
}
OK... let me start by saying I know there is a similar post here (How to create a Drupal rule to check (on cron) a date field and if passed set field "status" to "ended"?) but the answer on that post does not work. Step 4 (In the component add the condition 'Data comparison' and select node:type) does not work or even exists as an option.
What I need to do is this:
On Cron > If content type is event and the end date has passed the current date then change the status field from Active to Ended. (select list)
I was able to do this by using the Event: Content is viewed but I really need to to work when cron is ran.
Side note: with the current version I have (Content is viewed) it does change Active to Ended but it also for some reason deletes the title of the node which is strange becuase the title filed is required by Drupal... any idea wht that is happening?
Not sure if it helps but here is an export of what I have done myself:
{ "rules_event_status" : {
"LABEL" : "Event Status",
"PLUGIN" : "reaction rule",
"ACTIVE" : false,
"REQUIRES" : [ "rules", "php" ],
"ON" : [ "node_view" ],
"IF" : [
{ "node_is_of_type" : { "node" : [ "node" ], "type" : { "value" : { "event" : "event" } } } },
{ "AND" : [] },
{ "php_eval" : { "code" : "\/\/dpm(strtotime($node-\u003Efield_event_date_time[LANGUAGE_NONE][0][\u0027value2\u0027]));\r\nif (time() \u003E strtotime($node-\u003Efield_event_date_time[LANGUAGE_NONE][0][\u0027value2\u0027]))\r\n{\r\n return true;\r\n}" } }
],
"DO" : [
{ "data_set" : { "data" : [ "node:field-event-status" ], "value" : "Ended" } }
]
}
}
Any help is very much appreciated.
Thanks
C
to use any custom fields or fields created by other modules than node, you have to add condition "entity has field" to your rules which will make that field "visible" and accesible for later work
side note: I think you can do the date comparison without php_eval, just add another entity has field condition and create "data comparison" condition. There should be tokens available to your needs
Not sure I fully understand the question: rules can be triggered by cron.
You should be able to get it to run when cron executes by picking the "React on event" attribute of the rule to "System > Cron maintenance tasks are executed".
Am I missing something?
I have just glanced over the MongoDB collection for users and it seems to allow multiple login providers for a single user. From what I see, everything seems to be "there": Multiple services, different resume tokens ...
But is there currently a documented way to "associate" a new login provider with an existing user? I couldn't find anything in the official Docs :(
Or is there anything preventing this in the collection "schema"? Just in case, here is how it looks for a single user using the "password" login service.
{
"createdAt" : 123456,
"services" : {
"password" : {
"srp" : {
"identity" : "XXX",
"salt" : "XXX",
"verifier" : "XXX"
}
},
"resume" : {
"loginTokens" : [
{
"token" : "XXX",
"when" : 123456
}
]
}
},
"emails" : [
{
"address" : "foo#example.org",
"verified" : false
}
],
"_id" : "7f98645e-df24-4015-8075-2463c6c8cfc5"
}
With the current version of meteor (0.8.0.3) it is not possible to make use of multiple login providers out of the box. But there is package on athmosphere which allows this.
I haven't tested this, but from what I know you can login the user with password, and then call Meteor.loginWithFacebook, for example, while the user is logged in. This should add the Facebook information to the current user's data.