Commit 135809e1 authored by Jeroen Bobbeldijk's avatar Jeroen Bobbeldijk Committed by Jeroen Bobbeldijk
Browse files

Issue #2564327 by jeroen.b, lucastockmann, manu manu, jstoller: Host entity...

Issue #2564327 by jeroen.b, lucastockmann, manu manu, jstoller: Host entity problems (ajax errors / nested paragraphs / UI speed)
parent cf46e40a
Loading
Loading
Loading
Loading
+45 −35
Changes for ParagraphsItemEntity.inc: 45 added lines, 35 removed lines.
Original line number Diff line number Diff line
@@ -89,11 +89,18 @@ class ParagraphsItemEntity extends Entity {
  /**
   * Whether the paragraphs item is archived, i.e. not in use.
   *
   * @see FieldCollectionItemEntity::isInUse()
   * @see ParagraphsItemEntity::isInUse()
   * @var bool
   */
  public $archived = FALSE;

  /**
   * The name of the paragraphs field this item is associated with.
   *
   * @var string
   */
  private $fetchedHostEntityDetails = NULL;

  /**
   * Constructs the entity object.
   */
@@ -129,10 +136,8 @@ class ParagraphsItemEntity extends Entity {
   * paragraphs item.
   */
  public function instanceInfo() {
    if ($this->fetchHostDetails()) {
    return field_info_instance($this->hostEntityType(), $this->field_name, $this->hostEntityBundle());
  }
  }

  /**
   * Returns the field instance label translated to interface language.
@@ -193,12 +198,11 @@ class ParagraphsItemEntity extends Entity {
   *   When you try to set the host when the item has already been created.
   */
  public function setHostEntity($entity_type, $entity, $langcode = LANGUAGE_NONE, $create_link = TRUE) {
    if (!empty($this->is_new)) {
    $this->hostEntityType = $entity_type;
    $this->hostEntity = $entity;
    $this->langcode = $langcode;

      list($this->hostEntityId, $this->hostEntityRevisionId) = entity_extract_ids($this->hostEntityType, $this->hostEntity);
    list($this->hostEntityId, $this->hostEntityRevisionId, $this->hostEntityBundle) = entity_extract_ids($this->hostEntityType, $this->hostEntity);
    // If the host entity is not saved yet, set the id to FALSE. So
    // fetchHostDetails() does not try to load the host entity details.
    if (!isset($this->hostEntityId)) {
@@ -213,10 +217,8 @@ class ParagraphsItemEntity extends Entity {
    if ($create_link) {
      $entity->{$this->field_name}[$this->langcode][] = array('entity' => $this);
    }
    }
    else {
      throw new Exception('The host entity may be set only during creation of a paragraphs item.');
    }

    $this->fetchedHostEntityDetails = TRUE;
  }

  /**
@@ -232,8 +234,8 @@ class ParagraphsItemEntity extends Entity {
  /**
   * Function to force change the host entity type of this paragraph item.
   *
   * @param $entity
   *   The entity to force the host to.
   * @param $entity_type
   *   The entity type to force the host to.
   */
  public function forceHostEntityType($entity_type) {
    $this->hostEntityType = $entity_type;
@@ -243,29 +245,30 @@ class ParagraphsItemEntity extends Entity {
   * Returns the host entity, which embeds this paragraph item.
   */
  public function hostEntity() {
    if ($this->fetchHostDetails()) {
      $this->hostEntity = entity_revision_load($this->hostEntityType, $this->hostEntityRevisionId);
    $this->fetchHostDetails();
    return $this->hostEntity;
  }
  }

  /**
   * Returns the entity type of the host entity, which embeds this
   * paragraph item.
   */
  public function hostEntityType() {
    if ($this->fetchHostDetails()) {
    return $this->hostEntityType;
  }
  }

  /**
   * Returns the id of the host entity, which embeds this paragraph item.
   */
  public function hostEntityId() {
    if ($this->fetchHostDetails()) {
    return $this->hostEntityId;
  }

  /**
   * Returns the revisions id of the host entity, which embeds this paragraph item.
   */
  public function hostEntityRevisionId() {
    return $this->hostEntityRevisionId;
  }

  /**
@@ -273,10 +276,8 @@ class ParagraphsItemEntity extends Entity {
   * item.
   */
  public function hostEntityBundle() {
    if ($this->fetchHostDetails()) {
    return $this->hostEntityBundle;
  }
  }

  /**
   * Fetches details of the host and saves it in the entity.
@@ -285,13 +286,15 @@ class ParagraphsItemEntity extends Entity {
   *   Whether the fetching was successful.
   */
  protected function fetchHostDetails() {
    if (!isset($this->hostEntityId)) {
    if ($this->fetchedHostEntityDetails === NULL) {
      if ($this->item_id) {
        // For saved paragraphs, query the field data to determine the
        // right host entity.
        $query = new EntityFieldQuery();
        $query->fieldCondition($this->fieldInfo(), 'revision_id', $this->revision_id);
        if (!$this->isInUse()) {
          $query->age(FIELD_LOAD_REVISION);
        }
        $result = $query->execute();
        list($this->hostEntityType, $data) = each($result);

@@ -300,14 +303,24 @@ class ParagraphsItemEntity extends Entity {
          $host_entity_info = entity_get_info($this->hostEntityType);
          $host_entity_keys = $host_entity_info['entity keys'];

          $this->hostEntityId = $data_values->{$host_entity_keys['id']};
          $this->hostEntityRevisionId = $data_values->{$host_entity_keys['revision']};
          $this->hostEntityBundle = $data_values->{$host_entity_keys['bundle']};
          $this->hostEntityId = (isset($data_values->{$host_entity_keys['id']}) ? $data_values->{$host_entity_keys['id']} : NULL);
          $this->hostEntityRevisionId = (isset($data_values->{$host_entity_keys['revision']}) ? $data_values->{$host_entity_keys['revision']} : NULL);
          $this->hostEntityBundle = (isset($data_values->{$host_entity_keys['bundle']}) ? $data_values->{$host_entity_keys['bundle']} : NULL);

          if ($this->isInUse()) {
            $this->hostEntity = entity_load_single($this->hostEntityType, $this->hostEntityId);
          }
          elseif ($this->hostEntityRevisionId) {
            $this->hostEntity = entity_revision_load($this->hostEntityType, $this->hostEntityRevisionId);
          }

          $this->fetchedHostEntityDetails = TRUE;
        }
        else {
          $this->hostEntityId = FALSE;
          $this->hostEntityRevisionId = FALSE;
          $this->hostEntityBundle = FALSE;
          $this->fetchedHostEntityDetails = FALSE;
        }
      }
      else {
@@ -315,9 +328,10 @@ class ParagraphsItemEntity extends Entity {
        $this->hostEntityId = FALSE;
        $this->hostEntityRevisionId = FALSE;
        $this->hostEntityBundle = FALSE;
        $this->fetchedHostEntityDetails = FALSE;
      }
    }
    return !empty($this->hostEntityId) || !empty($this->hostEntity) || !empty($this->hostEntityRevisionId);
    return $this->fetchedHostEntityDetails;
  }

  /**
@@ -416,14 +430,6 @@ class ParagraphsItemEntity extends Entity {
    }
  }

  /**
   * Deletes the paragraphs item and the reference in the host entity.
   */
  public function delete() {
    parent::delete();
    $this->deleteHostEntityReference();
  }

  /**
   * Deletes the host entity's reference of the paragraphs item.
   */
@@ -534,12 +540,16 @@ class ParagraphsItemEntity extends Entity {
   */
  public function __sleep() {
    $vars = get_object_vars($this);

    unset($vars['entityInfo'], $vars['idKey'], $vars['nameKey'], $vars['statusKey']);
    unset($vars['fieldInfo']);
    // Also do not serialize the host entity, but only if it has already an id.
    if ($this->hostEntity && ($this->hostEntityId || $this->hostEntityRevisionId)) {

    // Also do not serialize the host entity.
    // We add our hostEntity in code.
    unset($vars['hostEntity']);
    }

    // We unset our host entity, we have to let our object know.
    unset($vars['fetchedHostEntityDetails']);

    // Also key the returned array with the variable names so the method may
    // be easily overridden and customized.
+1 −0
Changes for paragraphs.field_formatter.inc: 1 added line, 0 removed lines.
Original line number Diff line number Diff line
@@ -96,6 +96,7 @@ function paragraphs_field_formatter_view($entity_type, $entity, $field, $instanc

      foreach ($items as $delta => $item) {
        if ($paragraph = paragraphs_field_get_entity($item)) {
          $paragraph->setHostEntity($entity_type, $entity, $langcode);
          if (entity_access('view', 'paragraphs_item', $paragraph)) {
            $element[$delta]['entity'] = $paragraph->view($view_mode);
          }
+22 −14
Changes for paragraphs.field_widget.inc: 22 added lines, 14 removed lines.
Original line number Diff line number Diff line
@@ -39,8 +39,7 @@ function paragraphs_field_widget_form(&$form, &$form_state, $field, $instance, $

      break;
    case 'paragraphs_embed':
      $form['#entity'] = $element['#entity'];
      return paragraphs_field_multiple_value_form($field, $instance, $langcode, $items, $form, $form_state);
      return paragraphs_field_multiple_value_form($field, $instance, $langcode, $items, $form, $form_state, $delta, $element);
      break;
  }
}
@@ -53,7 +52,7 @@ function paragraphs_field_widget_form(&$form, &$form_state, $field, $instance, $
 * - AHAH-'add more' button
 * - drag-n-drop value reordering
 */
function paragraphs_field_multiple_value_form($field, $instance, $langcode, $items, &$form, &$form_state) {
function paragraphs_field_multiple_value_form($field, $instance, $langcode, $items, &$form, &$form_state, $delta, $original_element) {
  $field_name = $field['field_name'];
  $parents = $form['#parents'];

@@ -76,9 +75,9 @@ function paragraphs_field_multiple_value_form($field, $instance, $langcode, $ite
    for ($delta = 0; $delta <= $max; $delta++) {
      $multiple = TRUE;
      $element = array(
        '#entity_type' => $instance['entity_type'],
        '#entity' => $form['#entity'],
        '#bundle' => $instance['bundle'],
        '#entity_type' => $original_element['#entity_type'],
        '#entity' => $original_element['#entity'],
        '#bundle' => $original_element['#bundle'],
        '#field_name' => $field_name,
        '#language' => $langcode,
        '#field_parents' => $parents,
@@ -180,17 +179,18 @@ function paragraphs_field_multiple_value_form($field, $instance, $langcode, $ite
      $removed_a_bundle = FALSE;

      $weight = 0;
      $entity_shell = new stdClass();
      $entity_shell->is_new = TRUE;
      $entity_shell->hostEntity = $form['#entity'];
      $entity_shell->hostEntityType = $field_state['instance']['entity_type'];

      foreach ($select_bundles as $machine_name => $bundle) {
        $select_bundles[$machine_name] = array(
          'name' => $bundle,
          'weight' => $weight,
        );

        $entity_shell->bundle = $machine_name;

        /* @var $entity_shell ParagraphsItemEntity */
        $entity_shell = entity_create('paragraphs_item', array('bundle' => $machine_name, 'field_name' => $field_name));
        $entity_shell->setHostEntity($original_element['#entity_type'], $original_element['#entity'], $langcode, FALSE);

        if (!entity_access('create', 'paragraphs_item', $entity_shell)) {
          unset($select_bundles[$machine_name]);
          $removed_a_bundle = TRUE;
@@ -229,7 +229,11 @@ function paragraphs_field_multiple_value_form($field, $instance, $langcode, $ite

          if ($add_mode == 'button') {
            foreach ($select_bundles as $machine_name => $bundle) {
              $entity_shell->bundle = $machine_name;

              /* @var $entity_shell ParagraphsItemEntity */
              $entity_shell = entity_create('paragraphs_item', array('bundle' => $machine_name, 'field_name' => $field_name));
              $entity_shell->setHostEntity($original_element['#entity_type'], $original_element['#entity'], $langcode, FALSE);

              $field_elements['add_more']['add_more_bundle_' . $machine_name] = array(
                '#type' => 'submit',
                '#name' => strtr($id_prefix, '-', '_') . '_add_more_add_more_bundle_' . $machine_name,
@@ -400,7 +404,10 @@ function paragraphs_field_widget_form_build(&$form, &$form_state, $field, $insta
    else {
      $being_edited_paragraph = FALSE;
    }

    /* @var $paragraph_item ParagraphsItemEntity */
    $paragraph_item = $field_state['entity'][$delta];
    $paragraph_item->setHostEntity($field_state['instance']['entity_type'], $element['#entity'], $langcode, FALSE);
  }
  else {
    if (isset($items[$delta])) {
@@ -410,6 +417,7 @@ function paragraphs_field_widget_form_build(&$form, &$form_state, $field, $insta
    // Show an empty collection if we have no existing one or it does not
    // load.
    if (empty($paragraph_item) && $bundle) {
      /* @var $paragraph_item ParagraphsItemEntity */
      $paragraph_item = entity_create('paragraphs_item', array('bundle' => $bundle, 'field_name' => $field_name));
      $paragraph_item->being_edited = TRUE;
      $being_edited_paragraph = TRUE;
@@ -417,8 +425,8 @@ function paragraphs_field_widget_form_build(&$form, &$form_state, $field, $insta
    }

    if (!empty($paragraph_item)) {
      $paragraph_item->forceHostEntity($element['#entity']);
      $paragraph_item->forceHostEntityType($field_state['instance']['entity_type']);
      /* @var $paragraph_item ParagraphsItemEntity */
      $paragraph_item->setHostEntity($element['#entity_type'], $element['#entity'], $langcode, FALSE);

      // Put our entity in the form state, so FAPI callbacks can access it.
      $field_state['entity'][$delta] = $paragraph_item;
+71 −57
Changes for paragraphs.module: 71 added lines, 57 removed lines.
Original line number Diff line number Diff line
@@ -171,10 +171,15 @@ function paragraphs_item_access($op, $entity, $account, $entity_type) {
    return FALSE;
  }

  // Cid is the bundle name when op is create.
  if ($op == 'create') {
  // When we have no entity, create a generic cid.
  if (empty($entity)) {
    $cid = 'all_entities:' . $op;
  }
  // When OP is create, or the entity is new, the bundle is the cache key.
  elseif ($op == 'create' || (isset($entity->is_new) && $entity->is_new)) {
    $cid = $entity->bundle;
  }
  // Else our cid is entity specific.
  else {
    $cid = $entity->item_id . '_' . $entity->revision_id;
  }
@@ -182,7 +187,9 @@ function paragraphs_item_access($op, $entity, $account, $entity_type) {
  // If we've already checked access for this bundle, user and op, return from
  // cache. Otherwise, we are optimistic and consider that the user can
  // view / update / delete or create a paragraph.
  $user_access_permission = isset($permissions[$account->uid][$cid][$op]) ? $permissions[$account->uid][$cid][$op] : TRUE;
  if (isset($permissions[$account->uid][$cid][$op])) {
    return $permissions[$account->uid][$cid][$op];
  }

  // We grant access to the paragraph item if both of the following conditions are met:
  // - No modules say to deny access.
@@ -194,6 +201,9 @@ function paragraphs_item_access($op, $entity, $account, $entity_type) {
  }
  elseif (in_array(PARAGRAPHS_ITEM_ACCESS_ALLOW, $access, TRUE)) {
    $user_access_permission = TRUE;
  } else {
    // Deny access by default.
    $user_access_permission = FALSE;
  }

  // Store the result of the permission in our matrix.
@@ -207,12 +217,21 @@ function paragraphs_item_access($op, $entity, $account, $entity_type) {
 *
 * Finds the parent entity and then checks for access on the parent entity.
 *
 * @param ParagraphsItemEntity $entity
 *   The entity to check for.
 *
 * @param string $op
 *   The operation to check for.
 *
 * @param $account
 *   The account to check for.
 *
 * @return bool
 *   Whether the user has access to a paragraphs item.
 */
function paragraphs_paragraphs_item_access($entity, $op, $account) {
  $permissions = &drupal_static(__FUNCTION__, array());
  $real_op = $op;
  $parent_permissions = &drupal_static(__FUNCTION__  . '_parents', array());

  if (!in_array($op, array('view', 'update', 'delete', 'create'), TRUE)) {
    // If there was no bundle to check against, or the $op was not one of the
@@ -220,71 +239,63 @@ function paragraphs_paragraphs_item_access($entity, $op, $account) {
    return PARAGRAPHS_ITEM_ACCESS_IGNORE;
  }

  $check_parent_op = $op;

  // Update/Delete/Create access requires update access on the parent.
  if (in_array($op, array('update', 'delete', 'create'), TRUE)) {
    $op = 'update';
    $check_parent_op = 'update';
  }

  $bundle = $entity->bundle;

  // Cid is the bundle name when op is create.
  if ($real_op == 'create') {
  // When we have no entity, create a generic cid.
  if (empty($entity)) {
    $cid = 'all_entities:' . $op;
  }
  // When OP is create, or the entity is new, the bundle is the cache key.
  elseif ($op == 'create' || (isset($entity->is_new) && $entity->is_new)) {
    $cid = $entity->bundle;
  }
  // Else our cid is entity specific.
  else {
    $cid = $entity->item_id . '_' . $entity->revision_id;
  }

  // If we've already checked access for this cid, user and op, return from
  // cache.
  if (isset($permissions[$account->uid][$cid][$real_op])) {
    return $permissions[$account->uid][$cid][$real_op];
  }

  $permissions[$account->uid][$cid][$real_op] = PARAGRAPHS_ITEM_ACCESS_IGNORE;

  if (isset($entity->hostEntity) && isset($entity->hostEntityType)) {
    $entity_info = entity_get_info($entity->hostEntityType);
    $id_key = $entity_info['entity keys']['id'];

    // Check if we have an ID key set, if not parent entity is new, we check for create access.
    if (!isset($entity->hostEntity->{$id_key}) || empty($entity->hostEntity->{$id_key})) {
      $op = 'create';
    }

    $entity_access = entity_access($op, $entity->hostEntityType, $entity->hostEntity, $account);
    if ($entity_access) {
      $permissions[$account->uid][$cid][$real_op] = PARAGRAPHS_ITEM_ACCESS_ALLOW;
    } else {
      $permissions[$account->uid][$cid][$real_op] = PARAGRAPHS_ITEM_ACCESS_DENY;
  // Check if we cached permission check.
  if (isset($permissions[$account->uid][$cid][$op])) {
    return $permissions[$account->uid][$cid][$op];
  }
  } else {

    if ($entity->hostEntity()) {
      $parent_entity = $entity->hostEntity();
  if (empty($entity)) {
    // Ignore access when we don't have a host entity.
    $permissions[$account->uid][$cid][$op] = PARAGRAPHS_ITEM_ACCESS_IGNORE;
  } elseif ($host_entity = $entity->hostEntity()) {
    $host_entity_info = entity_get_info($entity->hostEntityType());
    $host_id_key = $host_entity_info['entity keys']['id'];

      $entity_info = entity_get_info($entity->hostEntityType());
      $id_key = $entity_info['entity keys']['id'];
    $parent_cid = $entity->hostEntityType() . '_' . implode('_', entity_extract_ids($entity->hostEntityType(), $host_entity));

    // Check if we have an ID key set, if not parent entity is new, we check for create access.
      if (!isset($parent_entity->{$id_key}) || empty($parent_entity->{$id_key})) {
        $op = 'create';
    if (!isset($host_entity->{$host_id_key}) || empty($host_entity->{$host_id_key})) {
      $check_parent_op = 'create';
    }

      if ($parent_entity && $entity_access = entity_access($op, $entity->hostEntityType(), $parent_entity, $account)) {
        if ($entity_access) {
          $permissions[$account->uid][$cid][$real_op] = PARAGRAPHS_ITEM_ACCESS_ALLOW;

          // 1 allowed, we break out.
          return $permissions[$account->uid][$cid][$real_op];
        }
    if (isset($parent_permissions[$account->uid][$parent_cid][$check_parent_op])) {
      return $parent_permissions[$account->uid][$parent_cid][$check_parent_op];
    }

      $permissions[$account->uid][$cid][$real_op] = PARAGRAPHS_ITEM_ACCESS_DENY;
    if (entity_access($check_parent_op, $entity->hostEntityType(), $host_entity)) {
      $permissions[$account->uid][$cid][$op] = PARAGRAPHS_ITEM_ACCESS_ALLOW;
      $parent_permissions[$account->uid][$parent_cid][$check_parent_op] = PARAGRAPHS_ITEM_ACCESS_ALLOW;
    } else {
      // Deny access as parent entity access failed.
      $permissions[$account->uid][$cid][$op] = PARAGRAPHS_ITEM_ACCESS_DENY;
      $parent_permissions[$account->uid][$parent_cid][$check_parent_op] = PARAGRAPHS_ITEM_ACCESS_DENY;
    }
  } else {
    // Ignore access when we don't have a host entity.
    $permissions[$account->uid][$cid][$op] = PARAGRAPHS_ITEM_ACCESS_IGNORE;
  }

  return $permissions[$account->uid][$cid][$real_op];
  return $permissions[$account->uid][$cid][$op];
}

/**
@@ -683,15 +694,17 @@ function paragraphs_field_presave($host_entity_type, $host_entity, $field, $inst
    // In case the entity has been changed / created, save it and set the id.
    // If the host entity creates a new revision, save new item-revisions as
    // well.
    if (isset($item['entity']) || !empty($host_entity->revision)) {

      if ($entity = paragraphs_field_get_entity($item)) {
    $entity = FALSE;
    if (isset($item['entity'])) {
      $entity = paragraphs_field_get_entity($item);
    } elseif (isset($item['revision_id'])) {
      $entity = paragraphs_item_revision_load($item['revision_id']);
    }

        if (!empty($entity->is_new)) {
    if ($entity) {
      $entity->setHostEntity($host_entity_type, $host_entity, $langcode, FALSE);
        }

        // If the host entity is saved as new revision, do the same for the item.
      // If the host entity supports revisions and is saved as new revision, do the same for the item.
      if (!empty($host_entity->revision)) {
        $entity->revision = TRUE;
        $is_default = entity_revision_is_default($host_entity_type, $host_entity);
@@ -702,6 +715,7 @@ function paragraphs_field_presave($host_entity_type, $host_entity, $field, $inst
          $entity->archived = FALSE;
        }
      }

      if (isset($entity->removed) && $entity->removed) {
        unset($items[$key]);
      }
@@ -716,7 +730,6 @@ function paragraphs_field_presave($host_entity_type, $host_entity, $field, $inst
    }
  }
}
}


/**
@@ -753,7 +766,7 @@ function paragraphs_field_update($entity_type, $entity, $field, $instance, $lang
    else {
      // Delete unused paragraph items now.
      foreach (paragraphs_item_load_multiple($ids) as $item) {
        $item->forceHostEntity($entity);
        $item->setHostEntity($entity_type, $entity, $langcode, FALSE);
        $item->deleteRevision(TRUE);
      }
    }
@@ -805,8 +818,9 @@ function paragraphs_field_delete_revision($entity_type, $entity, $field, $instan
  if ($field['type'] == 'paragraphs') {
    foreach ($items as $item) {
      if (!empty($item['revision_id'])) {
        if ($entity = paragraphs_item_revision_load($item['revision_id'])) {
          $entity->deleteRevision(TRUE);
        if ($paragraphs_item = paragraphs_item_revision_load($item['revision_id'])) {
          $paragraphs_item->setHostEntity($entity_type, $entity, $langcode, FALSE);
          $paragraphs_item->deleteRevision(TRUE);
        }
      }
    }
+2 −0
Changes for paragraphs.node_clone.inc: 2 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -59,6 +59,8 @@ function paragraphs_clone_items($entity_type, &$entity, $field_name, $language =

  foreach ($old_items as $old_item) {
    list( , , $bundle) = entity_extract_ids('paragraphs_item', $old_item);

    /* @var $new_item ParagraphsItemEntity */
    $new_item = entity_create('paragraphs_item', array('bundle' => $bundle, 'field_name' => $field_name));
    $new_item->setHostEntity($entity_type, $entity, $language);