Commit | Line | Data |
---|---|---|
7f254ad8 AE |
1 | <?php |
2 | ||
3 | /** | |
4 | * @file | |
5 | * Module file for the entity API. | |
6 | */ | |
7 | ||
8 | module_load_include('inc', 'entity', 'modules/callbacks'); | |
9 | module_load_include('inc', 'entity', 'includes/entity.property'); | |
10 | ||
11 | ||
12 | /** | |
13 | * Defines status codes used for exportable entities. | |
14 | */ | |
15 | ||
16 | /** | |
17 | * A bit flag used to let us know if an entity is in the database. | |
18 | */ | |
19 | ||
20 | ||
21 | /** | |
22 | * A bit flag used to let us know if an entity has been customly defined. | |
23 | */ | |
24 | define('ENTITY_CUSTOM', 0x01); | |
25 | ||
26 | /** | |
27 | * Deprecated, but still here for backward compatibility. | |
28 | */ | |
29 | define('ENTITY_IN_DB', 0x01); | |
30 | ||
31 | /** | |
32 | * A bit flag used to let us know if an entity is a 'default' in code. | |
33 | */ | |
34 | define('ENTITY_IN_CODE', 0x02); | |
35 | ||
36 | /** | |
37 | * A bit flag used to mark entities as overridden, e.g. they were originally | |
38 | * definded in code and are saved now in the database. Same as | |
39 | * (ENTITY_CUSTOM | ENTITY_IN_CODE). | |
40 | */ | |
41 | define('ENTITY_OVERRIDDEN', 0x03); | |
42 | ||
43 | /** | |
44 | * A bit flag used to mark entities as fixed, thus not changeable for any | |
45 | * user. | |
46 | */ | |
47 | define('ENTITY_FIXED', 0x04 | 0x02); | |
48 | ||
49 | ||
50 | ||
51 | /** | |
52 | * Determines whether for the given entity type a given operation is available. | |
53 | * | |
54 | * @param $entity_type | |
55 | * The type of the entity. | |
56 | * @param $op | |
57 | * One of 'create', 'view', 'save', 'delete', 'revision delete', 'access' or | |
58 | * 'form'. | |
59 | * | |
60 | * @return boolean | |
61 | * Whether the entity type supports the given operation. | |
62 | */ | |
63 | function entity_type_supports($entity_type, $op) { | |
64 | $info = entity_get_info($entity_type); | |
65 | $keys = array( | |
66 | 'view' => 'view callback', | |
67 | 'create' => 'creation callback', | |
68 | 'delete' => 'deletion callback', | |
69 | 'revision delete' => 'revision deletion callback', | |
70 | 'save' => 'save callback', | |
71 | 'access' => 'access callback', | |
72 | 'form' => 'form callback' | |
73 | ); | |
74 | if (isset($info[$keys[$op]])) { | |
75 | return TRUE; | |
76 | } | |
77 | if ($op == 'revision delete') { | |
78 | return in_array('EntityAPIControllerInterface', class_implements($info['controller class'])); | |
79 | } | |
80 | if ($op == 'form') { | |
81 | return (bool) entity_ui_controller($entity_type); | |
82 | } | |
83 | if ($op != 'access') { | |
84 | return in_array('EntityAPIControllerInterface', class_implements($info['controller class'])); | |
85 | } | |
86 | return FALSE; | |
87 | } | |
88 | ||
89 | /** | |
90 | * Menu loader function: load an entity from its path. | |
91 | * | |
92 | * This can be used to load entities of all types in menu paths: | |
93 | * | |
94 | * @code | |
95 | * $items['myentity/%entity_object'] = array( | |
96 | * 'load arguments' => array('myentity'), | |
97 | * 'title' => ..., | |
98 | * 'page callback' => ..., | |
99 | * 'page arguments' => array(...), | |
100 | * 'access arguments' => array(...), | |
101 | * ); | |
102 | * @endcode | |
103 | * | |
104 | * @param $entity_id | |
105 | * The ID of the entity to load, passed by the menu URL. | |
106 | * @param $entity_type | |
107 | * The type of the entity to load. | |
108 | * @return | |
109 | * A fully loaded entity object, or FALSE in case of error. | |
110 | */ | |
111 | function entity_object_load($entity_id, $entity_type) { | |
112 | $entities = entity_load($entity_type, array($entity_id)); | |
113 | return reset($entities); | |
114 | } | |
115 | ||
116 | /** | |
117 | * Page callback to show links to add an entity of a specific bundle. | |
118 | * | |
119 | * Entity modules that provide a further description to their bundles may wish | |
120 | * to implement their own version of this to show these. | |
121 | * | |
122 | * @param $entity_type | |
123 | * The type of the entity. | |
124 | */ | |
125 | function entity_ui_bundle_add_page($entity_type) { | |
126 | // Set the title, as we're a MENU_LOCAL_ACTION and hence just get tab titles. | |
127 | module_load_include('inc', 'entity', 'includes/entity.ui'); | |
128 | drupal_set_title(entity_ui_get_action_title('add', $entity_type)); | |
129 | ||
130 | // Get entity info for our bundles. | |
131 | $info = entity_get_info($entity_type); | |
132 | $items = array(); | |
133 | foreach ($info['bundles'] as $bundle_name => $bundle_info) { | |
134 | // Create an empty entity with just the bundle set to check for access. | |
135 | $dummy_entity = entity_create($entity_type, array( | |
136 | $info['entity keys']['bundle'] => $bundle_name, | |
137 | )); | |
138 | // If modules use a uid, they can default to the current-user | |
139 | // in their create() method on the storage controller. | |
140 | if (entity_access('create', $entity_type, $dummy_entity, $account = NULL)) { | |
141 | $add_path = $info['admin ui']['path'] . '/add/' . $bundle_name; | |
142 | $items[] = l(t('Add @label', array('@label' => $bundle_info['label'])), $add_path); | |
143 | } | |
144 | } | |
145 | return theme('item_list', array('items' => $items)); | |
146 | } | |
147 | ||
148 | /** | |
149 | * Page callback to add an entity of a specific bundle. | |
150 | * | |
151 | * @param $entity_type | |
152 | * The type of the entity. | |
153 | * @param $bundle_name | |
154 | * The bundle machine name. | |
155 | */ | |
156 | function entity_ui_get_bundle_add_form($entity_type, $bundle_name) { | |
157 | $info = entity_get_info($entity_type); | |
158 | $bundle_key = $info['entity keys']['bundle']; | |
159 | ||
160 | // Make a stub entity of the right bundle to pass to the entity_ui_get_form(). | |
161 | $values = array( | |
162 | $bundle_key => $bundle_name, | |
163 | ); | |
164 | $entity = entity_create($entity_type, $values); | |
165 | ||
166 | return entity_ui_get_form($entity_type, $entity, 'add'); | |
167 | } | |
168 | ||
169 | /** | |
170 | * Page callback for viewing an entity. | |
171 | * | |
172 | * @param Entity $entity | |
173 | * The entity to be rendered. | |
174 | * | |
175 | * @return array | |
176 | * A renderable array of the entity in full view mode. | |
177 | */ | |
178 | function entity_ui_entity_page_view($entity) { | |
179 | module_load_include('inc', 'entity', 'includes/entity.ui'); | |
180 | return $entity->view('full', NULL, TRUE); | |
181 | } | |
182 | ||
183 | /** | |
184 | * Gets the page title for the passed operation. | |
185 | */ | |
186 | function entity_ui_get_page_title($op, $entity_type, $entity = NULL) { | |
187 | if (isset($entity)) { | |
188 | module_load_include('inc', 'entity', 'includes/entity.ui'); | |
189 | $label = entity_label($entity_type, $entity); | |
190 | list(, , $bundle) = entity_extract_ids($entity_type, $entity); | |
191 | } | |
192 | else { | |
193 | $info = entity_get_info($entity_type); | |
194 | $label = $info['label']; | |
195 | $bundle = NULL; | |
196 | } | |
197 | ||
198 | switch ($op) { | |
199 | case 'view': | |
200 | return $label; | |
201 | case 'edit': | |
202 | return t('Edit @label', array('@label' => $label)); | |
203 | case 'clone': | |
204 | return t('Clone @label', array('@label' => $label)); | |
205 | case 'revert': | |
206 | return t('Revert @label', array('@label' => $label)); | |
207 | case 'delete': | |
208 | return t('Delete @label', array('@label' => $label)); | |
209 | case 'export': | |
210 | return t('Export @label', array('@label' => $label)); | |
211 | } | |
212 | ||
213 | return entity_ui_get_action_title($op, $entity_type, $bundle); | |
214 | } | |
215 | ||
216 | /** | |
217 | * A wrapper around entity_load() to load a single entity by name or numeric id. | |
218 | * | |
219 | * @todo: Re-name entity_load() to entity_load_multiple() in d8 core and this | |
220 | * to entity_load(). | |
221 | * | |
222 | * @param $entity_type | |
223 | * The entity type to load, e.g. node or user. | |
224 | * @param $id | |
225 | * The entity id, either the numeric id or the entity name. In case the entity | |
226 | * type has specified a name key, both the numeric id and the name may be | |
227 | * passed. | |
228 | * | |
229 | * @return | |
230 | * The entity object, or FALSE. | |
231 | * | |
232 | * @see entity_load() | |
233 | */ | |
234 | function entity_load_single($entity_type, $id) { | |
235 | $entities = entity_load($entity_type, array($id)); | |
236 | return reset($entities); | |
237 | } | |
238 | ||
239 | /** | |
240 | * A wrapper around entity_load() to return entities keyed by name key if existing. | |
241 | * | |
242 | * @param $entity_type | |
243 | * The entity type to load, e.g. node or user. | |
244 | * @param $names | |
245 | * An array of entity names or ids, or FALSE to load all entities. | |
246 | * @param $conditions | |
247 | * (deprecated) An associative array of conditions on the base table, where | |
248 | * the keys are the database fields and the values are the values those | |
249 | * fields must have. Instead, it is preferable to use EntityFieldQuery to | |
250 | * retrieve a list of entity IDs loadable by this function. | |
251 | * | |
252 | * @return | |
253 | * An array of entity objects indexed by their names (or ids if the entity | |
254 | * type has no name key). | |
255 | * | |
256 | * @see entity_load() | |
257 | */ | |
258 | function entity_load_multiple_by_name($entity_type, $names = FALSE, $conditions = array()) { | |
259 | $entities = entity_load($entity_type, $names, $conditions); | |
260 | $info = entity_get_info($entity_type); | |
261 | if (!isset($info['entity keys']['name'])) { | |
262 | return $entities; | |
263 | } | |
264 | return entity_key_array_by_property($entities, $info['entity keys']['name']); | |
265 | } | |
266 | ||
267 | /** | |
268 | * Permanently save an entity. | |
269 | * | |
270 | * In case of failures, an exception is thrown. | |
271 | * | |
272 | * @param $entity_type | |
273 | * The type of the entity. | |
274 | * @param $entity | |
275 | * The entity to save. | |
276 | * | |
277 | * @return | |
278 | * For entity types provided by the CRUD API, SAVED_NEW or SAVED_UPDATED is | |
279 | * returned depending on the operation performed. If there is no information | |
280 | * how to save the entity, FALSE is returned. | |
281 | * | |
282 | * @see entity_type_supports() | |
283 | */ | |
284 | function entity_save($entity_type, $entity) { | |
285 | $info = entity_get_info($entity_type); | |
286 | if (method_exists($entity, 'save')) { | |
287 | return $entity->save(); | |
288 | } | |
289 | elseif (isset($info['save callback'])) { | |
290 | $info['save callback']($entity); | |
291 | } | |
292 | elseif (in_array('EntityAPIControllerInterface', class_implements($info['controller class']))) { | |
293 | return entity_get_controller($entity_type)->save($entity); | |
294 | } | |
295 | else { | |
296 | return FALSE; | |
297 | } | |
298 | } | |
299 | ||
300 | /** | |
301 | * Permanently delete the given entity. | |
302 | * | |
303 | * In case of failures, an exception is thrown. | |
304 | * | |
305 | * @param $entity_type | |
306 | * The type of the entity. | |
307 | * @param $id | |
308 | * The uniform identifier of the entity to delete. | |
309 | * | |
310 | * @return | |
311 | * FALSE, if there were no information how to delete the entity. | |
312 | * | |
313 | * @see entity_type_supports() | |
314 | */ | |
315 | function entity_delete($entity_type, $id) { | |
316 | return entity_delete_multiple($entity_type, array($id)); | |
317 | } | |
318 | ||
319 | /** | |
320 | * Permanently delete multiple entities. | |
321 | * | |
322 | * @param $entity_type | |
323 | * The type of the entity. | |
324 | * @param $ids | |
325 | * An array of entity ids of the entities to delete. In case the entity makes | |
326 | * use of a name key, both the names or numeric ids may be passed. | |
327 | * @return | |
328 | * FALSE if the given entity type isn't compatible to the CRUD API. | |
329 | */ | |
330 | function entity_delete_multiple($entity_type, $ids) { | |
331 | $info = entity_get_info($entity_type); | |
332 | if (isset($info['deletion callback'])) { | |
333 | foreach ($ids as $id) { | |
334 | $info['deletion callback']($id); | |
335 | } | |
336 | } | |
337 | elseif (in_array('EntityAPIControllerInterface', class_implements($info['controller class']))) { | |
338 | entity_get_controller($entity_type)->delete($ids); | |
339 | } | |
340 | else { | |
341 | return FALSE; | |
342 | } | |
343 | } | |
344 | ||
345 | /** | |
346 | * Loads an entity revision. | |
347 | * | |
348 | * @param $entity_type | |
349 | * The type of the entity. | |
350 | * @param $revision_id | |
351 | * The id of the revision to load. | |
352 | * | |
353 | * @return | |
354 | * The entity object, or FALSE if there is no entity with the given revision | |
355 | * id. | |
356 | */ | |
357 | function entity_revision_load($entity_type, $revision_id) { | |
358 | $info = entity_get_info($entity_type); | |
359 | if (!empty($info['entity keys']['revision'])) { | |
360 | $entity_revisions = entity_load($entity_type, FALSE, array($info['entity keys']['revision'] => $revision_id)); | |
361 | return reset($entity_revisions); | |
362 | } | |
363 | return FALSE; | |
364 | } | |
365 | ||
366 | /** | |
367 | * Deletes an entity revision. | |
368 | * | |
369 | * @param $entity_type | |
370 | * The type of the entity. | |
371 | * @param $revision_id | |
372 | * The revision ID to delete. | |
373 | * | |
374 | * @return | |
375 | * TRUE if the entity revision could be deleted, FALSE otherwise. | |
376 | */ | |
377 | function entity_revision_delete($entity_type, $revision_id) { | |
378 | $info = entity_get_info($entity_type); | |
379 | if (isset($info['revision deletion callback'])) { | |
380 | return $info['revision deletion callback']($revision_id, $entity_type); | |
381 | } | |
382 | elseif (in_array('EntityAPIControllerRevisionableInterface', class_implements($info['controller class']))) { | |
383 | return entity_get_controller($entity_type)->deleteRevision($revision_id); | |
384 | } | |
385 | return FALSE; | |
386 | } | |
387 | ||
388 | /** | |
389 | * Checks whether the given entity is the default revision. | |
390 | * | |
391 | * Note that newly created entities will always be created in default revision, | |
392 | * thus TRUE is returned for not yet saved entities. | |
393 | * | |
394 | * @param $entity_type | |
395 | * The type of the entity. | |
396 | * @param $entity | |
397 | * The entity object to check. | |
398 | * | |
399 | * @return boolean | |
400 | * A boolean indicating whether the entity is in default revision is returned. | |
401 | * If the entity is not revisionable or is new, TRUE is returned. | |
402 | * | |
403 | * @see entity_revision_set_default() | |
404 | */ | |
405 | function entity_revision_is_default($entity_type, $entity) { | |
406 | $info = entity_get_info($entity_type); | |
407 | if (empty($info['entity keys']['revision'])) { | |
408 | return TRUE; | |
409 | } | |
410 | // Newly created entities will always be created in default revision. | |
411 | if (!empty($entity->is_new) || empty($entity->{$info['entity keys']['id']})) { | |
412 | return TRUE; | |
413 | } | |
414 | if (in_array('EntityAPIControllerRevisionableInterface', class_implements($info['controller class']))) { | |
415 | $key = !empty($info['entity keys']['default revision']) ? $info['entity keys']['default revision'] : 'default_revision'; | |
416 | return !empty($entity->$key); | |
417 | } | |
418 | else { | |
419 | // Else, just load the default entity and compare the ID. Usually, the | |
420 | // entity should be already statically cached anyway. | |
421 | $default = entity_load_single($entity_type, $entity->{$info['entity keys']['id']}); | |
422 | return $default->{$info['entity keys']['revision']} == $entity->{$info['entity keys']['revision']}; | |
423 | } | |
424 | } | |
425 | ||
426 | /** | |
427 | * Sets a given entity revision as default revision. | |
428 | * | |
429 | * Note that the default revision flag will only be supported by entity types | |
430 | * based upon the EntityAPIController, i.e. implementing the | |
431 | * EntityAPIControllerRevisionableInterface. | |
432 | * | |
433 | * @param $entity_type | |
434 | * The type of the entity. | |
435 | * @param $entity | |
436 | * The entity revision to update. | |
437 | * | |
438 | * @see entity_revision_is_default() | |
439 | */ | |
440 | function entity_revision_set_default($entity_type, $entity) { | |
441 | $info = entity_get_info($entity_type); | |
442 | if (!empty($info['entity keys']['revision'])) { | |
443 | $key = !empty($info['entity keys']['default revision']) ? $info['entity keys']['default revision'] : 'default_revision'; | |
444 | $entity->$key = TRUE; | |
445 | } | |
446 | } | |
447 | ||
448 | /** | |
449 | * Create a new entity object. | |
450 | * | |
451 | * @param $entity_type | |
452 | * The type of the entity. | |
453 | * @param $values | |
454 | * An array of values to set, keyed by property name. If the entity type has | |
455 | * bundles the bundle key has to be specified. | |
456 | * @return | |
457 | * A new instance of the entity type or FALSE if there is no information for | |
458 | * the given entity type. | |
459 | * | |
460 | * @see entity_type_supports() | |
461 | */ | |
462 | function entity_create($entity_type, array $values) { | |
463 | $info = entity_get_info($entity_type); | |
464 | if (isset($info['creation callback'])) { | |
465 | return $info['creation callback']($values, $entity_type); | |
466 | } | |
467 | elseif (in_array('EntityAPIControllerInterface', class_implements($info['controller class']))) { | |
468 | return entity_get_controller($entity_type)->create($values); | |
469 | } | |
470 | return FALSE; | |
471 | } | |
472 | ||
473 | /** | |
474 | * Exports an entity. | |
475 | * | |
476 | * Note: Currently, this only works for entity types provided with the entity | |
477 | * CRUD API. | |
478 | * | |
479 | * @param $entity_type | |
480 | * The type of the entity. | |
481 | * @param $entity | |
482 | * The entity to export. | |
483 | * @param $prefix | |
484 | * An optional prefix for each line. | |
485 | * @return | |
486 | * The exported entity as serialized string. The format is determined by the | |
487 | * respective entity controller, e.g. it is JSON for the EntityAPIController. | |
488 | * The output is suitable for entity_import(). | |
489 | */ | |
490 | function entity_export($entity_type, $entity, $prefix = '') { | |
491 | if (method_exists($entity, 'export')) { | |
492 | return $entity->export($prefix); | |
493 | } | |
494 | $info = entity_get_info($entity_type); | |
495 | if (in_array('EntityAPIControllerInterface', class_implements($info['controller class']))) { | |
496 | return entity_get_controller($entity_type)->export($entity, $prefix); | |
497 | } | |
498 | } | |
499 | ||
500 | /** | |
501 | * Imports an entity. | |
502 | * | |
503 | * Note: Currently, this only works for entity types provided with the entity | |
504 | * CRUD API. | |
505 | * | |
506 | * @param $entity_type | |
507 | * The type of the entity. | |
508 | * @param string $export | |
509 | * The string containing the serialized entity as produced by | |
510 | * entity_export(). | |
511 | * @return | |
512 | * The imported entity object not yet saved. | |
513 | */ | |
514 | function entity_import($entity_type, $export) { | |
515 | $info = entity_get_info($entity_type); | |
516 | if (in_array('EntityAPIControllerInterface', class_implements($info['controller class']))) { | |
517 | return entity_get_controller($entity_type)->import($export); | |
518 | } | |
519 | } | |
520 | ||
521 | /** | |
522 | * Checks whether an entity type is fieldable. | |
523 | * | |
524 | * @param $entity_type | |
525 | * The type of the entity. | |
526 | * | |
527 | * @return | |
528 | * TRUE if the entity type is fieldable, FALSE otherwise. | |
529 | */ | |
530 | function entity_type_is_fieldable($entity_type) { | |
531 | $info = entity_get_info($entity_type); | |
532 | return !empty($info['fieldable']); | |
533 | } | |
534 | ||
535 | /** | |
536 | * Builds a structured array representing the entity's content. | |
537 | * | |
538 | * The content built for the entity will vary depending on the $view_mode | |
539 | * parameter. | |
540 | * | |
541 | * Note: Currently, this only works for entity types provided with the entity | |
542 | * CRUD API. | |
543 | * | |
544 | * @param $entity_type | |
545 | * The type of the entity. | |
546 | * @param $entity | |
547 | * An entity object. | |
548 | * @param $view_mode | |
549 | * A view mode as used by this entity type, e.g. 'full', 'teaser'... | |
550 | * @param $langcode | |
551 | * (optional) A language code to use for rendering. Defaults to the global | |
552 | * content language of the current request. | |
553 | * @return | |
554 | * The renderable array. | |
555 | */ | |
556 | function entity_build_content($entity_type, $entity, $view_mode = 'full', $langcode = NULL) { | |
557 | $info = entity_get_info($entity_type); | |
558 | if (method_exists($entity, 'buildContent')) { | |
559 | return $entity->buildContent($view_mode, $langcode); | |
560 | } | |
561 | elseif (in_array('EntityAPIControllerInterface', class_implements($info['controller class']))) { | |
562 | return entity_get_controller($entity_type)->buildContent($entity, $view_mode, $langcode); | |
563 | } | |
564 | } | |
565 | ||
566 | /** | |
567 | * Returns the entity identifier, i.e. the entities name or numeric id. | |
568 | * | |
569 | * Unlike entity_extract_ids() this function returns the name of the entity | |
570 | * instead of the numeric id, in case the entity type has specified a name key. | |
571 | * | |
572 | * @param $entity_type | |
573 | * The type of the entity. | |
574 | * @param $entity | |
575 | * An entity object. | |
576 | * | |
577 | * @see entity_extract_ids() | |
578 | */ | |
579 | function entity_id($entity_type, $entity) { | |
580 | if (method_exists($entity, 'identifier')) { | |
581 | return $entity->identifier(); | |
582 | } | |
583 | $info = entity_get_info($entity_type); | |
584 | $key = isset($info['entity keys']['name']) ? $info['entity keys']['name'] : $info['entity keys']['id']; | |
585 | return isset($entity->$key) ? $entity->$key : NULL; | |
586 | } | |
587 | ||
588 | /** | |
589 | * Generate an array for rendering the given entities. | |
590 | * | |
591 | * Entities being viewed, are generally expected to be fully-loaded entity | |
592 | * objects, thus have their name or id key set. However, it is possible to | |
593 | * view a single entity without any id, e.g. for generating a preview during | |
594 | * creation. | |
595 | * | |
596 | * @param $entity_type | |
597 | * The type of the entity. | |
598 | * @param $entities | |
599 | * An array of entities to render. | |
600 | * @param $view_mode | |
601 | * A view mode as used by this entity type, e.g. 'full', 'teaser'... | |
602 | * @param $langcode | |
603 | * (optional) A language code to use for rendering. Defaults to the global | |
604 | * content language of the current request. | |
605 | * @param $page | |
606 | * (optional) If set will control if the entity is rendered: if TRUE | |
607 | * the entity will be rendered without its title, so that it can be embeded | |
608 | * in another context. If FALSE the entity will be displayed with its title | |
609 | * in a mode suitable for lists. | |
610 | * If unset, the page mode will be enabled if the current path is the URI | |
611 | * of the entity, as returned by entity_uri(). | |
612 | * This parameter is only supported for entities which controller is a | |
613 | * EntityAPIControllerInterface. | |
614 | * @return | |
615 | * The renderable array, keyed by the entity type and by entity identifiers, | |
616 | * for which the entity name is used if existing - see entity_id(). If there | |
617 | * is no information on how to view an entity, FALSE is returned. | |
618 | */ | |
619 | function entity_view($entity_type, $entities, $view_mode = 'full', $langcode = NULL, $page = NULL) { | |
620 | $info = entity_get_info($entity_type); | |
621 | if (isset($info['view callback'])) { | |
622 | $entities = entity_key_array_by_property($entities, $info['entity keys']['id']); | |
623 | return $info['view callback']($entities, $view_mode, $langcode, $entity_type); | |
624 | } | |
625 | elseif (in_array('EntityAPIControllerInterface', class_implements($info['controller class']))) { | |
626 | return entity_get_controller($entity_type)->view($entities, $view_mode, $langcode, $page); | |
627 | } | |
628 | return FALSE; | |
629 | } | |
630 | ||
631 | /** | |
632 | * Determines whether the given user can perform actions on an entity. | |
633 | * | |
634 | * For create operations, the pattern is to create an entity and then | |
635 | * check if the user has create access. | |
636 | * | |
637 | * @code | |
638 | * $node = entity_create('node', array('type' => 'page')); | |
639 | * $access = entity_access('create', 'node', $node, $account); | |
640 | * @endcode | |
641 | * | |
642 | * @param $op | |
643 | * The operation being performed. One of 'view', 'update', 'create' or | |
644 | * 'delete'. | |
645 | * @param $entity_type | |
646 | * The entity type of the entity to check for. | |
647 | * @param $entity | |
648 | * Optionally an entity to check access for. If no entity is given, it will be | |
649 | * determined whether access is allowed for all entities of the given type. | |
650 | * @param $account | |
651 | * The user to check for. Leave it to NULL to check for the global user. | |
652 | * | |
653 | * @return boolean | |
654 | * Whether access is allowed or not. If the entity type does not specify any | |
655 | * access information, NULL is returned. | |
656 | * | |
657 | * @see entity_type_supports() | |
658 | */ | |
659 | function entity_access($op, $entity_type, $entity = NULL, $account = NULL) { | |
660 | if (($info = entity_get_info()) && isset($info[$entity_type]['access callback'])) { | |
661 | return $info[$entity_type]['access callback']($op, $entity, $account, $entity_type); | |
662 | } | |
663 | } | |
664 | ||
665 | /** | |
666 | * Gets the edit form for any entity. | |
667 | * | |
668 | * This helper makes use of drupal_get_form() and the regular form builder | |
669 | * function of the entity type to retrieve and process the form as usual. | |
670 | * | |
671 | * In order to use this helper to show an entity add form, the new entity object | |
672 | * can be created via entity_create() or entity_property_values_create_entity(). | |
673 | * | |
674 | * @param $entity_type | |
675 | * The type of the entity. | |
676 | * @param $entity | |
677 | * The entity to show the edit form for. | |
678 | * @return | |
679 | * The renderable array of the form. If there is no entity form or missing | |
680 | * metadata, FALSE is returned. | |
681 | * | |
682 | * @see entity_type_supports() | |
683 | */ | |
684 | function entity_form($entity_type, $entity) { | |
685 | $info = entity_get_info($entity_type); | |
686 | if (isset($info['form callback'])) { | |
687 | return $info['form callback']($entity, $entity_type); | |
688 | } | |
689 | // If there is an UI controller, the providing module has to implement the | |
690 | // entity form using entity_ui_get_form(). | |
691 | elseif (entity_ui_controller($entity_type)) { | |
692 | return entity_metadata_form_entity_ui($entity, $entity_type); | |
693 | } | |
694 | return FALSE; | |
695 | } | |
696 | ||
697 | /** | |
698 | * Converts an array of entities to be keyed by the values of a given property. | |
699 | * | |
700 | * @param array $entities | |
701 | * The array of entities to convert. | |
702 | * @param $property | |
703 | * The name of entity property, by which the array should be keyed. To get | |
704 | * reasonable results, the property has to have unique values. | |
705 | * | |
706 | * @return array | |
707 | * The same entities in the same order, but keyed by their $property values. | |
708 | */ | |
709 | function entity_key_array_by_property(array $entities, $property) { | |
710 | $ret = array(); | |
711 | foreach ($entities as $entity) { | |
712 | $key = isset($entity->$property) ? $entity->$property : NULL; | |
713 | $ret[$key] = $entity; | |
714 | } | |
715 | return $ret; | |
716 | } | |
717 | ||
718 | /** | |
719 | * Get the entity info for the entity types provided via the entity CRUD API. | |
720 | * | |
721 | * @return | |
722 | * An array in the same format as entity_get_info(), containing the entities | |
723 | * whose controller class implements the EntityAPIControllerInterface. | |
724 | */ | |
725 | function entity_crud_get_info() { | |
726 | $types = array(); | |
727 | foreach (entity_get_info() as $type => $info) { | |
728 | if (isset($info['controller class']) && in_array('EntityAPIControllerInterface', class_implements($info['controller class']))) { | |
729 | $types[$type] = $info; | |
730 | } | |
731 | } | |
732 | return $types; | |
733 | } | |
734 | ||
735 | /** | |
736 | * Checks if a given entity has a certain exportable status. | |
737 | * | |
738 | * @param $entity_type | |
739 | * The type of the entity. | |
740 | * @param $entity | |
741 | * The entity to check the status on. | |
742 | * @param $status | |
743 | * The constant status like ENTITY_CUSTOM, ENTITY_IN_CODE, ENTITY_OVERRIDDEN | |
744 | * or ENTITY_FIXED. | |
745 | * | |
746 | * @return | |
747 | * TRUE if the entity has the status, FALSE otherwise. | |
748 | */ | |
749 | function entity_has_status($entity_type, $entity, $status) { | |
750 | $info = entity_get_info($entity_type); | |
751 | $status_key = empty($info['entity keys']['status']) ? 'status' : $info['entity keys']['status']; | |
752 | return isset($entity->{$status_key}) && ($entity->{$status_key} & $status) == $status; | |
753 | } | |
754 | ||
755 | /** | |
756 | * Export a variable. Copied from ctools. | |
757 | * | |
758 | * This is a replacement for var_export(), allowing us to more nicely | |
759 | * format exports. It will recurse down into arrays and will try to | |
760 | * properly export bools when it can. | |
761 | */ | |
762 | function entity_var_export($var, $prefix = '') { | |
763 | if (is_array($var)) { | |
764 | if (empty($var)) { | |
765 | $output = 'array()'; | |
766 | } | |
767 | else { | |
768 | $output = "array(\n"; | |
769 | foreach ($var as $key => $value) { | |
770 | $output .= " '$key' => " . entity_var_export($value, ' ') . ",\n"; | |
771 | } | |
772 | $output .= ')'; | |
773 | } | |
774 | } | |
775 | elseif (is_bool($var)) { | |
776 | $output = $var ? 'TRUE' : 'FALSE'; | |
777 | } | |
778 | else { | |
779 | $output = var_export($var, TRUE); | |
780 | } | |
781 | ||
782 | if ($prefix) { | |
783 | $output = str_replace("\n", "\n$prefix", $output); | |
784 | } | |
785 | return $output; | |
786 | } | |
787 | ||
788 | /** | |
789 | * Export a variable in pretty formatted JSON. | |
790 | */ | |
791 | function entity_var_json_export($var, $prefix = '') { | |
792 | if (is_array($var) && $var) { | |
793 | // Defines whether we use a JSON array or object. | |
794 | $use_array = ($var == array_values($var)); | |
795 | $output = $use_array ? "[" : "{"; | |
796 | ||
797 | foreach ($var as $key => $value) { | |
798 | if ($use_array) { | |
799 | $values[] = entity_var_json_export($value, ' '); | |
800 | } | |
801 | else { | |
802 | $values[] = entity_var_json_export((string) $key, ' ') . ' : ' . entity_var_json_export($value, ' '); | |
803 | } | |
804 | } | |
805 | // Use several lines for long content. However for objects with a single | |
806 | // entry keep the key in the first line. | |
807 | if (strlen($content = implode(', ', $values)) > 70 && ($use_array || count($values) > 1)) { | |
808 | $output .= "\n " . implode(",\n ", $values) . "\n"; | |
809 | } | |
810 | elseif (strpos($content, "\n") !== FALSE) { | |
811 | $output .= " " . $content . "\n"; | |
812 | } | |
813 | else { | |
814 | $output .= " " . $content . ' '; | |
815 | } | |
816 | $output .= $use_array ? ']' : '}'; | |
817 | } | |
818 | else { | |
819 | $output = drupal_json_encode($var); | |
820 | } | |
821 | ||
822 | if ($prefix) { | |
823 | $output = str_replace("\n", "\n$prefix", $output); | |
824 | } | |
825 | return $output; | |
826 | } | |
827 | ||
828 | /** | |
829 | * Rebuild the default entities provided in code. | |
830 | * | |
831 | * Exportable entities provided in code get saved to the database once a module | |
832 | * providing defaults in code is activated. This allows module and entity_load() | |
833 | * to easily deal with exportable entities just by relying on the database. | |
834 | * | |
835 | * The defaults get rebuilt if the cache is cleared or new modules providing | |
836 | * defaults are enabled, such that the defaults in the database are up to date. | |
837 | * A default entity gets updated with the latest defaults in code during rebuild | |
838 | * as long as the default has not been overridden. Once a module providing | |
839 | * defaults is disabled, its default entities get removed from the database | |
840 | * unless they have been overridden. In that case the overridden entity is left | |
841 | * in the database, but its status gets updated to 'custom'. | |
842 | * | |
843 | * @param $entity_types | |
844 | * (optional) If specified, only the defaults of the given entity types are | |
845 | * rebuilt. | |
846 | */ | |
847 | function entity_defaults_rebuild($entity_types = NULL) { | |
848 | if (!isset($entity_types)) { | |
849 | $entity_types = array(); | |
850 | foreach (entity_crud_get_info() as $type => $info) { | |
851 | if (!empty($info['exportable'])) { | |
852 | $entity_types[] = $type; | |
853 | } | |
854 | }; | |
855 | } | |
856 | foreach ($entity_types as $type) { | |
857 | _entity_defaults_rebuild($type); | |
858 | } | |
859 | } | |
860 | ||
861 | /** | |
862 | * Actually rebuild the defaults of a given entity type. | |
863 | */ | |
864 | function _entity_defaults_rebuild($entity_type) { | |
865 | if (lock_acquire('entity_rebuild_' . $entity_type)) { | |
866 | $info = entity_get_info($entity_type); | |
867 | $hook = isset($info['export']['default hook']) ? $info['export']['default hook'] : 'default_' . $entity_type; | |
868 | $keys = $info['entity keys'] + array('module' => 'module', 'status' => 'status', 'name' => $info['entity keys']['id']); | |
869 | ||
870 | // Check for the existence of the module and status columns. | |
871 | if (!in_array($keys['status'], $info['schema_fields_sql']['base table']) || !in_array($keys['module'], $info['schema_fields_sql']['base table'])) { | |
872 | trigger_error("Missing database columns for the exportable entity $entity_type as defined by entity_exportable_schema_fields(). Update the according module and run update.php!", E_USER_WARNING); | |
873 | return; | |
874 | } | |
875 | ||
876 | // Invoke the hook and collect default entities. | |
877 | $entities = array(); | |
878 | foreach (module_implements($hook) as $module) { | |
879 | foreach ((array) module_invoke($module, $hook) as $name => $entity) { | |
880 | $entity->{$keys['name']} = $name; | |
881 | $entity->{$keys['module']} = $module; | |
882 | $entities[$name] = $entity; | |
883 | } | |
884 | } | |
885 | drupal_alter($hook, $entities); | |
886 | ||
887 | // Check for defaults that disappeared. | |
888 | $existing_defaults = entity_load_multiple_by_name($entity_type, FALSE, array($keys['status'] => array(ENTITY_OVERRIDDEN, ENTITY_IN_CODE, ENTITY_FIXED))); | |
889 | ||
890 | foreach ($existing_defaults as $name => $entity) { | |
891 | if (empty($entities[$name])) { | |
892 | $entity->is_rebuild = TRUE; | |
893 | if (entity_has_status($entity_type, $entity, ENTITY_OVERRIDDEN)) { | |
894 | $entity->{$keys['status']} = ENTITY_CUSTOM; | |
895 | entity_save($entity_type, $entity); | |
896 | } | |
897 | else { | |
898 | entity_delete($entity_type, $name); | |
899 | } | |
900 | unset($entity->is_rebuild); | |
901 | } | |
902 | } | |
903 | ||
904 | // Load all existing entities. | |
905 | $existing_entities = entity_load_multiple_by_name($entity_type, array_keys($entities)); | |
906 | ||
907 | foreach ($existing_entities as $name => $entity) { | |
908 | if (entity_has_status($entity_type, $entity, ENTITY_CUSTOM)) { | |
909 | // If the entity already exists but is not yet marked as overridden, we | |
910 | // have to update the status. | |
911 | if (!entity_has_status($entity_type, $entity, ENTITY_OVERRIDDEN)) { | |
912 | $entity->{$keys['status']} |= ENTITY_OVERRIDDEN; | |
913 | $entity->{$keys['module']} = $entities[$name]->{$keys['module']}; | |
914 | $entity->is_rebuild = TRUE; | |
915 | entity_save($entity_type, $entity); | |
916 | unset($entity->is_rebuild); | |
917 | } | |
918 | ||
919 | // The entity is overridden, so we do not need to save the default. | |
920 | unset($entities[$name]); | |
921 | } | |
922 | } | |
923 | ||
924 | // Save defaults. | |
925 | $originals = array(); | |
926 | foreach ($entities as $name => $entity) { | |
927 | if (!empty($existing_entities[$name])) { | |
928 | // Make sure we are updating the existing default. | |
929 | $entity->{$keys['id']} = $existing_entities[$name]->{$keys['id']}; | |
930 | unset($entity->is_new); | |
931 | } | |
932 | // Pre-populate $entity->original as we already have it. So we avoid | |
933 | // loading it again. | |
934 | $entity->original = !empty($existing_entities[$name]) ? $existing_entities[$name] : FALSE; | |
935 | // Keep original entities for hook_{entity_type}_defaults_rebuild() | |
936 | // implementations. | |
937 | $originals[$name] = $entity->original; | |
938 | ||
939 | if (!isset($entity->{$keys['status']})) { | |
940 | $entity->{$keys['status']} = ENTITY_IN_CODE; | |
941 | } | |
942 | else { | |
943 | $entity->{$keys['status']} |= ENTITY_IN_CODE; | |
944 | } | |
945 | $entity->is_rebuild = TRUE; | |
946 | entity_save($entity_type, $entity); | |
947 | unset($entity->is_rebuild); | |
948 | } | |
949 | ||
950 | // Invoke an entity type-specific hook so modules may apply changes, e.g. | |
951 | // efficiently rebuild caches. | |
952 | module_invoke_all($entity_type . '_defaults_rebuild', $entities, $originals); | |
953 | ||
954 | lock_release('entity_rebuild_' . $entity_type); | |
955 | } | |
956 | } | |
957 | ||
958 | /** | |
959 | * Implements hook_modules_installed(). | |
960 | */ | |
961 | function entity_modules_installed($modules) { | |
962 | module_load_install('entity'); | |
963 | entity_entitycache_installed_modules($modules); | |
964 | } | |
965 | ||
966 | /** | |
967 | * Implements hook_modules_uninstalled(). | |
968 | */ | |
969 | function entity_modules_uninstalled($modules) { | |
970 | module_load_install('entity'); | |
971 | entity_entitycache_uninstalled_modules($modules); | |
972 | } | |
973 | ||
974 | /** | |
975 | * Implements hook_modules_enabled(). | |
976 | */ | |
977 | function entity_modules_enabled($modules) { | |
978 | foreach (_entity_modules_get_default_types($modules) as $type) { | |
979 | _entity_defaults_rebuild($type); | |
980 | } | |
981 | } | |
982 | ||
983 | /** | |
984 | * Implements hook_modules_disabled(). | |
985 | */ | |
986 | function entity_modules_disabled($modules) { | |
987 | foreach (_entity_modules_get_default_types($modules) as $entity_type) { | |
988 | $info = entity_get_info($entity_type); | |
989 | ||
990 | // Do nothing if the module providing the entity type has been disabled too. | |
991 | if (isset($info['module']) && in_array($info['module'], $modules)) { | |
992 | return; | |
993 | } | |
994 | ||
995 | $keys = $info['entity keys'] + array('module' => 'module', 'status' => 'status', 'name' => $info['entity keys']['id']); | |
996 | // Remove entities provided in code by one of the disabled modules. | |
997 | $query = new EntityFieldQuery(); | |
998 | $query->entityCondition('entity_type', $entity_type, '=') | |
999 | ->propertyCondition($keys['module'], $modules, 'IN') | |
1000 | ->propertyCondition($keys['status'], array(ENTITY_IN_CODE, ENTITY_FIXED), 'IN'); | |
1001 | $result = $query->execute(); | |
1002 | if (isset($result[$entity_type])) { | |
1003 | $entities = entity_load($entity_type, array_keys($result[$entity_type])); | |
1004 | entity_delete_multiple($entity_type, array_keys($entities)); | |
1005 | } | |
1006 | ||
1007 | // Update overridden entities to be now custom. | |
1008 | $query = new EntityFieldQuery(); | |
1009 | $query->entityCondition('entity_type', $entity_type, '=') | |
1010 | ->propertyCondition($keys['module'], $modules, 'IN') | |
1011 | ->propertyCondition($keys['status'], ENTITY_OVERRIDDEN, '='); | |
1012 | $result = $query->execute(); | |
1013 | if (isset($result[$entity_type])) { | |
1014 | foreach (entity_load($entity_type, array_keys($result[$entity_type])) as $name => $entity) { | |
1015 | $entity->{$keys['status']} = ENTITY_CUSTOM; | |
1016 | $entity->{$keys['module']} = NULL; | |
1017 | entity_save($entity_type, $entity); | |
1018 | } | |
1019 | } | |
1020 | ||
1021 | // Rebuild the remaining defaults so any alterations of the disabled modules | |
1022 | // are gone. | |
1023 | _entity_defaults_rebuild($entity_type); | |
1024 | } | |
1025 | } | |
1026 | ||
1027 | /** | |
1028 | * Gets all entity types for which defaults are provided by the $modules. | |
1029 | */ | |
1030 | function _entity_modules_get_default_types($modules) { | |
1031 | $types = array(); | |
1032 | foreach (entity_crud_get_info() as $entity_type => $info) { | |
1033 | if (!empty($info['exportable'])) { | |
1034 | $hook = isset($info['export']['default hook']) ? $info['export']['default hook'] : 'default_' . $entity_type; | |
1035 | foreach ($modules as $module) { | |
1036 | if (module_hook($module, $hook) || module_hook($module, $hook . '_alter')) { | |
1037 | $types[] = $entity_type; | |
1038 | } | |
1039 | } | |
1040 | } | |
1041 | } | |
1042 | return $types; | |
1043 | } | |
1044 | ||
1045 | /** | |
1046 | * Defines schema fields required for exportable entities. | |
1047 | * | |
1048 | * Warning: Do not call this function in your module's hook_schema() | |
1049 | * implementation or update functions. It is not safe to call functions of | |
1050 | * dependencies at this point. Instead of calling the function, just copy over | |
1051 | * the content. | |
1052 | * For more details see the issue http://drupal.org/node/1122812. | |
1053 | */ | |
1054 | function entity_exportable_schema_fields($module_col = 'module', $status_col = 'status') { | |
1055 | return array( | |
1056 | $status_col => array( | |
1057 | 'type' => 'int', | |
1058 | 'not null' => TRUE, | |
1059 | // Set the default to ENTITY_CUSTOM without using the constant as it is | |
1060 | // not safe to use it at this point. | |
1061 | 'default' => 0x01, | |
1062 | 'size' => 'tiny', | |
1063 | 'description' => 'The exportable status of the entity.', | |
1064 | ), | |
1065 | $module_col => array( | |
1066 | 'description' => 'The name of the providing module if the entity has been defined in code.', | |
1067 | 'type' => 'varchar', | |
1068 | 'length' => 255, | |
1069 | 'not null' => FALSE, | |
1070 | ), | |
1071 | ); | |
1072 | } | |
1073 | ||
1074 | /** | |
1075 | * Implements hook_flush_caches(). | |
1076 | */ | |
1077 | function entity_flush_caches() { | |
1078 | entity_property_info_cache_clear(); | |
1079 | // Re-build defaults in code, however skip it on the admin modules page. In | |
1080 | // case of enabling or disabling modules we already rebuild defaults in | |
1081 | // entity_modules_enabled() and entity_modules_disabled(), so we do not need | |
1082 | // to do it again. | |
1083 | // Also check if rebuilding on cache flush is explicitly disabled. | |
1084 | if (current_path() != 'admin/modules/list/confirm' && variable_get('entity_rebuild_on_flush', TRUE)) { | |
1085 | entity_defaults_rebuild(); | |
1086 | } | |
1087 | ||
1088 | // Care about entitycache tables. | |
1089 | if (module_exists('entitycache')) { | |
1090 | $tables = array(); | |
1091 | foreach (entity_crud_get_info() as $entity_type => $entity_info) { | |
1092 | if (isset($entity_info['module']) && !empty($entity_info['entity cache'])) { | |
1093 | $tables[] = 'cache_entity_' . $entity_type; | |
1094 | } | |
1095 | } | |
1096 | return $tables; | |
1097 | } | |
1098 | } | |
1099 | ||
1100 | /** | |
1101 | * Implements hook_theme(). | |
1102 | */ | |
1103 | function entity_theme() { | |
1104 | // Build a pattern in the form of "(type1|type2|...)(\.|__)" such that all | |
1105 | // templates starting with an entity type or named like the entity type | |
1106 | // are found. | |
1107 | // This has to match the template suggestions provided in | |
1108 | // template_preprocess_entity(). | |
1109 | $types = array_keys(entity_crud_get_info()); | |
1110 | $pattern = '(' . implode('|', $types) . ')(\.|__)'; | |
1111 | ||
1112 | return array( | |
1113 | 'entity_status' => array( | |
1114 | 'variables' => array('status' => NULL, 'html' => TRUE), | |
1115 | 'file' => 'theme/entity.theme.inc', | |
1116 | ), | |
1117 | 'entity' => array( | |
1118 | 'render element' => 'elements', | |
1119 | 'template' => 'entity', | |
1120 | 'pattern' => $pattern, | |
1121 | 'path' => drupal_get_path('module', 'entity') . '/theme', | |
1122 | 'file' => 'entity.theme.inc', | |
1123 | ), | |
1124 | 'entity_property' => array( | |
1125 | 'render element' => 'elements', | |
1126 | 'file' => 'theme/entity.theme.inc', | |
1127 | ), | |
1128 | 'entity_ui_overview_item' => array( | |
1129 | 'variables' => array('label' => NULL, 'entity_type' => NULL, 'url' => FALSE, 'name' => FALSE), | |
1130 | 'file' => 'includes/entity.ui.inc' | |
1131 | ), | |
1132 | ); | |
1133 | } | |
1134 | ||
1135 | /** | |
1136 | * Label callback that refers to the entity classes label method. | |
1137 | */ | |
1138 | function entity_class_label($entity) { | |
1139 | return $entity->label(); | |
1140 | } | |
1141 | ||
1142 | /** | |
1143 | * URI callback that refers to the entity classes uri method. | |
1144 | */ | |
1145 | function entity_class_uri($entity) { | |
1146 | return $entity->uri(); | |
1147 | } | |
1148 | ||
1149 | /** | |
1150 | * Implements hook_file_download_access() for entity types provided by the CRUD API. | |
1151 | */ | |
1152 | function entity_file_download_access($field, $entity_type, $entity) { | |
1153 | $info = entity_get_info($entity_type); | |
1154 | if (in_array('EntityAPIControllerInterface', class_implements($info['controller class']))) { | |
1155 | return entity_access('view', $entity_type, $entity); | |
1156 | } | |
1157 | } | |
1158 | ||
1159 | /** | |
1160 | * Determines the UI controller class for a given entity type. | |
1161 | * | |
1162 | * @return EntityDefaultUIController | |
1163 | * If a type is given, the controller for the given entity type. Else an array | |
1164 | * of all enabled UI controllers keyed by entity type is returned. | |
1165 | */ | |
1166 | function entity_ui_controller($type = NULL) { | |
1167 | $static = &drupal_static(__FUNCTION__); | |
1168 | ||
1169 | if (!isset($type)) { | |
1170 | // Invoke the function for each type to ensure we have fully populated the | |
1171 | // static variable. | |
1172 | foreach (entity_get_info() as $entity_type => $info) { | |
1173 | entity_ui_controller($entity_type); | |
1174 | } | |
1175 | return array_filter($static); | |
1176 | } | |
1177 | ||
1178 | if (!isset($static[$type])) { | |
1179 | $info = entity_get_info($type); | |
1180 | $class = isset($info['admin ui']['controller class']) ? $info['admin ui']['controller class'] : 'EntityDefaultUIController'; | |
1181 | $static[$type] = (isset($info['admin ui']['path']) && $class) ? new $class($type, $info) : FALSE; | |
1182 | } | |
1183 | ||
1184 | return $static[$type]; | |
1185 | } | |
1186 | ||
1187 | /** | |
1188 | * Implements hook_menu(). | |
1189 | * | |
1190 | * @see EntityDefaultUIController::hook_menu() | |
1191 | */ | |
1192 | function entity_menu() { | |
1193 | $items = array(); | |
1194 | foreach (entity_ui_controller() as $controller) { | |
1195 | $items += $controller->hook_menu(); | |
1196 | } | |
1197 | return $items; | |
1198 | } | |
1199 | ||
1200 | /** | |
1201 | * Implements hook_forms(). | |
1202 | * | |
1203 | * @see EntityDefaultUIController::hook_forms() | |
1204 | * @see entity_ui_get_form() | |
1205 | */ | |
1206 | function entity_forms($form_id, $args) { | |
1207 | // For efficiency only invoke an entity types controller, if a form of it is | |
1208 | // requested. Thus if the first (overview and operation form) or the third | |
1209 | // argument (edit form) is an entity type name, add in the types forms. | |
1210 | if (isset($args[0]) && is_string($args[0]) && entity_get_info($args[0])) { | |
1211 | $type = $args[0]; | |
1212 | } | |
1213 | elseif (isset($args[2]) && is_string($args[2]) && entity_get_info($args[2])) { | |
1214 | $type = $args[2]; | |
1215 | } | |
1216 | if (isset($type) && $controller = entity_ui_controller($type)) { | |
1217 | return $controller->hook_forms(); | |
1218 | } | |
1219 | } | |
1220 | ||
1221 | /** | |
1222 | * A wrapper around drupal_get_form() that helps building entity forms. | |
1223 | * | |
1224 | * This function may be used by entities to build their entity form. It has to | |
1225 | * be used instead of calling drupal_get_form(). | |
1226 | * Entity forms built with this helper receive useful defaults suiting for | |
1227 | * editing a single entity, whereas the special cases of adding and cloning | |
1228 | * of entities are supported too. | |
1229 | * | |
1230 | * While this function is intended to be used to get entity forms for entities | |
1231 | * using the entity ui controller, it may be used for entity types not using | |
1232 | * the ui controller too. | |
1233 | * | |
1234 | * @param $entity_type | |
1235 | * The entity type for which to get the form. | |
1236 | * @param $entity | |
1237 | * The entity for which to return the form. | |
1238 | * If $op is 'add' the entity has to be either initialized before calling this | |
1239 | * function, or NULL may be passed. If NULL is passed, an entity will be | |
1240 | * initialized with empty values using entity_create(). Thus entities, for | |
1241 | * which this is problematic have to care to pass in an initialized entity. | |
1242 | * @param $op | |
1243 | * (optional) One of 'edit', 'add' or 'clone'. Defaults to edit. | |
1244 | * @param $form_state | |
1245 | * (optional) A pre-populated form state, e.g. to add in form include files. | |
1246 | * See entity_metadata_form_entity_ui(). | |
1247 | * | |
1248 | * @return | |
1249 | * The fully built and processed form, ready to be rendered. | |
1250 | * | |
1251 | * @see EntityDefaultUIController::hook_forms() | |
1252 | * @see entity_ui_form_submit_build_entity() | |
1253 | */ | |
1254 | function entity_ui_get_form($entity_type, $entity, $op = 'edit', $form_state = array()) { | |
1255 | if (isset($entity)) { | |
1256 | list(, , $bundle) = entity_extract_ids($entity_type, $entity); | |
1257 | } | |
1258 | $form_id = (!isset($bundle) || $bundle == $entity_type) ? $entity_type . '_form' : $entity_type . '_edit_' . $bundle . '_form'; | |
1259 | ||
1260 | if (!isset($entity) && $op == 'add') { | |
1261 | $entity = entity_create($entity_type, array()); | |
1262 | } | |
1263 | ||
1264 | // Do not use drupal_get_form(), but invoke drupal_build_form() ourself so | |
1265 | // we can prepulate the form state. | |
1266 | $form_state['wrapper_callback'] = 'entity_ui_main_form_defaults'; | |
1267 | $form_state['entity_type'] = $entity_type; | |
1268 | form_load_include($form_state, 'inc', 'entity', 'includes/entity.ui'); | |
1269 | ||
1270 | // Handle cloning. We cannot do that in the wrapper callback as it is too late | |
1271 | // for changing arguments. | |
1272 | if ($op == 'clone') { | |
1273 | $entity = entity_ui_clone_entity($entity_type, $entity); | |
1274 | } | |
1275 | ||
1276 | // We don't pass the entity type as first parameter, as the implementing | |
1277 | // module knows the type anyway. However, in order to allow for efficient | |
1278 | // hook_forms() implementiations we append the entity type as last argument, | |
1279 | // which the module implementing the form constructor may safely ignore. | |
1280 | // @see entity_forms() | |
1281 | $form_state['build_info']['args'] = array($entity, $op, $entity_type); | |
1282 | return drupal_build_form($form_id, $form_state); | |
1283 | } | |
1284 | ||
1285 | ||
1286 | /** | |
1287 | * Gets the page/menu title for local action operations. | |
1288 | * | |
1289 | * @param $op | |
1290 | * The current operation. One of 'add' or 'import'. | |
1291 | * @param $entity_type | |
1292 | * The entity type. | |
1293 | * @param $bundle_name | |
1294 | * (Optional) The name of the bundle. May be NULL if the bundle name is not | |
1295 | * relevant to the current page. If the entity type has only one bundle, or no | |
1296 | * bundles, this will be the same as the entity type. | |
1297 | */ | |
1298 | function entity_ui_get_action_title($op, $entity_type, $bundle_name = NULL) { | |
1299 | $info = entity_get_info($entity_type); | |
1300 | switch ($op) { | |
1301 | case 'add': | |
1302 | if (isset($bundle_name) && $bundle_name != $entity_type) { | |
1303 | return t('Add @bundle_name @entity_type', array( | |
1304 | '@bundle_name' => drupal_strtolower($info['bundles'][$bundle_name]['label']), | |
1305 | '@entity_type' => drupal_strtolower($info['label']), | |
1306 | )); | |
1307 | } | |
1308 | else { | |
1309 | return t('Add @entity_type', array('@entity_type' => drupal_strtolower($info['label']))); | |
1310 | } | |
1311 | case 'import': | |
1312 | return t('Import @entity_type', array('@entity_type' => drupal_strtolower($info['label']))); | |
1313 | } | |
1314 | } | |
1315 | ||
1316 | /** | |
1317 | * Helper for using i18n_string(). | |
1318 | * | |
1319 | * @param $name | |
1320 | * Textgroup and context glued with ':'. | |
1321 | * @param $default | |
1322 | * String in default language. Default language may or may not be English. | |
1323 | * @param $langcode | |
1324 | * (optional) The code of a certain language to translate the string into. | |
1325 | * Defaults to the i18n_string() default, i.e. the current language. | |
1326 | * | |
1327 | * @see i18n_string() | |
1328 | */ | |
1329 | function entity_i18n_string($name, $default, $langcode = NULL) { | |
1330 | return function_exists('i18n_string') ? i18n_string($name, $default, array('langcode' => $langcode)) : $default; | |
1331 | } | |
1332 | ||
1333 | /** | |
1334 | * Implements hook_views_api(). | |
1335 | */ | |
1336 | function entity_views_api() { | |
1337 | return array( | |
1338 | 'api' => '3.0-alpha1', | |
1339 | 'path' => drupal_get_path('module', 'entity') . '/views', | |
1340 | ); | |
1341 | } | |
1342 | ||
1343 | /** | |
1344 | * Implements hook_field_extra_fields(). | |
1345 | */ | |
1346 | function entity_field_extra_fields() { | |
1347 | // Invoke specified controllers for entity types provided by the CRUD API. | |
1348 | $items = array(); | |
1349 | foreach (entity_crud_get_info() as $type => $info) { | |
1350 | if (!empty($info['extra fields controller class'])) { | |
1351 | $items = array_merge_recursive($items, entity_get_extra_fields_controller($type)->fieldExtraFields()); | |
1352 | } | |
1353 | } | |
1354 | return $items; | |
1355 | } | |
1356 | ||
1357 | /** | |
1358 | * Gets the extra field controller class for a given entity type. | |
1359 | * | |
1360 | * @return EntityExtraFieldsControllerInterface|false | |
1361 | * The controller for the given entity type or FALSE if none is specified. | |
1362 | */ | |
1363 | function entity_get_extra_fields_controller($type = NULL) { | |
1364 | $static = &drupal_static(__FUNCTION__); | |
1365 | ||
1366 | if (!isset($static[$type])) { | |
1367 | $static[$type] = FALSE; | |
1368 | $info = entity_get_info($type); | |
1369 | if (!empty($info['extra fields controller class'])) { | |
1370 | $static[$type] = new $info['extra fields controller class']($type); | |
1371 | } | |
1372 | } | |
1373 | return $static[$type]; | |
1374 | } | |
1375 | ||
1376 | /** | |
1377 | * Returns a property wrapper for the given data. | |
1378 | * | |
1379 | * If an entity is wrapped, the wrapper can be used to retrieve further wrappers | |
1380 | * for the entitity properties. For that the wrapper support chaining, e.g. you | |
1381 | * can use a node wrapper to get the node authors mail address: | |
1382 | * | |
1383 | * @code | |
1384 | * echo $wrappedNode->author->mail->value(); | |
1385 | * @endcode | |
1386 | * | |
1387 | * @param $type | |
1388 | * The type of the passed data. | |
1389 | * @param $data | |
1390 | * The data to wrap. It may be set to NULL, so the wrapper can be used | |
1391 | * without any data for getting information about properties. | |
1392 | * @param $info | |
1393 | * (optional) Specify additional information for the passed data: | |
1394 | * - langcode: (optional) If the data is language specific, its langauge | |
1395 | * code. Defaults to NULL, what means language neutral. | |
1396 | * - bundle: (optional) If an entity is wrapped but not passed, use this key | |
1397 | * to specify the bundle to return a wrapper for. | |
1398 | * - property info: (optional) May be used to use a wrapper with an arbitrary | |
1399 | * data structure (type 'struct'). Use this key for specifying info about | |
1400 | * properties in the same structure as used by hook_entity_property_info(). | |
1401 | * - property info alter: (optional) A callback for altering the property | |
1402 | * info before it is utilized by the wrapper. | |
1403 | * - property defaults: (optional) An array of defaults for the info of | |
1404 | * each property of the wrapped data item. | |
1405 | * @return EntityMetadataWrapper | |
1406 | * Dependend on the passed data the right wrapper is returned. | |
1407 | */ | |
1408 | function entity_metadata_wrapper($type, $data = NULL, array $info = array()) { | |
1409 | if ($type == 'entity' || (($entity_info = entity_get_info()) && isset($entity_info[$type]))) { | |
1410 | // If the passed entity is the global $user, we load the user object by only | |
1411 | // passing on the user id. The global user is not a fully loaded entity. | |
1412 | if ($type == 'user' && is_object($data) && $data == $GLOBALS['user']) { | |
1413 | $data = $data->uid; | |
1414 | } | |
1415 | return new EntityDrupalWrapper($type, $data, $info); | |
1416 | } | |
1417 | elseif ($type == 'list' || entity_property_list_extract_type($type)) { | |
1418 | return new EntityListWrapper($type, $data, $info); | |
1419 | } | |
1420 | elseif (isset($info['property info'])) { | |
1421 | return new EntityStructureWrapper($type, $data, $info); | |
1422 | } | |
1423 | else { | |
1424 | return new EntityValueWrapper($type, $data, $info); | |
1425 | } | |
1426 | } | |
1427 | ||
1428 | /** | |
1429 | * Returns a metadata wrapper for accessing site-wide properties. | |
1430 | * | |
1431 | * Although there is no 'site' entity or such, modules may provide info about | |
1432 | * site-wide properties using hook_entity_property_info(). This function returns | |
1433 | * a wrapper for making use of this properties. | |
1434 | * | |
1435 | * @return EntityMetadataWrapper | |
1436 | * A wrapper for accessing site-wide properties. | |
1437 | * | |
1438 | * @see entity_metadata_system_entity_property_info() | |
1439 | */ | |
1440 | function entity_metadata_site_wrapper() { | |
1441 | $site_info = entity_get_property_info('site'); | |
1442 | $info['property info'] = $site_info['properties']; | |
1443 | return entity_metadata_wrapper('site', FALSE, $info); | |
1444 | } | |
1445 | ||
1446 | /** | |
1447 | * Implements hook_module_implements_alter(). | |
1448 | * | |
1449 | * Moves the hook_entity_info_alter() implementation to the bottom so it is | |
1450 | * invoked after all modules relying on the entity API. | |
1451 | * That way we ensure to run last and clear the field-info cache after the | |
1452 | * others added in their bundle information. | |
1453 | * | |
1454 | * @see entity_entity_info_alter() | |
1455 | */ | |
1456 | function entity_module_implements_alter(&$implementations, $hook) { | |
1457 | if ($hook == 'entity_info_alter') { | |
1458 | // Move our hook implementation to the bottom. | |
1459 | $group = $implementations['entity']; | |
1460 | unset($implementations['entity']); | |
1461 | $implementations['entity'] = $group; | |
1462 | } | |
1463 | } | |
1464 | ||
1465 | /** | |
1466 | * Implements hook_entity_info_alter(). | |
1467 | * | |
1468 | * @see entity_module_implements_alter() | |
1469 | */ | |
1470 | function entity_entity_info_alter(&$entity_info) { | |
1471 | _entity_info_add_metadata($entity_info); | |
1472 | ||
1473 | // Populate a default value for the 'configuration' key of all entity types. | |
1474 | foreach ($entity_info as $type => $info) { | |
1475 | if (!isset($info['configuration'])) { | |
1476 | $entity_info[$type]['configuration'] = !empty($info['exportable']); | |
1477 | } | |
1478 | ||
1479 | if (isset($info['controller class']) && in_array('EntityAPIControllerInterface', class_implements($info['controller class']))) { | |
1480 | // Automatically disable field cache when entity cache is used. | |
1481 | if (!empty($info['entity cache']) && module_exists('entitycache')) { | |
1482 | $entity_info[$type]['field cache'] = FALSE; | |
1483 | } | |
1484 | } | |
1485 | } | |
1486 | } | |
1487 | ||
1488 | /** | |
1489 | * Adds metadata and callbacks for core entities to the entity info. | |
1490 | */ | |
1491 | function _entity_info_add_metadata(&$entity_info) { | |
1492 | // Set plural labels. | |
1493 | $entity_info['node']['plural label'] = t('Nodes'); | |
1494 | $entity_info['user']['plural label'] = t('Users'); | |
1495 | $entity_info['file']['plural label'] = t('Files'); | |
1496 | ||
1497 | // Set descriptions. | |
1498 | $entity_info['node']['description'] = t('Nodes represent the main site content items.'); | |
1499 | $entity_info['user']['description'] = t('Users who have created accounts on your site.'); | |
1500 | $entity_info['file']['description'] = t('Uploaded file.'); | |
1501 | ||
1502 | // Set access callbacks. | |
1503 | $entity_info['node']['access callback'] = 'entity_metadata_no_hook_node_access'; | |
1504 | $entity_info['user']['access callback'] = 'entity_metadata_user_access'; | |
1505 | // File entity has it's own entity_access function. | |
1506 | if (!module_exists('file_entity')) { | |
1507 | $entity_info['file']['access callback'] = 'entity_metadata_file_access'; | |
1508 | } | |
1509 | ||
1510 | // CRUD function callbacks. | |
1511 | $entity_info['node']['creation callback'] = 'entity_metadata_create_node'; | |
1512 | $entity_info['node']['save callback'] = 'node_save'; | |
1513 | $entity_info['node']['deletion callback'] = 'node_delete'; | |
1514 | $entity_info['node']['revision deletion callback'] = 'node_revision_delete'; | |
1515 | $entity_info['user']['creation callback'] = 'entity_metadata_create_object'; | |
1516 | $entity_info['user']['save callback'] = 'entity_metadata_user_save'; | |
1517 | $entity_info['user']['deletion callback'] = 'user_delete'; | |
1518 | $entity_info['file']['save callback'] = 'file_save'; | |
1519 | $entity_info['file']['deletion callback'] = 'entity_metadata_delete_file'; | |
1520 | ||
1521 | // Form callbacks. | |
1522 | $entity_info['node']['form callback'] = 'entity_metadata_form_node'; | |
1523 | $entity_info['user']['form callback'] = 'entity_metadata_form_user'; | |
1524 | ||
1525 | // URI callbacks. | |
1526 | if (!isset($entity_info['file']['uri callback'])) { | |
1527 | $entity_info['file']['uri callback'] = 'entity_metadata_uri_file'; | |
1528 | } | |
1529 | ||
1530 | // View callbacks. | |
1531 | $entity_info['node']['view callback'] = 'entity_metadata_view_node'; | |
1532 | $entity_info['user']['view callback'] = 'entity_metadata_view_single'; | |
1533 | ||
1534 | if (module_exists('comment')) { | |
1535 | $entity_info['comment']['plural label'] = t('Comments'); | |
1536 | $entity_info['comment']['description'] = t('Remark or note that refers to a node.'); | |
1537 | $entity_info['comment']['access callback'] = 'entity_metadata_comment_access'; | |
1538 | $entity_info['comment']['creation callback'] = 'entity_metadata_create_comment'; | |
1539 | $entity_info['comment']['save callback'] = 'comment_save'; | |
1540 | $entity_info['comment']['deletion callback'] = 'comment_delete'; | |
1541 | $entity_info['comment']['view callback'] = 'entity_metadata_view_comment'; | |
1542 | $entity_info['comment']['form callback'] = 'entity_metadata_form_comment'; | |
1543 | } | |
1544 | if (module_exists('taxonomy')) { | |
1545 | $entity_info['taxonomy_term']['plural label'] = t('Taxonomy terms'); | |
1546 | $entity_info['taxonomy_term']['description'] = t('Taxonomy terms are used for classifying content.'); | |
1547 | $entity_info['taxonomy_term']['access callback'] = 'entity_metadata_taxonomy_access'; | |
1548 | $entity_info['taxonomy_term']['creation callback'] = 'entity_metadata_create_object'; | |
1549 | $entity_info['taxonomy_term']['save callback'] = 'taxonomy_term_save'; | |
1550 | $entity_info['taxonomy_term']['deletion callback'] = 'taxonomy_term_delete'; | |
1551 | $entity_info['taxonomy_term']['view callback'] = 'entity_metadata_view_single'; | |
1552 | $entity_info['taxonomy_term']['form callback'] = 'entity_metadata_form_taxonomy_term'; | |
1553 | ||
1554 | $entity_info['taxonomy_vocabulary']['plural label'] = t('Taxonomy vocabularies'); | |
1555 | $entity_info['taxonomy_vocabulary']['description'] = t('Vocabularies contain related taxonomy terms, which are used for classifying content.'); | |
1556 | $entity_info['taxonomy_vocabulary']['access callback'] = 'entity_metadata_taxonomy_access'; | |
1557 | $entity_info['taxonomy_vocabulary']['creation callback'] = 'entity_metadata_create_object'; | |
1558 | $entity_info['taxonomy_vocabulary']['save callback'] = 'taxonomy_vocabulary_save'; | |
1559 | $entity_info['taxonomy_vocabulary']['deletion callback'] = 'taxonomy_vocabulary_delete'; | |
1560 | $entity_info['taxonomy_vocabulary']['form callback'] = 'entity_metadata_form_taxonomy_vocabulary'; | |
1561 | // Token type mapping. | |
1562 | $entity_info['taxonomy_term']['token type'] = 'term'; | |
1563 | $entity_info['taxonomy_vocabulary']['token type'] = 'vocabulary'; | |
1564 | } | |
1565 | } | |
1566 | ||
1567 | /** | |
1568 | * Implements hook_ctools_plugin_directory(). | |
1569 | */ | |
1570 | function entity_ctools_plugin_directory($module, $plugin) { | |
1571 | if ($module == 'ctools' && $plugin == 'content_types') { | |
1572 | return 'ctools/content_types'; | |
1573 | } | |
1574 | } |