Browse Source

security updated for entity api

Bachir Soussi Chiadmi 6 years ago
parent
commit
8fb74fdf95

+ 9 - 1
sites/all/modules/entity/README.txt

@@ -11,7 +11,15 @@ entity CRUD controller, which helps simplifying the creation of new entity types
 This is an API module. You only need to enable it if a module depends on it or
 you are interested in using it for development.
 
-This README is for interested developers. If you are not interested in
+Advanced usage:
+---------------
+You can optimize cache clearing performance by setting the variable
+'entity_rebuild_on_flush' to FALSE. This skips rebuilding of feature
+components and exported entities during cache flushing. Instead, it is triggered
+by the features module only; e.g., when features are reverted.
+
+
+The README below is for interested developers. If you are not interested in
 developing, you may stop reading now.
 
 --------------------------------------------------------------------------------

+ 153 - 0
sites/all/modules/entity/ctools/relationships/entity_property.inc

@@ -0,0 +1,153 @@
+<?php
+
+/**
+ * @file
+ * Plugin to provide an relationship handler for any entity property.
+ */
+
+/**
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+  'title' => t('Entity property or field (via Entity Metadata Wrapper)'),
+  'description' => t('Creates any kind of context for entity properties and fields.'),
+  'context' => 'entity_entity_property_context',
+  'required context' => new ctools_context_required(t('Entity'), 'entity'),
+  'edit form' => 'entity_entity_property_edit_form',
+  'edit form validate' => 'entity_entity_property_edit_form_validate',
+  'defaults' => array(
+    'selector' => '',
+    'target_context' => 'entity',
+    'concatenator' => ','
+  ),
+);
+
+/**
+ * Return a new context based on an existing context.
+ */
+function entity_entity_property_context($context, $conf) {
+  $plugin = $conf['name'];
+
+  // If unset it wants a generic, unfilled context, which is just NULL.
+  if (empty($context->data)) {
+    return ctools_context_create_empty(isset($conf['target_context']) ? $conf['target_context'] : 'entity', NULL);
+  }
+
+  list($part1, $entity_type) = explode(':', $context->plugin);
+
+  if (isset($context->data) && $entity = $context->data) {
+    // Apply the selector.
+    $wrapper = entity_metadata_wrapper($entity_type, $entity);
+    $parts = explode(':', $conf['selector']);
+
+    try {
+      foreach ($parts as $part) {
+        if (!($wrapper instanceof EntityStructureWrapper || $wrapper instanceof EntityListWrapper)) {
+          $wrapper = NULL;
+          $value = NULL;
+          continue;
+        }
+        $wrapper = $wrapper->get($part);
+      }
+    }
+    catch (EntityMetadataWrapperException $e) {
+      $wrapper = NULL;
+      $value = NULL;
+    }
+
+    if (isset($wrapper)) {
+      $value = $wrapper->value();
+    }
+
+    // Massage list values.
+    if ($wrapper instanceof EntityListWrapper) {
+      $value = $wrapper[0]->value();
+      $argument = implode($conf['concatenator'], $wrapper->value(array('identifier' => TRUE)));
+    }
+    elseif ($wrapper instanceof EntityDrupalWrapper) {
+      $argument = $wrapper->getIdentifier();
+    }
+
+    // Bail out if we were unable to retrieve the value.
+    if (!isset($value)) {
+      return ctools_context_create_empty(isset($conf['target_context']) ? $conf['target_context'] : 'entity', NULL);
+    }
+
+    $context = ctools_context_create($conf['target_context'], $value);
+    // Provide a suiting argument if context is used as argument.
+    if (isset($argument)) {
+      $context->argument = $argument;
+    }
+    return $context;
+  }
+}
+
+function entity_entity_property_edit_form($form, &$form_state) {
+  $conf = $form_state['conf'];
+
+  $form['selector'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Data selector'),
+    '#description' => t('Any valid data selector, e.g. "title" to select a node\'s title, or "field_tags:1" to select the second tag.'),
+    '#default_value' =>  $conf['selector'],
+    '#required' => TRUE,
+  );
+  $form['concatenator'] = array(
+    '#title' => t('Concatenator (if multiple)'),
+    '#type' => 'select',
+    '#options' => array(',' => ', (AND)', '+' => '+ (OR)'),
+    '#default_value' => $conf['concatenator'],
+    '#prefix' => '<div class="clearfix">',
+    '#suffix' => '</div>',
+    '#description' => t("When the resulting value is multiple valued and the context is passed on to a view as argument, the value can be concatenated in the form of 1+2+3 (for OR) or 1,2,3 (for AND)."),
+  );
+  return $form;
+}
+
+function entity_entity_property_edit_form_validate($form, &$form_state) {
+  $context_key = $form_state['values']['context'];
+  $context = $form_state['contexts'][$context_key];
+  $entity_type = $context->type[2];
+
+  try {
+    $all_properties = entity_get_all_property_info($entity_type);
+    $wrapper = entity_metadata_wrapper($entity_type, NULL, array('property info' => $all_properties));
+    $parts = explode(':', $form_state['values']['selector']);
+    foreach ($parts as $part) {
+      if (!($wrapper instanceof EntityStructureWrapper || $wrapper instanceof EntityListWrapper)) {
+        form_set_error('selector', t('Unable to apply the data selector part %key.'. array('%key' => $part)));
+        continue;
+      }
+      $wrapper = $wrapper->get($part);
+    }
+    $type = entity_entity_property_map_data_type($wrapper->type());
+    $form_state['conf']['target_context'] = $type;
+  }
+  catch (EntityMetadataWrapperException $e) {
+    form_set_error('selector', t('Unable to apply the data selector on entity type %type. @reason', array('@reason' => $e->getMessage(), '%type' => $entity_type)));
+  }
+
+  // Auto-generate a sane identifier.
+  if ($form_state['values']['identifier'] == $form['identifier']['#default_value']) {
+    $form_state['values']['identifier'] = 'Entity property ' . $entity_type . ':' . check_plain($form_state['values']['selector']);
+  }
+}
+
+/**
+ * Maps an entity-property data type to ctools types.
+ */
+function entity_entity_property_map_data_type($type) {
+  // Remove list<> wrappers from types.
+  while ($item_type = entity_property_list_extract_type($type)) {
+    $type = $item_type;
+  }
+  // Massage data type of entites to c
+  if (entity_get_info($type)) {
+    $type = "entity:$type";
+  }
+  if ($type == 'text') {
+    $type = 'string';
+  }
+  return $type;
+}

