Problem

Field_collection_item_access() has some weirdness in it right now. $Op is defined and re-defined inside of it (removing all but view and update values for entity access), the code doesn't follow a consistent standard in setting variables, and calls to it usually set the $op 'edit' value to 'update' in a ternary, just to re-set, and re-re-set it inside the function.

Solution

Field_collection_item_access() should take the $op value as is, and do all needed manipulation inside the function itself. That way no special knowledge is needed to call the function, and if we decide to change the default 'edit' operation value to 'update', we can change the manipulation code in one place -- the function -- instead of having to touch all of the function calls themselves.

Updated code. This should be backwards compatible with all current function calls, in case a third party is still calling it with the ternary setting 'edit' to 'update':

function field_collection_item_access($op, FieldCollectionItemEntity $item = NULL, $account = NULL) {
  if (user_access('administer field collections', $account)) {
    return TRUE;
  }
  if (!isset($item)) {
    return FALSE;
  }
  
  $field = field_info_field($item->field_name);
  
  // Entity access takes  'view', 'update', 'create' or 'delete' operations.
  if ($op == 'edit') {
    $op = 'update';
  }
  // Field access takes only 'view' or 'edit' operations.
  $field_op = $op == 'view' ? 'view' : 'edit';
  
  // Access is determined by the entity and field containing the reference.
  return entity_access($op, $item->hostEntityType(), $item->hostEntity(), $account) && field_access($field_op, $field, $item->hostEntityType(), $item->hostEntity(), $account);
}

Remaining Tasks

I'll write a patch that adds the above code and revises all of the function calls.
If you see a horrible flaw in my suggestion, please let me know.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

RobW’s picture

Status: Active » Needs review
FileSize
2.59 KB

Patch. I've only casually tested, so needs a closer look.

Status: Needs review » Needs work

The last submitted patch, field_collection-item_access-1688114-1.patch, failed testing.

RobW’s picture

Status: Needs work » Needs review

Also, if anyone can explain what FieldCollectionItemEntity with no comma after it is doing in the args... looks almost like an accidental paste, but has been in dev releases for a little while.

RobW’s picture

Status: Needs review » Needs work

The last submitted patch, field_collection-item_access-1688114-1.patch, failed testing.

RobW’s picture

Don't know why this is failing, applies fine and seems to work for me.

RobW’s picture

Status: Needs work » Needs review
RobW’s picture

Re-rolled for the latest dev.

Status: Needs review » Needs work

The last submitted patch, field_collection-item_access-1688114-8.patch, failed testing.

Anonymous’s picture

Issue summary: View changes

added formatting, made language more specific

RobW’s picture

Issue summary: View changes

You know, just editing.

RobW’s picture

Status: Needs work » Needs review
FileSize
3.21 KB

Ah, ok, I learned how to use simpletest. Behold the green!

fago’s picture

Status: Needs review » Needs work

hm, I don't think that adding even more possible $op makes it any better. At least the function signature of field-collection is now sane, so let's better keep it that way.

However, what we could do is introducing a field-access wrapper function that does that edit -> update conversion for checking access on field collection *fields*.

RobW’s picture

The main problem I see with the access check as it stands is it's being called with a ternary. You should be able to check field access without doing any logic in the function call.

The way I've set it up allows all possible ops for field_access and entity_access, since we're checking both in field_collection_item_access. If you'd prefer to restrict the values to just add, edit, and delete, I'm into it; I'd just like to see the logic being used in the ternary pulled inside of the function.

Can you explain the idea behind a field_access wrapper function?

RobW’s picture

Actually, in this patch there's no code specifically targeting anything besides add, edit, and delete. If you want to accept only those, we can just edit the comments.

/**
 * Determines whether the given user has access to a field collection.
 *
 * @param $op
 *   The operation being performed. One of 'view', 'edit', 
 *   'add', or 'delete'.
 * @param $item
 *   Optionally a field collection item. If nothing is given, access for all
 *   items is determined.
 * @param $account
 *   The user to check for. Leave it to NULL to check for the global user.
 * @return boolean
 *   Whether access is allowed or not.
 */
function field_collection_item_access($op, FieldCollectionItemEntity $item = NULL, $account = NULL) {
  if (user_access('administer field collections', $account)) {
    return TRUE;
  }
  if (!isset($item)) {
    return FALSE;
  }
  
  // Op coming from $settings is 'add', 'edit', or 'delete'.
  // Field access only takes 'view' or 'edit' operations.
  $field_op = $op == 'view' ? 'view' : 'edit';

  // Entity access takes 'view', 'update', 'create', or 'delete' operations.
  if ($op = 'edit') {
    $entity_op = 'update';
  }
  elseif ($op = 'add') {
    $entity_op = 'create';
  }
  
  // Access is determined by the entity and field containing the reference.
  $field = field_info_field($item->field_name);

  $entity_access = entity_access($entity_op, $item->hostEntityType(), $item->hostEntity(), $account);
  $field_access = field_access($field_op, $field, $item->hostEntityType(), $item->hostEntity(), $account);

  return $entity_access && $field_access;
}

Thinking about targeting ops a little more, using the ternary turns any op argument besides "view" into "edit". It might be better to change this to:

  if ($op = 'add' || 'edit' || 'delete') {
    $field_op = 'edit';
  }

I don't think it really matters; if you use a random string for the op link, entity access will return false (since it transforms the ops specifically, any non "view-add-edit-delete" string gets through). Just bringing it up for the hell of it.

fago’s picture

We cannot only accept "add, edit, and delete", because the other operations are pre-defined for entities by the entity module (and the common $ops for entities) anyway. The mapping that has to happen for field collection exists because field-access and entity access use different $ops.

>Can you explain the idea behind a field_access wrapper function?

Sure. We could stay with the current entity-$ops for field_collection_item_access(), but add field_collection_field_access() which accepts the field $ops, so the ugly ternary check can live in there and is not necessary in the calling code anymore.

RobW’s picture

The patch rewrites field_collection_item_access as a big wrapper function for both entity_access and field_access. I'm not sure when there would be a need to check the field access for a field collection field without also checking the entity access for the field collection item, so I don't know if it's a good idea to add a separate function wrap that by itself.

I think we might be trying to say the same things but with different words. If you can tell me what operations field collection_item_access should take as arguments, I'll code it up.

RobW’s picture

Issue summary: View changes

I should read twice and edit once...