1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
|
<?php
/**
* @file
* Entity API for handling entities like nodes or users.
*/
/**
* Implements hook_help().
*/
function entity_help($path, $arg) {
switch ($path) {
case 'admin/help#entity':
$output = '';
$output .= '<h3>' . t('About') . '</h3>';
$output .= '<p>' . t('The Entity module provides an API for managing entities like nodes and users, i.e. an API for loading and identifying entities. For more information, see the online handbook entry for <a href="!url">Entity module</a>', array('!url' => 'http://drupal.org/handbook/modules/entity')) . '</p>';
return $output;
}
}
/**
* Implements hook_modules_preenable().
*/
function entity_modules_preenable() {
entity_info_cache_clear();
}
/**
* Implements hook_modules_disabled().
*/
function entity_modules_disabled() {
entity_info_cache_clear();
}
/**
* Gets the entity info array of an entity type.
*
* @see hook_entity_info()
* @see hook_entity_info_alter()
*
* @param $entity_type
* The entity type, e.g. node, for which the info shall be returned, or NULL
* to return an array with info about all types.
*/
function entity_get_info($entity_type = NULL) {
global $language;
// Use the advanced drupal_static() pattern, since this is called very often.
static $drupal_static_fast;
if (!isset($drupal_static_fast)) {
$drupal_static_fast['entity_info'] = &drupal_static(__FUNCTION__);
}
$entity_info = &$drupal_static_fast['entity_info'];
// hook_entity_info() includes translated strings, so each language is cached
// separately.
$langcode = $language->language;
if (empty($entity_info)) {
if ($cache = cache()->get("entity_info:$langcode")) {
$entity_info = $cache->data;
}
else {
$entity_info = module_invoke_all('entity_info');
// Merge in default values.
foreach ($entity_info as $name => $data) {
$entity_info[$name] += array(
'fieldable' => FALSE,
'controller class' => 'DrupalDefaultEntityController',
'static cache' => TRUE,
'field cache' => TRUE,
'load hook' => $name . '_load',
'bundles' => array(),
'view modes' => array(),
'entity keys' => array(),
'translation' => array(),
);
$entity_info[$name]['entity keys'] += array(
'revision' => '',
'bundle' => '',
);
foreach ($entity_info[$name]['view modes'] as $view_mode => $view_mode_info) {
$entity_info[$name]['view modes'][$view_mode] += array(
'custom settings' => FALSE,
);
}
// If no bundle key is provided, assume a single bundle, named after
// the entity type.
if (empty($entity_info[$name]['entity keys']['bundle']) && empty($entity_info[$name]['bundles'])) {
$entity_info[$name]['bundles'] = array($name => array('label' => $entity_info[$name]['label']));
}
// Prepare entity schema fields SQL info for
// DrupalEntityControllerInterface::buildQuery().
if (isset($entity_info[$name]['base table'])) {
$entity_info[$name]['schema_fields_sql']['base table'] = drupal_schema_fields_sql($entity_info[$name]['base table']);
if (isset($entity_info[$name]['revision table'])) {
$entity_info[$name]['schema_fields_sql']['revision table'] = drupal_schema_fields_sql($entity_info[$name]['revision table']);
}
}
}
// Let other modules alter the entity info.
drupal_alter('entity_info', $entity_info);
cache()->set("entity_info:$langcode", $entity_info);
}
}
if (empty($entity_type)) {
return $entity_info;
}
elseif (isset($entity_info[$entity_type])) {
return $entity_info[$entity_type];
}
}
/**
* Resets the cached information about entity types.
*/
function entity_info_cache_clear() {
drupal_static_reset('entity_get_info');
// Clear all languages.
cache()->deletePrefix('entity_info:');
}
/**
* Helper function to extract id, vid, and bundle name from an entity.
*
* @param $entity_type
* The entity type; e.g. 'node' or 'user'.
* @param $entity
* The entity from which to extract values.
* @return
* A numerically indexed array (not a hash table) containing these
* elements:
* 0: primary id of the entity
* 1: revision id of the entity, or NULL if $entity_type is not versioned
* 2: bundle name of the entity
*/
function entity_extract_ids($entity_type, $entity) {
$info = entity_get_info($entity_type);
// Objects being created might not have id/vid yet.
$id = isset($entity->{$info['entity keys']['id']}) ? $entity->{$info['entity keys']['id']} : NULL;
$vid = ($info['entity keys']['revision'] && isset($entity->{$info['entity keys']['revision']})) ? $entity->{$info['entity keys']['revision']} : NULL;
if (!empty($info['entity keys']['bundle'])) {
// Explicitly fail for malformed entities missing the bundle property.
if (!isset($entity->{$info['entity keys']['bundle']}) || $entity->{$info['entity keys']['bundle']} === '') {
throw new EntityMalformedException(t('Missing bundle property on entity of type @entity_type.', array('@entity_type' => $entity_type)));
}
$bundle = $entity->{$info['entity keys']['bundle']};
}
else {
// The entity type provides no bundle key: assume a single bundle, named
// after the entity type.
$bundle = $entity_type;
}
return array($id, $vid, $bundle);
}
/**
* Helper function to assemble an object structure with initial ids.
*
* This function can be seen as reciprocal to entity_extract_ids().
*
* @param $entity_type
* The entity type; e.g. 'node' or 'user'.
* @param $ids
* A numerically indexed array, as returned by entity_extract_ids(),
* containing these elements:
* 0: primary id of the entity
* 1: revision id of the entity, or NULL if $entity_type is not versioned
* 2: bundle name of the entity, or NULL if $entity_type has no bundles
*
* @return
* An entity structure, initialized with the ids provided.
*/
function entity_create_stub_entity($entity_type, $ids) {
$entity = new stdClass();
$info = entity_get_info($entity_type);
$entity->{$info['entity keys']['id']} = $ids[0];
if (!empty($info['entity keys']['revision']) && isset($ids[1])) {
$entity->{$info['entity keys']['revision']} = $ids[1];
}
if (!empty($info['entity keys']['bundle']) && isset($ids[2])) {
$entity->{$info['entity keys']['bundle']} = $ids[2];
}
return $entity;
}
/**
* Loads entities from the database.
*
* This function should be used whenever you need to load more than one entity
* from the database. The entities are loaded into memory and will not require
* database access if loaded again during the same page request.
*
* The actual loading is done through a class that has to implement the
* DrupalEntityControllerInterface interface. By default,
* DrupalDefaultEntityController is used. Entity types can specify that a
* different class should be used by setting the 'controller class' key in
* hook_entity_info(). These classes can either implement the
* DrupalEntityControllerInterface interface, or, most commonly, extend the
* DrupalDefaultEntityController class. See node_entity_info() and the
* NodeController in node.module as an example.
*
* @see hook_entity_info()
* @see DrupalEntityControllerInterface
* @see DrupalDefaultEntityController
* @see EntityFieldQuery
*
* @param $entity_type
* The entity type to load, e.g. node or user.
* @param $ids
* An array of entity IDs, or FALSE to load all entities.
* @param $conditions
* (deprecated) An associative array of conditions on the base table, where
* the keys are the database fields and the values are the values those
* fields must have. Instead, it is preferable to use EntityFieldQuery to
* retrieve a list of entity IDs loadable by this function.
* @param $reset
* Whether to reset the internal cache for the requested entity type.
*
* @return
* An array of entity objects indexed by their ids.
*
* @todo Remove $conditions in Drupal 8.
*/
function entity_load($entity_type, $ids = FALSE, $conditions = array(), $reset = FALSE) {
if ($reset) {
entity_get_controller($entity_type)->resetCache();
}
return entity_get_controller($entity_type)->load($ids, $conditions);
}
/**
* Loads the unchanged, i.e. not modified, entity from the database.
*
* Unlike entity_load() this function ensures the entity is directly loaded from
* the database, thus bypassing any static cache. In particular, this function
* is useful to determine changes by comparing the entity being saved to the
* stored entity.
*
* @param $entity_type
* The entity type to load, e.g. node or user.
* @param $id
* The id of the entity to load.
*
* @return
* The unchanged entity, or FALSE if the entity cannot be loaded.
*/
function entity_load_unchanged($entity_type, $id) {
entity_get_controller($entity_type)->resetCache(array($id));
$result = entity_get_controller($entity_type)->load(array($id));
return reset($result);
}
/**
* Gets the entity controller class for an entity type.
*/
function entity_get_controller($entity_type) {
$controllers = &drupal_static(__FUNCTION__, array());
if (!isset($controllers[$entity_type])) {
$type_info = entity_get_info($entity_type);
$class = $type_info['controller class'];
$controllers[$entity_type] = new $class($entity_type);
}
return $controllers[$entity_type];
}
/**
* Invokes hook_entity_prepare_view().
*
* If adding a new entity similar to nodes, comments or users, you should
* invoke this function during the ENTITY_build_content() or
* ENTITY_view_multiple() phases of rendering to allow other modules to alter
* the objects during this phase. This is needed for situations where
* information needs to be loaded outside of ENTITY_load() - particularly
* when loading entities into one another - i.e. a user object into a node, due
* to the potential for unwanted side-effects such as caching and infinite
* recursion. By convention, entity_prepare_view() is called after
* field_attach_prepare_view() to allow entity level hooks to act on content
* loaded by field API.
*
* @see hook_entity_prepare_view()
*
* @param $entity_type
* The type of entity, i.e. 'node', 'user'.
* @param $entities
* The entity objects which are being prepared for view, keyed by object ID.
*/
function entity_prepare_view($entity_type, $entities) {
// To ensure hooks are only run once per entity, check for an
// entity_view_prepared flag and only process items without it.
// @todo: resolve this more generally for both entity and field level hooks.
$prepare = array();
foreach ($entities as $id => $entity) {
if (empty($entity->entity_view_prepared)) {
// Add this entity to the items to be prepared.
$prepare[$id] = $entity;
// Mark this item as prepared.
$entity->entity_view_prepared = TRUE;
}
}
if (!empty($prepare)) {
module_invoke_all('entity_prepare_view', $prepare, $entity_type);
}
}
/**
* Returns the uri elements of an entity.
*
* @param $entity_type
* The entity type; e.g. 'node' or 'user'.
* @param $entity
* The entity for which to generate a path.
*
* @return
* An array containing the 'path' and 'options' keys used to build the uri of
* the entity, and matching the signature of url(). NULL if the entity has no
* uri of its own.
*/
function entity_uri($entity_type, $entity) {
$info = entity_get_info($entity_type);
list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity);
// A bundle-specific callback takes precedence over the generic one for the
// entity type.
if (isset($info['bundles'][$bundle]['uri callback'])) {
$uri_callback = $info['bundles'][$bundle]['uri callback'];
}
elseif (isset($info['uri callback'])) {
$uri_callback = $info['uri callback'];
}
else {
return NULL;
}
// Invoke the callback to get the URI. If there is no callback, return NULL.
if (isset($uri_callback) && function_exists($uri_callback)) {
$uri = $uri_callback($entity);
// Pass the entity data to url() so that alter functions do not need to
// lookup this entity again.
$uri['options']['entity_type'] = $entity_type;
$uri['options']['entity'] = $entity;
return $uri;
}
}
/**
* Returns the label of an entity.
*
* See the 'label callback' component of the hook_entity_info() return value
* for more information.
*
* @param $entity_type
* The entity type; e.g., 'node' or 'user'.
* @param $entity
* The entity for which to generate the label.
*
* @return
* The entity label, or FALSE if not found.
*/
function entity_label($entity_type, $entity) {
$label = FALSE;
$info = entity_get_info($entity_type);
if (isset($info['label callback']) && function_exists($info['label callback'])) {
$label = $info['label callback']($entity_type, $entity);
}
elseif (!empty($info['entity keys']['label']) && isset($entity->{$info['entity keys']['label']})) {
$label = $entity->{$info['entity keys']['label']};
}
return $label;
}
/**
* Helper function for attaching field API validation to entity forms.
*/
function entity_form_field_validate($entity_type, $form, &$form_state) {
// All field attach API functions act on an entity object, but during form
// validation, we don't have one. $form_state contains the entity as it was
// prior to processing the current form submission, and we must not update it
// until we have fully validated the submitted input. Therefore, for
// validation, act on a pseudo entity created out of the form values.
$pseudo_entity = (object) $form_state['values'];
field_attach_form_validate($entity_type, $pseudo_entity, $form, $form_state);
}
/**
* Helper function for copying submitted values to entity properties for simple entity forms.
*
* During the submission handling of an entity form's "Save", "Preview", and
* possibly other buttons, the form state's entity needs to be updated with the
* submitted form values. Each entity form implements its own builder function
* for doing this, appropriate for the particular entity and form, whereas
* modules may specify additional builder functions in $form['#entity_builders']
* for copying the form values of added form elements to entity properties.
* Many of the main entity builder functions can call this helper function to
* re-use its logic of copying $form_state['values'][PROPERTY] values to
* $entity->PROPERTY for all entries in $form_state['values'] that are not field
* data, and calling field_attach_submit() to copy field data. Apart from that
* this helper invokes any additional builder functions that have been specified
* in $form['#entity_builders'].
*
* For some entity forms (e.g., forms with complex non-field data and forms that
* simultaneously edit multiple entities), this behavior may be inappropriate,
* so the builder function for such forms needs to implement the required
* functionality instead of calling this function.
*/
function entity_form_submit_build_entity($entity_type, $entity, $form, &$form_state) {
$info = entity_get_info($entity_type);
list(, , $bundle) = entity_extract_ids($entity_type, $entity);
// Copy top-level form values that are not for fields to entity properties,
// without changing existing entity properties that are not being edited by
// this form. Copying field values must be done using field_attach_submit().
$values_excluding_fields = $info['fieldable'] ? array_diff_key($form_state['values'], field_info_instances($entity_type, $bundle)) : $form_state['values'];
foreach ($values_excluding_fields as $key => $value) {
$entity->$key = $value;
}
// Invoke all specified builders for copying form values to entity properties.
if (isset($form['#entity_builders'])) {
foreach ($form['#entity_builders'] as $function) {
$function($entity_type, $entity, $form, $form_state);
}
}
// Copy field values to the entity.
if ($info['fieldable']) {
field_attach_submit($entity_type, $entity, $form, $form_state);
}
}
/**
* Exception thrown when a malformed entity is passed.
*/
class EntityMalformedException extends Exception { }
|