+ 29 - 3
sites/all/modules/entity/entity.features.inc

@@ -7,6 +7,12 @@
 
 /**
  * Returns the configured entity features controller.
+ *
+ * @param string $type
+ *   The entity type to get the controller for.
+ *
+ * @return EntityDefaultFeaturesController
+ *   The configured entity features controller.
  */
 function entity_features_get_controller($type) {
   $static = &drupal_static(__FUNCTION__);
@@ -85,6 +91,7 @@ class EntityDefaultFeaturesController {
           $fields = field_info_instances($this->info['bundle of'], $entity->{$this->bundleKey});
           foreach ($fields as $name => $field) {
             $pipe['field'][] = "{$field['entity_type']}-{$field['bundle']}-{$field['field_name']}";
+            $pipe['field_instance'][] = "{$field['entity_type']}-{$field['bundle']}-{$field['field_name']}";
           }
         }
       }
@@ -125,9 +132,7 @@ class EntityDefaultFeaturesController {
    */
   function revert($module = NULL) {
     if ($defaults = features_get_default($this->type, $module)) {
-      foreach ($defaults as $name => $entity) {
-        entity_delete($this->type, $name);
-      }
+      entity_delete_multiple($this->type, array_keys($defaults));
     }
   }
 }
@@ -146,6 +151,8 @@ function entity_features_api() {
 }
 
 /**
+ * Implements hook_features_export_options().
+ *
  * Features component callback.
  */
 function entity_features_export_options($a1, $a2 = NULL) {
@@ -157,6 +164,8 @@ function entity_features_export_options($a1, $a2 = NULL) {
 }
 
 /**
+ * Implements hook_features_export().
+ *
  * Features component callback.
  */
 function entity_features_export($data, &$export, $module_name = '', $entity_type) {
@@ -164,6 +173,8 @@ function entity_features_export($data, &$export, $module_name = '', $entity_type
 }
 
 /**
+ * Implements hook_features_export_render().
+ *
  * Features component callback.
  */
 function entity_features_export_render($module, $data, $export = NULL, $entity_type) {
@@ -171,8 +182,23 @@ function entity_features_export_render($module, $data, $export = NULL, $entity_t
 }
 
 /**
+ * Implements hook_features_revert().
+ *
  * Features component callback.
  */
 function entity_features_revert($module = NULL, $entity_type) {
   return entity_features_get_controller($entity_type)->revert($module);
 }
+
+/**
+ * Implements hook_features_post_restore().
+ *
+ * Rebuild all defaults when a features rebuild is triggered - even the ones not
+ * handled by features itself.
+ */
+function entity_features_post_restore($op, $items = array()) {
+  if ($op == 'rebuild') {
+    // Use features rebuild to rebuild the features independent exports too.
+    entity_defaults_rebuild();
+  }
+}

+ 3 - 3
sites/all/modules/entity/entity.info

@@ -25,9 +25,9 @@ files[] = views/handlers/entity_views_handler_field_uri.inc
 files[] = views/handlers/entity_views_handler_relationship_by_bundle.inc
 files[] = views/handlers/entity_views_handler_relationship.inc
 files[] = views/plugins/entity_views_plugin_row_entity_view.inc
-; Information added by Drupal.org packaging script on 2015-02-25
-version = "7.x-1.6"
+; Information added by Drupal.org packaging script on 2018-02-14
+version = "7.x-1.9"
 core = "7.x"
 project = "entity"
-datestamp = "1424876582"
+datestamp = "1518620551"
 

+ 8 - 0
sites/all/modules/entity/entity.install

@@ -13,6 +13,14 @@ function entity_enable() {
   entity_entitycache_installed_modules();
 }
 
+/**
+ * Implements hook_uninstall().
+ */
+function entity_uninstall() {
+  // Delete variables.
+  variable_del('entity_rebuild_on_flush');
+}
+
 /**
  * The entity API modules have been merged into a single module.
  */

+ 22 - 16
sites/all/modules/entity/entity.module

@@ -184,8 +184,17 @@ function entity_ui_entity_page_view($entity) {
  * Gets the page title for the passed operation.
  */
 function entity_ui_get_page_title($op, $entity_type, $entity = NULL) {
-  module_load_include('inc', 'entity', 'includes/entity.ui');
-  $label = entity_label($entity_type, $entity);
+  if (isset($entity)) {
+    module_load_include('inc', 'entity', 'includes/entity.ui');
+    $label = entity_label($entity_type, $entity);
+    list(, , $bundle) = entity_extract_ids($entity_type, $entity);
+  }
+  else {
+    $info = entity_get_info($entity_type);
+    $label = $info['label'];
+    $bundle = NULL;
+  }
+
   switch ($op) {
     case 'view':
       return $label;
@@ -200,12 +209,7 @@ function entity_ui_get_page_title($op, $entity_type, $entity = NULL) {
     case 'export':
       return t('Export @label', array('@label' => $label));
   }
-  if (isset($entity)) {
-    list(, , $bundle) = entity_extract_ids($entity_type, $entity);
-  }
-  else {
-    $bundle = NULL;
-  }
+
   return entity_ui_get_action_title($op, $entity_type, $bundle);
 }
 
@@ -600,7 +604,7 @@ function entity_id($entity_type, $entity) {
  *   content language of the current request.
  * @param $page
  *   (optional) If set will control if the entity is rendered: if TRUE
- *   the entity will be rendered without its title, so that it can be embeded
+ *   the entity will be rendered without its title, so that it can be embedded
  *   in another context. If FALSE the entity will be displayed with its title
  *   in a mode suitable for lists.
  *   If unset, the page mode will be enabled if the current path is the URI
@@ -1076,16 +1080,18 @@ function entity_flush_caches() {
   // case of enabling or disabling modules we already rebuild defaults in
   // entity_modules_enabled() and entity_modules_disabled(), so we do not need
   // to do it again.
-  if (current_path() != 'admin/modules/list/confirm') {
+  // Also check if rebuilding on cache flush is explicitly disabled.
+  if (current_path() != 'admin/modules/list/confirm' && variable_get('entity_rebuild_on_flush', TRUE)) {
     entity_defaults_rebuild();
   }
 
   // Care about entitycache tables.
   if (module_exists('entitycache')) {
     $tables = array();
-    foreach (entity_crud_get_info() as $entity_type => $entity_info) {
-      if (isset($entity_info['module']) && !empty($entity_info['entity cache'])) {
-        $tables[] = 'cache_entity_' . $entity_type;
+    $tables_created = variable_get('entity_cache_tables_created');
+    if (is_array($tables_created)) {
+      foreach ($tables_created as $module => $entity_cache_tables) {
+        $tables = array_merge($tables, $entity_cache_tables);
       }
     }
     return $tables;
@@ -1372,7 +1378,7 @@ function entity_get_extra_fields_controller($type = NULL) {
  * Returns a property wrapper for the given data.
  *
  * If an entity is wrapped, the wrapper can be used to retrieve further wrappers
- * for the entitity properties. For that the wrapper support chaining, e.g. you
+ * for the entity properties. For that the wrapper support chaining, e.g. you
  * can use a node wrapper to get the node authors mail address:
  *
  * @code
@@ -1563,7 +1569,7 @@ function _entity_info_add_metadata(&$entity_info) {
  * Implements hook_ctools_plugin_directory().
  */
 function entity_ctools_plugin_directory($module, $plugin) {
-  if ($module == 'ctools' && $plugin == 'content_types') {
-    return 'ctools/content_types';
+  if ($module == 'ctools') {
+    return "ctools/$plugin";
   }
 }

+ 73 - 0
sites/all/modules/entity/entity.test

@@ -1342,6 +1342,74 @@ class EntityMetadataNodeRevisionAccessTestCase extends DrupalWebTestCase {
   }
 }
 
+/**
+ * Tests basic entity_access() functionality for taxonomy terms.
+ */
+class EntityMetadataTaxonomyAccessTestCase extends EntityWebTestCase {
+  public static function getInfo() {
+    return array(
+      'name' => 'Entity Metadata Taxonomy Access',
+      'description' => 'Test entity_access() for taxonomy terms',
+      'group' => 'Entity API',
+    );
+  }
+
+  /**
+   * Asserts entity_access() correctly grants or denies access.
+   */
+  function assertTaxonomyMetadataAccess($ops, $term, $account) {
+    foreach ($ops as $op => $result) {
+      $msg = t("entity_access() returns @result with operation '@op'.", array('@result' => $result ? 'TRUE' : 'FALSE', '@op' => $op));
+      $access = entity_access($op, 'taxonomy_term', $term, $account);
+      $this->assertEqual($result, $access, $msg);
+    }
+  }
+
+  /**
+   * @inheritdoc
+   */
+  function setUp() {
+    parent::setUp('entity', 'taxonomy');
+    // Clear permissions for authenticated users.
+    db_delete('role_permission')
+      ->condition('rid', DRUPAL_AUTHENTICATED_RID)
+      ->execute();
+  }
+
+  /**
+   * Runs basic tests for entity_access() function.
+   */
+  function testTaxonomyMetadataAccess() {
+    $vocab = $this->createVocabulary();
+    $term = entity_property_values_create_entity('taxonomy_term', array(
+      'name' => $this->randomName(),
+      'vocabulary' => $vocab,
+    ))->save()->value();
+    // Clear permissions static cache to get new taxonomy permissions.
+    drupal_static_reset('checkPermissions');
+
+    // Check assignment of view permissions.
+    $user1 = $this->drupalCreateUser(array('access content'));
+    $this->assertTaxonomyMetadataAccess(array('create' => FALSE, 'view' => TRUE, 'update' => FALSE, 'delete' => FALSE), $term, $user1);
+
+    // Check assignment of edit permissions.
+    $user2 = $this->drupalCreateUser(array('edit terms in ' . $vocab->vid));
+    $this->assertTaxonomyMetadataAccess(array('create' => FALSE, 'view' => FALSE, 'update' => TRUE, 'delete' => FALSE), $term, $user2);
+
+    // Check assignment of delete permissions.
+    $user3 = $this->drupalCreateUser(array('delete terms in ' . $vocab->vid));
+    $this->assertTaxonomyMetadataAccess(array('create' => FALSE, 'view' => FALSE, 'update' => FALSE, 'delete' => TRUE), $term, $user3);
+
+    // Check assignment of view, edit, delete permissions.
+    $user4 = $this->drupalCreateUser(array('access content', 'edit terms in ' . $vocab->vid, 'delete terms in ' . $vocab->vid));
+    $this->assertTaxonomyMetadataAccess(array('create' => FALSE, 'view' => TRUE, 'update' => TRUE, 'delete' => TRUE), $term, $user4);
+
+    // Check assignment of administration permissions.
+    $user5 = $this->drupalCreateUser(array('administer taxonomy'));
+    $this->assertTaxonomyMetadataAccess(array('create' => TRUE, 'view' => TRUE, 'update' => TRUE, 'delete' => TRUE), $term, $user5);
+  }
+}
+
 /**
  * Tests provided entity property info of the core modules.
  */
@@ -1466,6 +1534,7 @@ class EntityMetadataIntegrationTestCase extends EntityWebTestCase {
     $book = array('bid' => $node->nid, 'plid' => $node->book['mlid']);
     $node2 = $this->drupalCreateNode(array('type' => 'book', 'book' => $book));
     $node3 = $this->drupalCreateNode(array('type' => 'page'));
+    $node4 = $this->drupalCreateNode(array('type' => 'book', 'book' => array('bid' => 0, 'plid' => -1)));
 
     // Test whether the properties work.
     $wrapper = entity_metadata_wrapper('node', $node2);
@@ -1477,6 +1546,10 @@ class EntityMetadataIntegrationTestCase extends EntityWebTestCase {
     $wrapper = entity_metadata_wrapper('node', $node3);
     $this->assertEmpty($wrapper, 'book');
     $this->assertEmptyArray($wrapper, 'book_ancestors');
+
+    // Test a book node which is not contained in a hierarchy.
+    $wrapper = entity_metadata_wrapper('node', $node4);
+    $this->assertEmptyArray($wrapper, 'book_ancestors');
   }
 
   /**

+ 3 - 3
sites/all/modules/entity/entity_token.info

@@ -5,9 +5,9 @@ files[] = entity_token.tokens.inc
 files[] = entity_token.module
 dependencies[] = entity
 
-; Information added by Drupal.org packaging script on 2015-02-25
-version = "7.x-1.6"
+; Information added by Drupal.org packaging script on 2018-02-14
+version = "7.x-1.9"
 core = "7.x"
 project = "entity"
-datestamp = "1424876582"
+datestamp = "1518620551"
 

+ 1 - 1
sites/all/modules/entity/includes/entity.controller.inc

@@ -107,7 +107,7 @@ interface EntityAPIControllerInterface extends DrupalEntityControllerInterface {
    *   content language of the current request.
    * @param $page
    *   (optional) If set will control if the entity is rendered: if TRUE
-   *   the entity will be rendered without its title, so that it can be embeded
+   *   the entity will be rendered without its title, so that it can be embedded
    *   in another context. If FALSE the entity will be displayed with its title
    *   in a mode suitable for lists.
    *   If unset, the page mode will be enabled if the current path is the URI

+ 180 - 95
sites/all/modules/entity/includes/entity.inc

@@ -5,6 +5,168 @@
  * Provides a base class for entities.
  */
 
+/**
+ * Interface for class based entities.
+ */
+interface EntityInterface {
+
+  /**
+   * Returns the internal, numeric identifier.
+   *
+   * Returns the numeric identifier, even if the entity type has specified a
+   * name key. In the latter case, the numeric identifier is supposed to be used
+   * when dealing generically with entities or internally to refer to an entity,
+   * i.e. in a relational database. If unsure, use Entity:identifier().
+   */
+  public function internalIdentifier();
+
+  /**
+   * Returns the entity identifier, i.e. the entities name or numeric id.
+   *
+   * @return
+   *   The identifier of the entity. If the entity type makes use of a name key,
+   *   the name is returned, else the numeric id.
+   *
+   * @see entity_id()
+   */
+  public function identifier();
+
+  /**
+   * Returns the info of the type of the entity.
+   *
+   * @see entity_get_info()
+   */
+  public function entityInfo();
+
+  /**
+   * Returns the type of the entity.
+   */
+  public function entityType();
+
+  /**
+   * Returns the bundle of the entity.
+   *
+   * @return
+   *   The bundle of the entity. Defaults to the entity type if the entity type
+   *   does not make use of different bundles.
+   */
+  public function bundle();
+
+  /**
+   * Returns the EntityMetadataWrapper of the entity.
+   *
+   * @return EntityDrupalWrapper
+   *   An EntityMetadataWrapper containing the entity.
+   */
+  public function wrapper();
+
+  /**
+   * Returns the label of the entity.
+   *
+   * Modules may alter the label by specifying another 'label callback' using
+   * hook_entity_info_alter().
+   *
+   * @see entity_label()
+   */
+  public function label();
+
+  /**
+   * Returns the uri of the entity just as entity_uri().
+   *
+   * Modules may alter the uri by specifying another 'uri callback' using
+   * hook_entity_info_alter().
+   *
+   * @see entity_uri()
+   */
+  public function uri();
+
+  /**
+   * Checks if the entity has a certain exportable status.
+   *
+   * @param $status
+   *   A status constant, i.e. one of ENTITY_CUSTOM, ENTITY_IN_CODE,
+   *   ENTITY_OVERRIDDEN or ENTITY_FIXED.
+   *
+   * @return bool
+   *   For exportable entities TRUE if the entity has the status, else FALSE.
+   *   In case the entity is not exportable, NULL is returned.
+   *
+   * @see entity_has_status()
+   */
+  public function hasStatus($status);
+
+  /**
+   * Permanently saves the entity.
+   *
+   * @see entity_save()
+   */
+  public function save();
+
+  /**
+   * Permanently deletes the entity.
+   *
+   * @see entity_delete()
+   */
+  public function delete();
+
+  /**
+   * Exports the entity.
+   *
+   * @see entity_export()
+   */
+  public function export($prefix = '');
+
+  /**
+   * Generate an array for rendering the entity.
+   *
+   * @see entity_view()
+   */
+  public function view($view_mode = 'full', $langcode = NULL, $page = NULL);
+
+  /**
+   * Builds a structured array representing the entity's content.
+   *
+   * @see entity_build_content()
+   */
+  public function buildContent($view_mode = 'full', $langcode = NULL);
+
+  /**
+   * Gets the raw, translated value of a property or field.
+   *
+   * Supports retrieving field translations as well as i18n string translations.
+   *
+   * Note that this returns raw data values, which might not reflect what
+   * has been declared for hook_entity_property_info() as no 'getter callbacks'
+   * are invoked or no referenced entities are loaded. For retrieving values
+   * reflecting the property info make use of entity metadata wrappers, see
+   * entity_metadata_wrapper().
+   *
+   * @param $property
+   *   The name of the property to return; e.g., 'title'.
+   * @param $langcode
+   *   (optional) The language code of the language to which the value should
+   *   be translated. If set to NULL, the default display language is being
+   *   used.
+   *
+   * @return
+   *   The raw, translated property value; or the raw, un-translated value if no
+   *   translation is available.
+   *
+   * @todo Implement an analogous setTranslation() method for updating.
+   */
+  public function getTranslation($property, $langcode = NULL);
+
+  /**
+   * Checks whether the entity is the default revision.
+   *
+   * @return Boolean
+   *
+   * @see entity_revision_is_default()
+   */
+  public function isDefaultRevision();
+
+}
+
 /**
  * A common class for entities.
  *
@@ -25,7 +187,7 @@
  *   public $count = 0;
  * @endcode
  */
-class Entity {
+class Entity implements EntityInterface {
 
   protected $entityType;
   protected $entityInfo;
@@ -34,9 +196,7 @@ class Entity {
   protected $wrapper;
 
   /**
-   * Creates a new entity.
-   *
-   * @see entity_create()
+   * {@inheritdoc}
    */
   public function __construct(array $values = array(), $entityType = NULL) {
     if (empty($entityType)) {
@@ -61,62 +221,42 @@ class Entity {
   }
 
   /**
-   * Returns the internal, numeric identifier.
-   *
-   * Returns the numeric identifier, even if the entity type has specified a
-   * name key. In the latter case, the numeric identifier is supposed to be used
-   * when dealing generically with entities or internally to refer to an entity,
-   * i.e. in a relational database. If unsure, use Entity:identifier().
+   * {@inheritdoc}
    */
   public function internalIdentifier() {
     return isset($this->{$this->idKey}) ? $this->{$this->idKey} : NULL;
   }
 
   /**
-   * Returns the entity identifier, i.e. the entities name or numeric id.
-   *
-   * @return
-   *   The identifier of the entity. If the entity type makes use of a name key,
-   *   the name is returned, else the numeric id.
-   *
-   * @see entity_id()
+   * {@inheritdoc}
    */
   public function identifier() {
     return isset($this->{$this->nameKey}) ? $this->{$this->nameKey} : NULL;
   }
 
   /**
-   * Returns the info of the type of the entity.
-   *
-   * @see entity_get_info()
+   * {@inheritdoc}
    */
   public function entityInfo() {
     return $this->entityInfo;
   }
 
   /**
-   * Returns the type of the entity.
+   * {@inheritdoc}
    */
   public function entityType() {
     return $this->entityType;
   }
 
   /**
-   * Returns the bundle of the entity.
-   *
-   * @return
-   *   The bundle of the entity. Defaults to the entity type if the entity type
-   *   does not make use of different bundles.
+   * {@inheritdoc}
    */
   public function bundle() {
     return !empty($this->entityInfo['entity keys']['bundle']) ? $this->{$this->entityInfo['entity keys']['bundle']} : $this->entityType;
   }
 
   /**
-   * Returns the EntityMetadataWrapper of the entity.
-   *
-   * @return EntityDrupalWrapper
-   *   An EntityMetadataWrapper containing the entity.
+   * {@inheritdoc}
    */
   public function wrapper() {
     if (empty($this->wrapper)) {
@@ -130,12 +270,7 @@ class Entity {
   }
 
   /**
-   * Returns the label of the entity.
-   *
-   * Modules may alter the label by specifying another 'label callback' using
-   * hook_entity_info_alter().
-   *
-   * @see entity_label()
+   * {@inheritdoc}
    */
   public function label() {
     // If the default label flag is enabled, this is being invoked recursively.
@@ -165,12 +300,7 @@ class Entity {
   }
 
   /**
-   * Returns the uri of the entity just as entity_uri().
-   *
-   * Modules may alter the uri by specifying another 'uri callback' using
-   * hook_entity_info_alter().
-   *
-   * @see entity_uri()
+   * {@inheritdoc}
    */
   public function uri() {
     if (isset($this->entityInfo['uri callback']) && $this->entityInfo['uri callback'] == 'entity_class_uri') {
@@ -188,17 +318,7 @@ class Entity {
   }
 
   /**
-   * Checks if the entity has a certain exportable status.
-   *
-   * @param $status
-   *   A status constant, i.e. one of ENTITY_CUSTOM, ENTITY_IN_CODE,
-   *   ENTITY_OVERRIDDEN or ENTITY_FIXED.
-   *
-   * @return
-   *   For exportable entities TRUE if the entity has the status, else FALSE.
-   *   In case the entity is not exportable, NULL is returned.
-   *
-   * @see entity_has_status()
+   * {@inheritdoc}
    */
   public function hasStatus($status) {
     if (!empty($this->entityInfo['exportable'])) {
@@ -207,18 +327,14 @@ class Entity {
   }
 
   /**
-   * Permanently saves the entity.
-   *
-   * @see entity_save()
+   * {@inheritdoc}
    */
   public function save() {
     return entity_get_controller($this->entityType)->save($this);
   }
 
   /**
-   * Permanently deletes the entity.
-   *
-   * @see entity_delete()
+   * {@inheritdoc}
    */
   public function delete() {
     $id = $this->identifier();
@@ -228,55 +344,28 @@ class Entity {
   }
 
   /**
-   * Exports the entity.
-   *
-   * @see entity_export()
+   * {@inheritdoc}
    */
   public function export($prefix = '') {
     return entity_get_controller($this->entityType)->export($this, $prefix);
   }
 
   /**
-   * Generate an array for rendering the entity.
-   *
-   * @see entity_view()
+   * {@inheritdoc}
    */
   public function view($view_mode = 'full', $langcode = NULL, $page = NULL) {
     return entity_get_controller($this->entityType)->view(array($this), $view_mode, $langcode, $page);
   }
 
   /**
-   * Builds a structured array representing the entity's content.
-   *
-   * @see entity_build_content()
+   * {@inheritdoc}
    */
   public function buildContent($view_mode = 'full', $langcode = NULL) {
     return entity_get_controller($this->entityType)->buildContent($this, $view_mode, $langcode);
   }
 
   /**
-   * Gets the raw, translated value of a property or field.
-   *
-   * Supports retrieving field translations as well as i18n string translations.
-   *
-   * Note that this returns raw data values, which might not reflect what
-   * has been declared for hook_entity_property_info() as no 'getter callbacks'
-   * are invoked or no referenced entities are loaded. For retrieving values
-   * reflecting the property info make use of entity metadata wrappers, see
-   * entity_metadata_wrapper().
-   *
-   * @param $property_name
-   *   The name of the property to return; e.g., 'title'.
-   * @param $langcode
-   *   (optional) The language code of the language to which the value should
-   *   be translated. If set to NULL, the default display language is being
-   *   used.
-   *
-   * @return
-   *   The raw, translated property value; or the raw, un-translated value if no
-   *   translation is available.
-   *
-   * @todo Implement an analogous setTranslation() method for updating.
+   * {@inheritdoc}
    */
   public function getTranslation($property, $langcode = NULL) {
     $all_info = entity_get_all_property_info($this->entityType);
@@ -296,11 +385,7 @@ class Entity {
   }
 
   /**
-   * Checks whether the entity is the default revision.
-   *
-   * @return Boolean
-   *
-   * @see entity_revision_is_default()
+   * {@inheritdoc}
    */
   public function isDefaultRevision() {
     if (!empty($this->entityInfo['entity keys']['revision'])) {

+ 1 - 1
sites/all/modules/entity/includes/entity.property.inc

@@ -69,7 +69,7 @@ function entity_property_info_defaults() {
  *   (optiona) The entity type to return properties for.
  *
  * @return
- *   An array of info about properties. If the type is ommitted, all known
+ *   An array of info about properties. If the type is omitted, all known
  *   properties are returned.
  */
 function entity_get_all_property_info($entity_type = NULL) {

+ 4 - 4
sites/all/modules/entity/includes/entity.ui.inc

@@ -127,8 +127,8 @@ class EntityDefaultUIController {
    * Use per bundle form ids if possible, such that easy per bundle alterations
    * are supported too.
    *
-   * Note that for performance reasons, this method is only invoked for forms,
-   * which receive the entity_type as first argument. Thus any forms added, must
+   * Note that for performance reasons, this method is invoked only for forms
+   * which receive the entity_type as first argument. Thus any forms added must
    * follow that pattern.
    *
    * @see entity_forms()
@@ -645,7 +645,7 @@ function entity_ui_operation_form($form, &$form_state, $entity_type, $entity, $o
  */
 function entity_ui_main_form_defaults($form, &$form_state, $entity = NULL, $op = NULL) {
   // Now equals entity_ui_form_defaults() but is still here to keep backward
-  // compatability.
+  // compatibility.
   return entity_ui_form_defaults($form, $form_state, $form_state['entity_type'], $entity, $op);
 }
 
@@ -760,7 +760,7 @@ function entity_ui_validate_machine_name($element, &$form_state) {
 function theme_entity_ui_overview_item($variables) {
   $output = $variables['url'] ? l($variables['label'], $variables['url']['path'], $variables['url']['options']) : check_plain($variables['label']);
   if ($variables['name']) {
-    $output .= ' <small> (' . t('Machine name') . ': ' . check_plain($variables['name']) . ')</small>';
+    $output .= ' <small>(' . t('Machine name') . ': ' . check_plain($variables['name']) . ')</small>';
   }
   return $output;
 }

+ 53 - 4
sites/all/modules/entity/includes/entity.wrapper.inc

@@ -119,7 +119,11 @@ abstract class EntityMetadataWrapper {
    */
   public function set($value) {
     if (!$this->validate($value)) {
-      throw new EntityMetadataWrapperException('Invalid data value given. Be sure it matches the required data type and format.');
+      throw new EntityMetadataWrapperException(t('Invalid data value given. Be sure it matches the required data type and format. Value at !location: !value.', array(
+        // An exception's message is output through check_plain().
+        '!value' => is_array($value) || is_object($value) ? var_export($value, TRUE) : $value,
+        '!location' => $this->debugIdentifierLocation(),
+      )));
     }
     $this->clear();
     $this->data = $value;
@@ -231,6 +235,21 @@ abstract class EntityMetadataWrapper {
     return !empty($this->info['parent']) ? $this->info['parent']->propertyAccess($this->info['name'], $op, $account) : TRUE;
   }
 
+  /**
+   * Returns a string to use to identify this wrapper in error messages.
+   *
+   * @return
+   *  A string that identifies this wrapper and its chain of ancestors, of the
+   *  form 'grandparentidentifier->parentidentifier->identifier'.
+   */
+  public function debugIdentifierLocation() {
+    $debug = $this->info['name'];
+    if (isset($this->info['parent'])) {
+      $debug = $this->info['parent']->debugIdentifierLocation() . '->' . $debug;
+    }
+    return $debug;
+  }
+
   /**
    * Prepare for serializiation.
    */
@@ -734,7 +753,11 @@ class EntityDrupalWrapper extends EntityStructureWrapper {
    */
   public function set($value) {
     if (!$this->validate($value)) {
-      throw new EntityMetadataWrapperException('Invalid data value given. Be sure it matches the required data type and format.');
+      throw new EntityMetadataWrapperException(t('Invalid data value given. Be sure it matches the required data type and format. Value at !location: !value.', array(
+        // An exception's message is output through check_plain().
+        '!value' => is_array($value) || is_object($value) ? var_export($value, TRUE) : $value,
+        '!location' => $this->debugIdentifierLocation(),
+      )));
     }
     if ($this->info['type'] == 'entity' && $value === $this) {
       // Nothing to do.
@@ -821,7 +844,12 @@ class EntityDrupalWrapper extends EntityStructureWrapper {
     }
     else {
       // This is not a property, so fallback on entity access.
-      return $this->entityAccess($op == 'edit' ? 'update' : 'view', $account);
+      if ($op == 'edit') {
+        // If the operation is "edit" determine if its actually a "create" for
+        // new un-saved entities, or an "update" for existing ones.
+        return $this->entityAccess($this->getIdentifier() ? 'update' : 'create', $account);
+      }
+      return $this->entityAccess('view', $account);
     }
   }
 
@@ -909,6 +937,27 @@ class EntityDrupalWrapper extends EntityStructureWrapper {
     }
   }
 
+  /**
+   * Returns a string to use to identify this wrapper in error messages.
+   */
+  public function debugIdentifierLocation() {
+    // An entity wrapper can be at the top of the chain or a part of it.
+    if (isset($this->info['name'])) {
+      // This wrapper is part of a chain, eg in the position node->author.
+      // Return the name.
+      $debug = $this->info['name'];
+    }
+    else {
+      // This is a wrapper for an actual entity: return its type and id.
+      $debug = $this->info['type'] . '(' . $this->getIdentifier() . ')';
+    }
+
+    if (isset($this->info['parent'])) {
+      $debug = $this->info['parent']->debugIdentifierLocation() . '->' . $debug;
+    }
+    return $debug;
+  }
+
   /**
    * Prepare for serializiation.
    */
@@ -1067,7 +1116,7 @@ class EntityListWrapper extends EntityMetadataWrapper implements IteratorAggrega
    */
   public function getIterator() {
     // In case there is no data available, just iterate over the first item.
-    return new EntityMetadataWrapperIterator($this, $this->dataAvailable() ? array_keys(parent::value()) : array(0));
+    return new EntityMetadataWrapperIterator($this, ($this->dataAvailable() && is_array(parent::value())) ? array_keys(parent::value()) : array(0));
   }
 
   /**

+ 32 - 9
sites/all/modules/entity/modules/callbacks.inc

@@ -29,7 +29,7 @@ function entity_metadata_book_get_properties($node, array $options, $name, $enti
 
     case 'book_ancestors':
       $ancestors = array();
-      while (!empty($node->book['plid'])) {
+      while (!empty($node->book['plid']) && $node->book['plid'] != -1) {
         $link = book_link_load($node->book['plid']);
         array_unshift($ancestors, $link['nid']);
         $node = node_load($link['nid']);
@@ -670,9 +670,11 @@ function entity_metadata_field_file_validate_item($items, $context) {
 function entity_metadata_no_hook_node_access($op, $node = NULL, $account = NULL) {
   // First deal with the case where a $node is provided.
   if (isset($node)) {
-    if ($op == 'create') {
+    if (empty($node->vid) && in_array($op, array('create', 'update'))) {
+      // This is a new node or the original node.
       if (isset($node->type)) {
-        return node_access($op, $node->type, $account);
+        $op = empty($node->nid) || !empty($node->is_new) ? 'create' : 'update';
+        return node_access($op, $op == 'create' ? $node->type : $node, $account);
       }
       else {
         throw new EntityMalformedException('Permission to create a node was requested but no node type was given.');
@@ -796,14 +798,35 @@ function entity_metadata_comment_properties_access($op, $property, $entity = NUL
  * Access callback for the taxonomy entities.
  */
 function entity_metadata_taxonomy_access($op, $entity = NULL, $account = NULL, $entity_type = NULL) {
-  if ($entity_type == 'taxonomy_vocabulary') {
-    return user_access('administer taxonomy', $account);
-  }
-  if (isset($entity) && $op == 'update' && !isset($account) && taxonomy_term_edit_access($entity)) {
+  // If user has administer taxonomy permission then no further checks.
+  if (user_access('administer taxonomy', $account)) {
     return TRUE;
   }
-  if (user_access('administer taxonomy', $account) || user_access('access content', $account) && $op == 'view') {
-    return TRUE;
+  switch ($op) {
+    case "view":
+      if (user_access('access content', $account)) {
+        return TRUE;
+      }
+      break;
+    case "update":
+      if ($entity_type == 'taxonomy_term') {
+        return user_access("edit terms in $entity->vid", $account);
+      }
+      break;
+    case "create":
+      if ($entity_type == 'taxonomy_term') {
+        // Check for taxonomy_access_fix contrib module which adds additional
+        // permissions to create new terms in a given vocabulary.
+        if (function_exists('taxonomy_access_fix_access')) {
+          return taxonomy_access_fix_access('add terms', $entity->vocabulary_machine_name);
+        }
+      }
+      break;
+    case "delete":
+      if ($entity_type == 'taxonomy_term') {
+        return user_access("delete terms in $entity->vid", $account);
+      }
+      break;
   }
   return FALSE;
 }

+ 6 - 0
sites/all/modules/entity/modules/node.info.inc

@@ -163,4 +163,10 @@ function entity_metadata_node_entity_property_info_alter(&$info) {
     'auto creation' => 'entity_property_create_array',
     'field' => TRUE,
   );
+
+  // Make it a list if cardinality is not 1.
+  $field_body = field_info_field('body');
+  if (isset($field_body) && $field_body['cardinality'] != 1) {
+    $info['node']['properties']['body']['type'] = 'list<text_formatted>';
+  }
 }

+ 3 - 3
sites/all/modules/entity/tests/entity_feature.info

@@ -6,9 +6,9 @@ files[] = entity_feature.module
 dependencies[] = entity_test
 hidden = TRUE
 
-; Information added by Drupal.org packaging script on 2015-02-25
-version = "7.x-1.6"
+; Information added by Drupal.org packaging script on 2018-02-14
+version = "7.x-1.9"
 core = "7.x"
 project = "entity"
-datestamp = "1424876582"
+datestamp = "1518620551"
 

+ 3 - 3
sites/all/modules/entity/tests/entity_test.info

@@ -7,9 +7,9 @@ files[] = entity_test.install
 dependencies[] = entity
 hidden = TRUE
 
-; Information added by Drupal.org packaging script on 2015-02-25
-version = "7.x-1.6"
+; Information added by Drupal.org packaging script on 2018-02-14
+version = "7.x-1.9"
 core = "7.x"
 project = "entity"
-datestamp = "1424876582"
+datestamp = "1518620551"
 

+ 1 - 1
sites/all/modules/entity/tests/entity_test.module

@@ -2,7 +2,7 @@
 
 /**
  * @file
- * Test moduel for the entity API.
+ * Test module for the entity API.
  */
 
 /**

+ 3 - 3
sites/all/modules/entity/tests/entity_test_i18n.info

@@ -5,9 +5,9 @@ dependencies[] = i18n_string
 package = Multilingual - Internationalization
 core = 7.x
 hidden = TRUE
-; Information added by Drupal.org packaging script on 2015-02-25
-version = "7.x-1.6"
+; Information added by Drupal.org packaging script on 2018-02-14
+version = "7.x-1.9"
 core = "7.x"
 project = "entity"
-datestamp = "1424876582"
+datestamp = "1518620551"
 

+ 4 - 1
sites/all/modules/entity/theme/entity.theme.inc

@@ -80,9 +80,12 @@ function template_preprocess_entity_property(&$variables, $hook) {
   );
 
   // Populate the content with sensible defaults.
-  if (!isset($variables['content'])) {
+  if (!isset($element['#content'])) {
     $variables['content'] = entity_property_default_render_value_by_type($element['#entity_wrapped']->{$element['#property_name']});
   }
+  else {
+    $variables['content'] = $element['#content'];
+  }
 }
 
 /**

+ 22 - 0
sites/all/modules/entity/views/entity.views.inc

@@ -605,6 +605,28 @@ class EntityDefaultViewsController {
         );
       break;
 
+      case 'duration':
+        $return += $description + array(
+          'field' => array(
+            'real field' => $views_field_name,
+            'handler' => 'entity_views_handler_field_duration',
+            'click sortable' => TRUE,
+           ),
+          'sort' => array(
+            'real field' => $views_field_name,
+            'handler' => 'views_handler_sort',
+          ),
+          'filter' => array(
+            'real field' => $views_field_name,
+            'handler' => 'views_handler_filter_numeric',
+          ),
+          'argument' => array(
+            'real field' => $views_field_name,
+            'handler' => 'views_handler_argument_numeric',
+          ),
+        );
+      break;
+
       case 'uri':
         $return += $description + array(
           'field' => array(

+ 2 - 2
sites/all/modules/entity/views/handlers/entity_views_field_handler_helper.inc

@@ -18,7 +18,7 @@ class EntityFieldHandlerHelper {
    * Provide appropriate default options for a handler.
    */
   public static function option_definition($handler) {
-    if (entity_property_list_extract_type($handler->definition['type'])) {
+    if (isset($handler->definition['type']) && entity_property_list_extract_type($handler->definition['type'])) {
       $options['list']['contains']['mode'] = array('default' => 'collapse');
       $options['list']['contains']['separator'] = array('default' => ', ');
       $options['list']['contains']['type'] = array('default' => 'ul');
@@ -32,7 +32,7 @@ class EntityFieldHandlerHelper {
    * Provide an appropriate default option form for a handler.
    */
   public static function options_form($handler, &$form, &$form_state) {
-    if (entity_property_list_extract_type($handler->definition['type'])) {
+    if (isset($handler->definition['type']) && entity_property_list_extract_type($handler->definition['type'])) {
       $form['list']['mode'] = array(
         '#type' => 'select',
         '#title' => t('List handling'),

+ 31 - 1
sites/all/modules/entity/views/handlers/entity_views_handler_area_entity.inc

@@ -40,7 +40,7 @@ class entity_views_handler_area_entity extends views_handler_area {
     $form['entity_id'] = array(
       '#type' => 'textfield',
       '#title' => t('Entity id'),
-      '#description' => t('Choose the entity you want to display in the area.'),
+      '#description' => t('Choose the entity you want to display in the area. To render an entity given by a contextual filter use "%1" for the first argument, "%2" for the second, etc.'),
       '#default_value' => $this->options['entity_id'],
     );
 
@@ -105,6 +105,9 @@ class entity_views_handler_area_entity extends views_handler_area {
    * Render an entity using the view mode.
    */
   public function render_entity($entity_type, $entity_id, $view_mode) {
+    $tokens = $this->get_render_tokens();
+    // Replace argument tokens in entity id.
+    $entity_id = strtr($entity_id, $tokens);
     if (!empty($entity_type) && !empty($entity_id) && !empty($view_mode)) {
       $entity = entity_load_single($entity_type, $entity_id);
       if (!empty($this->options['bypass_access']) || entity_access('view', $entity_type, $entity)) {
@@ -117,4 +120,31 @@ class entity_views_handler_area_entity extends views_handler_area {
       return '';
     }
   }
+
+  /**
+   * Get the 'render' tokens to use for advanced rendering.
+   *
+   * This runs through all of the fields and arguments that
+   * are available and gets their values. This will then be
+   * used in one giant str_replace().
+   */
+  function get_render_tokens() {
+    $tokens = array();
+    if (!empty($this->view->build_info['substitutions'])) {
+      $tokens = $this->view->build_info['substitutions'];
+    }
+    $count = 0;
+    foreach ($this->view->display_handler->get_handlers('argument') as $arg => $handler) {
+      $token = '%' . ++$count;
+      if (!isset($tokens[$token])) {
+        $tokens[$token] = '';
+      }
+      // Use strip tags as there should never be HTML in the path.
+      // However, we need to preserve special characters like " that
+      // were removed by check_plain().
+      $tokens['%' . $count] = $handler->argument;
+    }
+
+    return $tokens;
+  }
 }

+ 3 - 0
sites/all/modules/entity/views/plugins/entity_views_plugin_row_entity_view.inc

@@ -88,6 +88,9 @@ class entity_views_plugin_row_entity_view extends views_plugin_row {
 
   public function render($values) {
     if ($entity = $this->get_value($values)) {
+      // Add the view object as views_plugin_row_node_view::render() would.
+      // Otherwise the views theme suggestions won't work properly.
+      $entity->view = $this->view;
       $render = $this->rendered_content[entity_id($this->entity_type, $entity)];
       return drupal_render($render);
     }