security updated for entity api

This commit is contained in:
Bachir Soussi Chiadmi 2018-04-24 14:17:02 +02:00
parent 870831757c
commit 8fb74fdf95
24 changed files with 649 additions and 154 deletions

View File

@ -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 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. 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. developing, you may stop reading now.
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------

View File

@ -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;
}

View File

@ -7,6 +7,12 @@
/** /**
* Returns the configured entity features controller. * 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) { function entity_features_get_controller($type) {
$static = &drupal_static(__FUNCTION__); $static = &drupal_static(__FUNCTION__);
@ -85,6 +91,7 @@ class EntityDefaultFeaturesController {
$fields = field_info_instances($this->info['bundle of'], $entity->{$this->bundleKey}); $fields = field_info_instances($this->info['bundle of'], $entity->{$this->bundleKey});
foreach ($fields as $name => $field) { foreach ($fields as $name => $field) {
$pipe['field'][] = "{$field['entity_type']}-{$field['bundle']}-{$field['field_name']}"; $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) { function revert($module = NULL) {
if ($defaults = features_get_default($this->type, $module)) { if ($defaults = features_get_default($this->type, $module)) {
foreach ($defaults as $name => $entity) { entity_delete_multiple($this->type, array_keys($defaults));
entity_delete($this->type, $name);
}
} }
} }
} }
@ -146,6 +151,8 @@ function entity_features_api() {
} }
/** /**
* Implements hook_features_export_options().
*
* Features component callback. * Features component callback.
*/ */
function entity_features_export_options($a1, $a2 = NULL) { 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. * Features component callback.
*/ */
function entity_features_export($data, &$export, $module_name = '', $entity_type) { 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. * Features component callback.
*/ */
function entity_features_export_render($module, $data, $export = NULL, $entity_type) { 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. * Features component callback.
*/ */
function entity_features_revert($module = NULL, $entity_type) { function entity_features_revert($module = NULL, $entity_type) {
return entity_features_get_controller($entity_type)->revert($module); 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();
}
}

View File

@ -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_by_bundle.inc
files[] = views/handlers/entity_views_handler_relationship.inc files[] = views/handlers/entity_views_handler_relationship.inc
files[] = views/plugins/entity_views_plugin_row_entity_view.inc files[] = views/plugins/entity_views_plugin_row_entity_view.inc
; Information added by Drupal.org packaging script on 2015-02-25 ; Information added by Drupal.org packaging script on 2018-02-14
version = "7.x-1.6" version = "7.x-1.9"
core = "7.x" core = "7.x"
project = "entity" project = "entity"
datestamp = "1424876582" datestamp = "1518620551"

View File

@ -13,6 +13,14 @@ function entity_enable() {
entity_entitycache_installed_modules(); 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. * The entity API modules have been merged into a single module.
*/ */

View File

@ -184,8 +184,17 @@ function entity_ui_entity_page_view($entity) {
* Gets the page title for the passed operation. * Gets the page title for the passed operation.
*/ */
function entity_ui_get_page_title($op, $entity_type, $entity = NULL) { function entity_ui_get_page_title($op, $entity_type, $entity = NULL) {
module_load_include('inc', 'entity', 'includes/entity.ui'); if (isset($entity)) {
$label = entity_label($entity_type, $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) { switch ($op) {
case 'view': case 'view':
return $label; return $label;
@ -200,12 +209,7 @@ function entity_ui_get_page_title($op, $entity_type, $entity = NULL) {
case 'export': case 'export':
return t('Export @label', array('@label' => $label)); 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); 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. * content language of the current request.
* @param $page * @param $page
* (optional) If set will control if the entity is rendered: if TRUE * (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 another context. If FALSE the entity will be displayed with its title
* in a mode suitable for lists. * in a mode suitable for lists.
* If unset, the page mode will be enabled if the current path is the URI * 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 // case of enabling or disabling modules we already rebuild defaults in
// entity_modules_enabled() and entity_modules_disabled(), so we do not need // entity_modules_enabled() and entity_modules_disabled(), so we do not need
// to do it again. // 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(); entity_defaults_rebuild();
} }
// Care about entitycache tables. // Care about entitycache tables.
if (module_exists('entitycache')) { if (module_exists('entitycache')) {
$tables = array(); $tables = array();
foreach (entity_crud_get_info() as $entity_type => $entity_info) { $tables_created = variable_get('entity_cache_tables_created');
if (isset($entity_info['module']) && !empty($entity_info['entity cache'])) { if (is_array($tables_created)) {
$tables[] = 'cache_entity_' . $entity_type; foreach ($tables_created as $module => $entity_cache_tables) {
$tables = array_merge($tables, $entity_cache_tables);
} }
} }
return $tables; return $tables;
@ -1372,7 +1378,7 @@ function entity_get_extra_fields_controller($type = NULL) {
* Returns a property wrapper for the given data. * Returns a property wrapper for the given data.
* *
* If an entity is wrapped, the wrapper can be used to retrieve further wrappers * 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: * can use a node wrapper to get the node authors mail address:
* *
* @code * @code
@ -1563,7 +1569,7 @@ function _entity_info_add_metadata(&$entity_info) {
* Implements hook_ctools_plugin_directory(). * Implements hook_ctools_plugin_directory().
*/ */
function entity_ctools_plugin_directory($module, $plugin) { function entity_ctools_plugin_directory($module, $plugin) {
if ($module == 'ctools' && $plugin == 'content_types') { if ($module == 'ctools') {
return 'ctools/content_types'; return "ctools/$plugin";
} }
} }

View File

@ -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. * 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']); $book = array('bid' => $node->nid, 'plid' => $node->book['mlid']);
$node2 = $this->drupalCreateNode(array('type' => 'book', 'book' => $book)); $node2 = $this->drupalCreateNode(array('type' => 'book', 'book' => $book));
$node3 = $this->drupalCreateNode(array('type' => 'page')); $node3 = $this->drupalCreateNode(array('type' => 'page'));
$node4 = $this->drupalCreateNode(array('type' => 'book', 'book' => array('bid' => 0, 'plid' => -1)));
// Test whether the properties work. // Test whether the properties work.
$wrapper = entity_metadata_wrapper('node', $node2); $wrapper = entity_metadata_wrapper('node', $node2);
@ -1477,6 +1546,10 @@ class EntityMetadataIntegrationTestCase extends EntityWebTestCase {
$wrapper = entity_metadata_wrapper('node', $node3); $wrapper = entity_metadata_wrapper('node', $node3);
$this->assertEmpty($wrapper, 'book'); $this->assertEmpty($wrapper, 'book');
$this->assertEmptyArray($wrapper, 'book_ancestors'); $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');
} }
/** /**

View File

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

View File

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

View File

@ -5,6 +5,168 @@
* Provides a base class for entities. * 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. * A common class for entities.
* *
@ -25,7 +187,7 @@
* public $count = 0; * public $count = 0;
* @endcode * @endcode
*/ */
class Entity { class Entity implements EntityInterface {
protected $entityType; protected $entityType;
protected $entityInfo; protected $entityInfo;
@ -34,9 +196,7 @@ class Entity {
protected $wrapper; protected $wrapper;
/** /**
* Creates a new entity. * {@inheritdoc}
*
* @see entity_create()
*/ */
public function __construct(array $values = array(), $entityType = NULL) { public function __construct(array $values = array(), $entityType = NULL) {
if (empty($entityType)) { if (empty($entityType)) {
@ -61,62 +221,42 @@ class Entity {
} }
/** /**
* Returns the internal, numeric identifier. * {@inheritdoc}
*
* 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() { public function internalIdentifier() {
return isset($this->{$this->idKey}) ? $this->{$this->idKey} : NULL; return isset($this->{$this->idKey}) ? $this->{$this->idKey} : NULL;
} }
/** /**
* Returns the entity identifier, i.e. the entities name or numeric id. * {@inheritdoc}
*
* @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() { public function identifier() {
return isset($this->{$this->nameKey}) ? $this->{$this->nameKey} : NULL; return isset($this->{$this->nameKey}) ? $this->{$this->nameKey} : NULL;
} }
/** /**
* Returns the info of the type of the entity. * {@inheritdoc}
*
* @see entity_get_info()
*/ */
public function entityInfo() { public function entityInfo() {
return $this->entityInfo; return $this->entityInfo;
} }
/** /**
* Returns the type of the entity. * {@inheritdoc}
*/ */
public function entityType() { public function entityType() {
return $this->entityType; return $this->entityType;
} }
/** /**
* Returns the bundle of the entity. * {@inheritdoc}
*
* @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() { public function bundle() {
return !empty($this->entityInfo['entity keys']['bundle']) ? $this->{$this->entityInfo['entity keys']['bundle']} : $this->entityType; return !empty($this->entityInfo['entity keys']['bundle']) ? $this->{$this->entityInfo['entity keys']['bundle']} : $this->entityType;
} }
/** /**
* Returns the EntityMetadataWrapper of the entity. * {@inheritdoc}
*
* @return EntityDrupalWrapper
* An EntityMetadataWrapper containing the entity.
*/ */
public function wrapper() { public function wrapper() {
if (empty($this->wrapper)) { if (empty($this->wrapper)) {
@ -130,12 +270,7 @@ class Entity {
} }
/** /**
* Returns the label of the entity. * {@inheritdoc}
*
* Modules may alter the label by specifying another 'label callback' using
* hook_entity_info_alter().
*
* @see entity_label()
*/ */
public function label() { public function label() {
// If the default label flag is enabled, this is being invoked recursively. // 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(). * {@inheritdoc}
*
* Modules may alter the uri by specifying another 'uri callback' using
* hook_entity_info_alter().
*
* @see entity_uri()
*/ */
public function uri() { public function uri() {
if (isset($this->entityInfo['uri callback']) && $this->entityInfo['uri callback'] == 'entity_class_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. * {@inheritdoc}
*
* @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()
*/ */
public function hasStatus($status) { public function hasStatus($status) {
if (!empty($this->entityInfo['exportable'])) { if (!empty($this->entityInfo['exportable'])) {
@ -207,18 +327,14 @@ class Entity {
} }
/** /**
* Permanently saves the entity. * {@inheritdoc}
*
* @see entity_save()
*/ */
public function save() { public function save() {
return entity_get_controller($this->entityType)->save($this); return entity_get_controller($this->entityType)->save($this);
} }
/** /**
* Permanently deletes the entity. * {@inheritdoc}
*
* @see entity_delete()
*/ */
public function delete() { public function delete() {
$id = $this->identifier(); $id = $this->identifier();
@ -228,55 +344,28 @@ class Entity {
} }
/** /**
* Exports the entity. * {@inheritdoc}
*
* @see entity_export()
*/ */
public function export($prefix = '') { public function export($prefix = '') {
return entity_get_controller($this->entityType)->export($this, $prefix); return entity_get_controller($this->entityType)->export($this, $prefix);
} }
/** /**
* Generate an array for rendering the entity. * {@inheritdoc}
*
* @see entity_view()
*/ */
public function view($view_mode = 'full', $langcode = NULL, $page = NULL) { public function view($view_mode = 'full', $langcode = NULL, $page = NULL) {
return entity_get_controller($this->entityType)->view(array($this), $view_mode, $langcode, $page); return entity_get_controller($this->entityType)->view(array($this), $view_mode, $langcode, $page);
} }
/** /**
* Builds a structured array representing the entity's content. * {@inheritdoc}
*
* @see entity_build_content()
*/ */
public function buildContent($view_mode = 'full', $langcode = NULL) { public function buildContent($view_mode = 'full', $langcode = NULL) {
return entity_get_controller($this->entityType)->buildContent($this, $view_mode, $langcode); return entity_get_controller($this->entityType)->buildContent($this, $view_mode, $langcode);
} }
/** /**
* Gets the raw, translated value of a property or field. * {@inheritdoc}
*
* 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.
*/ */
public function getTranslation($property, $langcode = NULL) { public function getTranslation($property, $langcode = NULL) {
$all_info = entity_get_all_property_info($this->entityType); $all_info = entity_get_all_property_info($this->entityType);
@ -296,11 +385,7 @@ class Entity {
} }
/** /**
* Checks whether the entity is the default revision. * {@inheritdoc}
*
* @return Boolean
*
* @see entity_revision_is_default()
*/ */
public function isDefaultRevision() { public function isDefaultRevision() {
if (!empty($this->entityInfo['entity keys']['revision'])) { if (!empty($this->entityInfo['entity keys']['revision'])) {

View File

@ -69,7 +69,7 @@ function entity_property_info_defaults() {
* (optiona) The entity type to return properties for. * (optiona) The entity type to return properties for.
* *
* @return * @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. * properties are returned.
*/ */
function entity_get_all_property_info($entity_type = NULL) { function entity_get_all_property_info($entity_type = NULL) {

View File

@ -127,8 +127,8 @@ class EntityDefaultUIController {
* Use per bundle form ids if possible, such that easy per bundle alterations * Use per bundle form ids if possible, such that easy per bundle alterations
* are supported too. * are supported too.
* *
* Note that for performance reasons, this method is only invoked for forms, * 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 * which receive the entity_type as first argument. Thus any forms added must
* follow that pattern. * follow that pattern.
* *
* @see entity_forms() * @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) { 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 // 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); 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) { function theme_entity_ui_overview_item($variables) {
$output = $variables['url'] ? l($variables['label'], $variables['url']['path'], $variables['url']['options']) : check_plain($variables['label']); $output = $variables['url'] ? l($variables['label'], $variables['url']['path'], $variables['url']['options']) : check_plain($variables['label']);
if ($variables['name']) { 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; return $output;
} }

View File

@ -119,7 +119,11 @@ abstract class EntityMetadataWrapper {
*/ */
public function set($value) { public function set($value) {
if (!$this->validate($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->clear();
$this->data = $value; $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; 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. * Prepare for serializiation.
*/ */
@ -734,7 +753,11 @@ class EntityDrupalWrapper extends EntityStructureWrapper {
*/ */
public function set($value) { public function set($value) {
if (!$this->validate($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) { if ($this->info['type'] == 'entity' && $value === $this) {
// Nothing to do. // Nothing to do.
@ -821,7 +844,12 @@ class EntityDrupalWrapper extends EntityStructureWrapper {
} }
else { else {
// This is not a property, so fallback on entity access. // 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. * Prepare for serializiation.
*/ */
@ -1067,7 +1116,7 @@ class EntityListWrapper extends EntityMetadataWrapper implements IteratorAggrega
*/ */
public function getIterator() { public function getIterator() {
// In case there is no data available, just iterate over the first item. // 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));
} }
/** /**

View File

@ -29,7 +29,7 @@ function entity_metadata_book_get_properties($node, array $options, $name, $enti
case 'book_ancestors': case 'book_ancestors':
$ancestors = array(); $ancestors = array();
while (!empty($node->book['plid'])) { while (!empty($node->book['plid']) && $node->book['plid'] != -1) {
$link = book_link_load($node->book['plid']); $link = book_link_load($node->book['plid']);
array_unshift($ancestors, $link['nid']); array_unshift($ancestors, $link['nid']);
$node = node_load($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) { function entity_metadata_no_hook_node_access($op, $node = NULL, $account = NULL) {
// First deal with the case where a $node is provided. // First deal with the case where a $node is provided.
if (isset($node)) { 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)) { 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 { else {
throw new EntityMalformedException('Permission to create a node was requested but no node type was given.'); 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. * Access callback for the taxonomy entities.
*/ */
function entity_metadata_taxonomy_access($op, $entity = NULL, $account = NULL, $entity_type = NULL) { function entity_metadata_taxonomy_access($op, $entity = NULL, $account = NULL, $entity_type = NULL) {
if ($entity_type == 'taxonomy_vocabulary') { // If user has administer taxonomy permission then no further checks.
return user_access('administer taxonomy', $account); if (user_access('administer taxonomy', $account)) {
}
if (isset($entity) && $op == 'update' && !isset($account) && taxonomy_term_edit_access($entity)) {
return TRUE; return TRUE;
} }
if (user_access('administer taxonomy', $account) || user_access('access content', $account) && $op == 'view') { switch ($op) {
return TRUE; 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; return FALSE;
} }

View File

@ -163,4 +163,10 @@ function entity_metadata_node_entity_property_info_alter(&$info) {
'auto creation' => 'entity_property_create_array', 'auto creation' => 'entity_property_create_array',
'field' => TRUE, '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>';
}
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -605,6 +605,28 @@ class EntityDefaultViewsController {
); );
break; 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': case 'uri':
$return += $description + array( $return += $description + array(
'field' => array( 'field' => array(

View File

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

View File

@ -40,7 +40,7 @@ class entity_views_handler_area_entity extends views_handler_area {
$form['entity_id'] = array( $form['entity_id'] = array(
'#type' => 'textfield', '#type' => 'textfield',
'#title' => t('Entity id'), '#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'], '#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. * Render an entity using the view mode.
*/ */
public function render_entity($entity_type, $entity_id, $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)) { if (!empty($entity_type) && !empty($entity_id) && !empty($view_mode)) {
$entity = entity_load_single($entity_type, $entity_id); $entity = entity_load_single($entity_type, $entity_id);
if (!empty($this->options['bypass_access']) || entity_access('view', $entity_type, $entity)) { 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 ''; 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;
}
} }

View File

@ -88,6 +88,9 @@ class entity_views_plugin_row_entity_view extends views_plugin_row {
public function render($values) { public function render($values) {
if ($entity = $this->get_value($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)]; $render = $this->rendered_content[entity_id($this->entity_type, $entity)];
return drupal_render($render); return drupal_render($render);
} }