5

I realize this is quite a specific Drupal commerce question, but perhaps I can get some insights as I am stuck here.

I need to control access to commerce orders on a per-order basis based on an entity reference field. So users will not have access to view commerce orders, except if they are related to them.

My custom access check requires an operation, and entity and a user.

Looking at all the hooks Drupal commerce executes in order to determine access:

  • commerce_order_access() calls the generic access helper, commerce_entity_access() for view operations, this constructs an access query based on the entity, which relies on Database API invoking hook_query_TAG_alter(). For orders, this is hook_query_commerce_order_access_alter().

  • commerce order's implementation of this hook, commerce_order_query_commerce_order_access_alter(), calls a generic implementation back in commerce module, commerce_entity_access_query_alter().

  • commerce_entity_access_query_alter() checks entity type and user access permissions for the entity and builds an OR conditions array that you can alter for Orders using hook_commerce_entity_access_condition_commerce_order_alter().

All these functions however never get a specific entity to operate on and always check 'global' permissions with user_access(). The committed patch Pass entity to Commerce entity access check contexts changes this so the entity is added to the query metadata in commerce_entity_access() and as such can also be used in hook_commerce_entity_access_condition_commerce_order_alter().

So far so good, this works and I can check access to my order in hook_commerce_entity_access_condition_commerce_order_alter().

The real problem

The problem I run into occurs when loading the orders through Views;

Views does not load the entity but just adds it to the select query and then performs the above access checks, starting with commerce_order_query_commerce_order_access_alter() (hook_query_TAG_alter()). This way the entity is not set (commerce_entity_access() is never called) and thus not available for access checking and all there is for me to alter is the query.

The thing I came up with

Actually the only thing I could think of was to load all commerce order entities, run them all through my access check and save the id's of the allowed orders. Then in hook_commerce_entity_access_condition_commerce_order_alter() add all these id's to the query in a condition. I am however afraid this will be a huge performance drop once there have been made some orders

So is there a way I can fetch entities from a Views select query? Or id's? Or do I need to implement my check elsewhere?

  • Can you be a bit more specific about that access check? Ie: that "control access to commerce orders" part of your question, is that something like some link (URL) for each specific order? So that if one clicks such link (to "access" the order), the actual browsing (or editing, etc) of such order is only allowed if "something is true". Such as the user needs to have access to a role like ABC (or whatever)? – Pierre.Vriens Dec 9 '15 at 19:22
  • I have the Crud operations for the entities completely working, so creating and editing via the pages defined in hook menu works flawless. The issue is when the commerce_order is referenced in views. In such case the entity is not loaded (but joined in the query), thus my access callback is not fired. All there is for me to edit is the views select query in which I was hoping to be able to select the id's from the table, load the entities and feed them to my Access callback. Then finish the query. – Neograph734 Dec 9 '15 at 20:16
2
+50

Your thing you came up with is certainly one way of doing it, though you're adding a step. If you can guarantee that your entity reference field will always be on the order, then you could just join your query to the entity reference table and check it against the current user's UID.

Something to the effect of:

INNER JOIN `field_order_user_reference` ON `commerce_order`.order_id = `field_order_user_reference`.entity_id 
...
WHERE
`field_order_user_reference`.user_id = ***CURRENT_USER***

Or something to that effect.

The way you have it implemented now, you will start to see performance degradation since you're adding a lot of overhead. Using pure SQL will help tremendously and you shouldn't see any slowdown.

Alternatively, since your order has an entity/user reference field attached to it, you should be able to just add it as a filter and set the filter user as the current one. Maybe not, but if you haven't tried it, it'd be worth a shot.

  • This could actually work. I didn't yet implement it as described as it the most inefficient way. Thanks for taking the time to respond, I'll try if I can get that to work. – Neograph734 Dec 8 '15 at 22:35
  • If you look at a module like Content access this is effectively what it does, it just didn't use entity reference, it kept it's own database and added it to the query. It's really the most effective way to do it. – nvahalik Dec 8 '15 at 22:37
  • vote up,I think it works – Yusef Dec 12 '15 at 12:41
  • I had some doubts as I also wanted my access function to be hookable. But after some fiddling around I realized I could also pass the query to the implementing module (rather then the operation and the entity). So I will build it like that. Thanks, I would not have tried that without your suggestion! – Neograph734 Dec 12 '15 at 21:02

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.