danielebarchiesi@4
|
1 <?php
|
danielebarchiesi@4
|
2
|
danielebarchiesi@4
|
3 /**
|
danielebarchiesi@4
|
4 * @file
|
danielebarchiesi@4
|
5 * Provides a controller building upon the core controller but providing more
|
danielebarchiesi@4
|
6 * features like full CRUD functionality.
|
danielebarchiesi@4
|
7 */
|
danielebarchiesi@4
|
8
|
danielebarchiesi@4
|
9 /**
|
danielebarchiesi@4
|
10 * Interface for EntityControllers compatible with the entity API.
|
danielebarchiesi@4
|
11 */
|
danielebarchiesi@4
|
12 interface EntityAPIControllerInterface extends DrupalEntityControllerInterface {
|
danielebarchiesi@4
|
13
|
danielebarchiesi@4
|
14 /**
|
danielebarchiesi@4
|
15 * Delete permanently saved entities.
|
danielebarchiesi@4
|
16 *
|
danielebarchiesi@4
|
17 * In case of failures, an exception is thrown.
|
danielebarchiesi@4
|
18 *
|
danielebarchiesi@4
|
19 * @param $ids
|
danielebarchiesi@4
|
20 * An array of entity IDs.
|
danielebarchiesi@4
|
21 */
|
danielebarchiesi@4
|
22 public function delete($ids);
|
danielebarchiesi@4
|
23
|
danielebarchiesi@4
|
24 /**
|
danielebarchiesi@4
|
25 * Invokes a hook on behalf of the entity. For hooks that have a respective
|
danielebarchiesi@4
|
26 * field API attacher like insert/update/.. the attacher is called too.
|
danielebarchiesi@4
|
27 */
|
danielebarchiesi@4
|
28 public function invoke($hook, $entity);
|
danielebarchiesi@4
|
29
|
danielebarchiesi@4
|
30 /**
|
danielebarchiesi@4
|
31 * Permanently saves the given entity.
|
danielebarchiesi@4
|
32 *
|
danielebarchiesi@4
|
33 * In case of failures, an exception is thrown.
|
danielebarchiesi@4
|
34 *
|
danielebarchiesi@4
|
35 * @param $entity
|
danielebarchiesi@4
|
36 * The entity to save.
|
danielebarchiesi@4
|
37 *
|
danielebarchiesi@4
|
38 * @return
|
danielebarchiesi@4
|
39 * SAVED_NEW or SAVED_UPDATED is returned depending on the operation
|
danielebarchiesi@4
|
40 * performed.
|
danielebarchiesi@4
|
41 */
|
danielebarchiesi@4
|
42 public function save($entity);
|
danielebarchiesi@4
|
43
|
danielebarchiesi@4
|
44 /**
|
danielebarchiesi@4
|
45 * Create a new entity.
|
danielebarchiesi@4
|
46 *
|
danielebarchiesi@4
|
47 * @param array $values
|
danielebarchiesi@4
|
48 * An array of values to set, keyed by property name.
|
danielebarchiesi@4
|
49 * @return
|
danielebarchiesi@4
|
50 * A new instance of the entity type.
|
danielebarchiesi@4
|
51 */
|
danielebarchiesi@4
|
52 public function create(array $values = array());
|
danielebarchiesi@4
|
53
|
danielebarchiesi@4
|
54 /**
|
danielebarchiesi@4
|
55 * Exports an entity as serialized string.
|
danielebarchiesi@4
|
56 *
|
danielebarchiesi@4
|
57 * @param $entity
|
danielebarchiesi@4
|
58 * The entity to export.
|
danielebarchiesi@4
|
59 * @param $prefix
|
danielebarchiesi@4
|
60 * An optional prefix for each line.
|
danielebarchiesi@4
|
61 *
|
danielebarchiesi@4
|
62 * @return
|
danielebarchiesi@4
|
63 * The exported entity as serialized string. The format is determined by
|
danielebarchiesi@4
|
64 * the controller and has to be compatible with the format that is accepted
|
danielebarchiesi@4
|
65 * by the import() method.
|
danielebarchiesi@4
|
66 */
|
danielebarchiesi@4
|
67 public function export($entity, $prefix = '');
|
danielebarchiesi@4
|
68
|
danielebarchiesi@4
|
69 /**
|
danielebarchiesi@4
|
70 * Imports an entity from a string.
|
danielebarchiesi@4
|
71 *
|
danielebarchiesi@4
|
72 * @param string $export
|
danielebarchiesi@4
|
73 * An exported entity as serialized string.
|
danielebarchiesi@4
|
74 *
|
danielebarchiesi@4
|
75 * @return
|
danielebarchiesi@4
|
76 * An entity object not yet saved.
|
danielebarchiesi@4
|
77 */
|
danielebarchiesi@4
|
78 public function import($export);
|
danielebarchiesi@4
|
79
|
danielebarchiesi@4
|
80 /**
|
danielebarchiesi@4
|
81 * Builds a structured array representing the entity's content.
|
danielebarchiesi@4
|
82 *
|
danielebarchiesi@4
|
83 * The content built for the entity will vary depending on the $view_mode
|
danielebarchiesi@4
|
84 * parameter.
|
danielebarchiesi@4
|
85 *
|
danielebarchiesi@4
|
86 * @param $entity
|
danielebarchiesi@4
|
87 * An entity object.
|
danielebarchiesi@4
|
88 * @param $view_mode
|
danielebarchiesi@4
|
89 * View mode, e.g. 'full', 'teaser'...
|
danielebarchiesi@4
|
90 * @param $langcode
|
danielebarchiesi@4
|
91 * (optional) A language code to use for rendering. Defaults to the global
|
danielebarchiesi@4
|
92 * content language of the current request.
|
danielebarchiesi@4
|
93 * @return
|
danielebarchiesi@4
|
94 * The renderable array.
|
danielebarchiesi@4
|
95 */
|
danielebarchiesi@4
|
96 public function buildContent($entity, $view_mode = 'full', $langcode = NULL);
|
danielebarchiesi@4
|
97
|
danielebarchiesi@4
|
98 /**
|
danielebarchiesi@4
|
99 * Generate an array for rendering the given entities.
|
danielebarchiesi@4
|
100 *
|
danielebarchiesi@4
|
101 * @param $entities
|
danielebarchiesi@4
|
102 * An array of entities to render.
|
danielebarchiesi@4
|
103 * @param $view_mode
|
danielebarchiesi@4
|
104 * View mode, e.g. 'full', 'teaser'...
|
danielebarchiesi@4
|
105 * @param $langcode
|
danielebarchiesi@4
|
106 * (optional) A language code to use for rendering. Defaults to the global
|
danielebarchiesi@4
|
107 * content language of the current request.
|
danielebarchiesi@4
|
108 * @param $page
|
danielebarchiesi@4
|
109 * (optional) If set will control if the entity is rendered: if TRUE
|
danielebarchiesi@4
|
110 * the entity will be rendered without its title, so that it can be embeded
|
danielebarchiesi@4
|
111 * in another context. If FALSE the entity will be displayed with its title
|
danielebarchiesi@4
|
112 * in a mode suitable for lists.
|
danielebarchiesi@4
|
113 * If unset, the page mode will be enabled if the current path is the URI
|
danielebarchiesi@4
|
114 * of the entity, as returned by entity_uri().
|
danielebarchiesi@4
|
115 * This parameter is only supported for entities which controller is a
|
danielebarchiesi@4
|
116 * EntityAPIControllerInterface.
|
danielebarchiesi@4
|
117 * @return
|
danielebarchiesi@4
|
118 * The renderable array, keyed by entity name or numeric id.
|
danielebarchiesi@4
|
119 */
|
danielebarchiesi@4
|
120 public function view($entities, $view_mode = 'full', $langcode = NULL, $page = NULL);
|
danielebarchiesi@4
|
121 }
|
danielebarchiesi@4
|
122
|
danielebarchiesi@4
|
123 /**
|
danielebarchiesi@4
|
124 * Interface for EntityControllers of entities that support revisions.
|
danielebarchiesi@4
|
125 */
|
danielebarchiesi@4
|
126 interface EntityAPIControllerRevisionableInterface extends EntityAPIControllerInterface {
|
danielebarchiesi@4
|
127
|
danielebarchiesi@4
|
128 /**
|
danielebarchiesi@4
|
129 * Delete an entity revision.
|
danielebarchiesi@4
|
130 *
|
danielebarchiesi@4
|
131 * Note that the default revision of an entity cannot be deleted.
|
danielebarchiesi@4
|
132 *
|
danielebarchiesi@4
|
133 * @param $revision_id
|
danielebarchiesi@4
|
134 * The ID of the revision to delete.
|
danielebarchiesi@4
|
135 *
|
danielebarchiesi@4
|
136 * @return boolean
|
danielebarchiesi@4
|
137 * TRUE if the entity revision could be deleted, FALSE otherwise.
|
danielebarchiesi@4
|
138 */
|
danielebarchiesi@4
|
139 public function deleteRevision($revision_id);
|
danielebarchiesi@4
|
140
|
danielebarchiesi@4
|
141 }
|
danielebarchiesi@4
|
142
|
danielebarchiesi@4
|
143 /**
|
danielebarchiesi@4
|
144 * A controller implementing EntityAPIControllerInterface for the database.
|
danielebarchiesi@4
|
145 */
|
danielebarchiesi@4
|
146 class EntityAPIController extends DrupalDefaultEntityController implements EntityAPIControllerRevisionableInterface {
|
danielebarchiesi@4
|
147
|
danielebarchiesi@4
|
148 protected $cacheComplete = FALSE;
|
danielebarchiesi@4
|
149 protected $bundleKey;
|
danielebarchiesi@4
|
150 protected $defaultRevisionKey;
|
danielebarchiesi@4
|
151
|
danielebarchiesi@4
|
152 /**
|
danielebarchiesi@4
|
153 * Overridden.
|
danielebarchiesi@4
|
154 * @see DrupalDefaultEntityController#__construct()
|
danielebarchiesi@4
|
155 */
|
danielebarchiesi@4
|
156 public function __construct($entityType) {
|
danielebarchiesi@4
|
157 parent::__construct($entityType);
|
danielebarchiesi@4
|
158 // If this is the bundle of another entity, set the bundle key.
|
danielebarchiesi@4
|
159 if (isset($this->entityInfo['bundle of'])) {
|
danielebarchiesi@4
|
160 $info = entity_get_info($this->entityInfo['bundle of']);
|
danielebarchiesi@4
|
161 $this->bundleKey = $info['bundle keys']['bundle'];
|
danielebarchiesi@4
|
162 }
|
danielebarchiesi@4
|
163 $this->defaultRevisionKey = !empty($this->entityInfo['entity keys']['default revision']) ? $this->entityInfo['entity keys']['default revision'] : 'default_revision';
|
danielebarchiesi@4
|
164 }
|
danielebarchiesi@4
|
165
|
danielebarchiesi@4
|
166 /**
|
danielebarchiesi@4
|
167 * Overrides DrupalDefaultEntityController::buildQuery().
|
danielebarchiesi@4
|
168 */
|
danielebarchiesi@4
|
169 protected function buildQuery($ids, $conditions = array(), $revision_id = FALSE) {
|
danielebarchiesi@4
|
170 $query = parent::buildQuery($ids, $conditions, $revision_id);
|
danielebarchiesi@4
|
171 if ($this->revisionKey) {
|
danielebarchiesi@4
|
172 // Compare revision id of the base and revision table, if equal then this
|
danielebarchiesi@4
|
173 // is the default revision.
|
danielebarchiesi@4
|
174 $query->addExpression('base.' . $this->revisionKey . ' = revision.' . $this->revisionKey, $this->defaultRevisionKey);
|
danielebarchiesi@4
|
175 }
|
danielebarchiesi@4
|
176 return $query;
|
danielebarchiesi@4
|
177 }
|
danielebarchiesi@4
|
178
|
danielebarchiesi@4
|
179 /**
|
danielebarchiesi@4
|
180 * Builds and executes the query for loading.
|
danielebarchiesi@4
|
181 *
|
danielebarchiesi@4
|
182 * @return The results in a Traversable object.
|
danielebarchiesi@4
|
183 */
|
danielebarchiesi@4
|
184 public function query($ids, $conditions, $revision_id = FALSE) {
|
danielebarchiesi@4
|
185 // Build the query.
|
danielebarchiesi@4
|
186 $query = $this->buildQuery($ids, $conditions, $revision_id);
|
danielebarchiesi@4
|
187 $result = $query->execute();
|
danielebarchiesi@4
|
188 if (!empty($this->entityInfo['entity class'])) {
|
danielebarchiesi@4
|
189 $result->setFetchMode(PDO::FETCH_CLASS, $this->entityInfo['entity class'], array(array(), $this->entityType));
|
danielebarchiesi@4
|
190 }
|
danielebarchiesi@4
|
191 return $result;
|
danielebarchiesi@4
|
192 }
|
danielebarchiesi@4
|
193
|
danielebarchiesi@4
|
194 /**
|
danielebarchiesi@4
|
195 * Overridden.
|
danielebarchiesi@4
|
196 * @see DrupalDefaultEntityController#load($ids, $conditions)
|
danielebarchiesi@4
|
197 *
|
danielebarchiesi@4
|
198 * In contrast to the parent implementation we factor out query execution, so
|
danielebarchiesi@4
|
199 * fetching can be further customized easily.
|
danielebarchiesi@4
|
200 */
|
danielebarchiesi@4
|
201 public function load($ids = array(), $conditions = array()) {
|
danielebarchiesi@4
|
202 $entities = array();
|
danielebarchiesi@4
|
203
|
danielebarchiesi@4
|
204 // Revisions are not statically cached, and require a different query to
|
danielebarchiesi@4
|
205 // other conditions, so separate the revision id into its own variable.
|
danielebarchiesi@4
|
206 if ($this->revisionKey && isset($conditions[$this->revisionKey])) {
|
danielebarchiesi@4
|
207 $revision_id = $conditions[$this->revisionKey];
|
danielebarchiesi@4
|
208 unset($conditions[$this->revisionKey]);
|
danielebarchiesi@4
|
209 }
|
danielebarchiesi@4
|
210 else {
|
danielebarchiesi@4
|
211 $revision_id = FALSE;
|
danielebarchiesi@4
|
212 }
|
danielebarchiesi@4
|
213
|
danielebarchiesi@4
|
214 // Create a new variable which is either a prepared version of the $ids
|
danielebarchiesi@4
|
215 // array for later comparison with the entity cache, or FALSE if no $ids
|
danielebarchiesi@4
|
216 // were passed. The $ids array is reduced as items are loaded from cache,
|
danielebarchiesi@4
|
217 // and we need to know if it's empty for this reason to avoid querying the
|
danielebarchiesi@4
|
218 // database when all requested entities are loaded from cache.
|
danielebarchiesi@4
|
219 $passed_ids = !empty($ids) ? array_flip($ids) : FALSE;
|
danielebarchiesi@4
|
220
|
danielebarchiesi@4
|
221 // Try to load entities from the static cache.
|
danielebarchiesi@4
|
222 if ($this->cache && !$revision_id) {
|
danielebarchiesi@4
|
223 $entities = $this->cacheGet($ids, $conditions);
|
danielebarchiesi@4
|
224 // If any entities were loaded, remove them from the ids still to load.
|
danielebarchiesi@4
|
225 if ($passed_ids) {
|
danielebarchiesi@4
|
226 $ids = array_keys(array_diff_key($passed_ids, $entities));
|
danielebarchiesi@4
|
227 }
|
danielebarchiesi@4
|
228 }
|
danielebarchiesi@4
|
229
|
danielebarchiesi@4
|
230 // Support the entitycache module if activated.
|
danielebarchiesi@4
|
231 if (!empty($this->entityInfo['entity cache']) && !$revision_id && $ids && !$conditions) {
|
danielebarchiesi@4
|
232 $cached_entities = EntityCacheControllerHelper::entityCacheGet($this, $ids, $conditions);
|
danielebarchiesi@4
|
233 // If any entities were loaded, remove them from the ids still to load.
|
danielebarchiesi@4
|
234 $ids = array_diff($ids, array_keys($cached_entities));
|
danielebarchiesi@4
|
235 $entities += $cached_entities;
|
danielebarchiesi@4
|
236
|
danielebarchiesi@4
|
237 // Add loaded entities to the static cache if we are not loading a
|
danielebarchiesi@4
|
238 // revision.
|
danielebarchiesi@4
|
239 if ($this->cache && !empty($cached_entities) && !$revision_id) {
|
danielebarchiesi@4
|
240 $this->cacheSet($cached_entities);
|
danielebarchiesi@4
|
241 }
|
danielebarchiesi@4
|
242 }
|
danielebarchiesi@4
|
243
|
danielebarchiesi@4
|
244 // Load any remaining entities from the database. This is the case if $ids
|
danielebarchiesi@4
|
245 // is set to FALSE (so we load all entities), if there are any ids left to
|
danielebarchiesi@4
|
246 // load or if loading a revision.
|
danielebarchiesi@4
|
247 if (!($this->cacheComplete && $ids === FALSE && !$conditions) && ($ids === FALSE || $ids || $revision_id)) {
|
danielebarchiesi@4
|
248 $queried_entities = array();
|
danielebarchiesi@4
|
249 foreach ($this->query($ids, $conditions, $revision_id) as $record) {
|
danielebarchiesi@4
|
250 // Skip entities already retrieved from cache.
|
danielebarchiesi@4
|
251 if (isset($entities[$record->{$this->idKey}])) {
|
danielebarchiesi@4
|
252 continue;
|
danielebarchiesi@4
|
253 }
|
danielebarchiesi@4
|
254
|
danielebarchiesi@4
|
255 // For DB-based entities take care of serialized columns.
|
danielebarchiesi@4
|
256 if (!empty($this->entityInfo['base table'])) {
|
danielebarchiesi@4
|
257 $schema = drupal_get_schema($this->entityInfo['base table']);
|
danielebarchiesi@4
|
258
|
danielebarchiesi@4
|
259 foreach ($schema['fields'] as $field => $info) {
|
danielebarchiesi@4
|
260 if (!empty($info['serialize']) && isset($record->$field)) {
|
danielebarchiesi@4
|
261 $record->$field = unserialize($record->$field);
|
danielebarchiesi@4
|
262 // Support automatic merging of 'data' fields into the entity.
|
danielebarchiesi@4
|
263 if (!empty($info['merge']) && is_array($record->$field)) {
|
danielebarchiesi@4
|
264 foreach ($record->$field as $key => $value) {
|
danielebarchiesi@4
|
265 $record->$key = $value;
|
danielebarchiesi@4
|
266 }
|
danielebarchiesi@4
|
267 unset($record->$field);
|
danielebarchiesi@4
|
268 }
|
danielebarchiesi@4
|
269 }
|
danielebarchiesi@4
|
270 }
|
danielebarchiesi@4
|
271 }
|
danielebarchiesi@4
|
272
|
danielebarchiesi@4
|
273 $queried_entities[$record->{$this->idKey}] = $record;
|
danielebarchiesi@4
|
274 }
|
danielebarchiesi@4
|
275 }
|
danielebarchiesi@4
|
276
|
danielebarchiesi@4
|
277 // Pass all entities loaded from the database through $this->attachLoad(),
|
danielebarchiesi@4
|
278 // which attaches fields (if supported by the entity type) and calls the
|
danielebarchiesi@4
|
279 // entity type specific load callback, for example hook_node_load().
|
danielebarchiesi@4
|
280 if (!empty($queried_entities)) {
|
danielebarchiesi@4
|
281 $this->attachLoad($queried_entities, $revision_id);
|
danielebarchiesi@4
|
282 $entities += $queried_entities;
|
danielebarchiesi@4
|
283 }
|
danielebarchiesi@4
|
284
|
danielebarchiesi@4
|
285 // Entitycache module support: Add entities to the entity cache if we are
|
danielebarchiesi@4
|
286 // not loading a revision.
|
danielebarchiesi@4
|
287 if (!empty($this->entityInfo['entity cache']) && !empty($queried_entities) && !$revision_id) {
|
danielebarchiesi@4
|
288 EntityCacheControllerHelper::entityCacheSet($this, $queried_entities);
|
danielebarchiesi@4
|
289 }
|
danielebarchiesi@4
|
290
|
danielebarchiesi@4
|
291 if ($this->cache) {
|
danielebarchiesi@4
|
292 // Add entities to the cache if we are not loading a revision.
|
danielebarchiesi@4
|
293 if (!empty($queried_entities) && !$revision_id) {
|
danielebarchiesi@4
|
294 $this->cacheSet($queried_entities);
|
danielebarchiesi@4
|
295
|
danielebarchiesi@4
|
296 // Remember if we have cached all entities now.
|
danielebarchiesi@4
|
297 if (!$conditions && $ids === FALSE) {
|
danielebarchiesi@4
|
298 $this->cacheComplete = TRUE;
|
danielebarchiesi@4
|
299 }
|
danielebarchiesi@4
|
300 }
|
danielebarchiesi@4
|
301 }
|
danielebarchiesi@4
|
302 // Ensure that the returned array is ordered the same as the original
|
danielebarchiesi@4
|
303 // $ids array if this was passed in and remove any invalid ids.
|
danielebarchiesi@4
|
304 if ($passed_ids && $passed_ids = array_intersect_key($passed_ids, $entities)) {
|
danielebarchiesi@4
|
305 foreach ($passed_ids as $id => $value) {
|
danielebarchiesi@4
|
306 $passed_ids[$id] = $entities[$id];
|
danielebarchiesi@4
|
307 }
|
danielebarchiesi@4
|
308 $entities = $passed_ids;
|
danielebarchiesi@4
|
309 }
|
danielebarchiesi@4
|
310 return $entities;
|
danielebarchiesi@4
|
311 }
|
danielebarchiesi@4
|
312
|
danielebarchiesi@4
|
313 /**
|
danielebarchiesi@4
|
314 * Overrides DrupalDefaultEntityController::resetCache().
|
danielebarchiesi@4
|
315 */
|
danielebarchiesi@4
|
316 public function resetCache(array $ids = NULL) {
|
danielebarchiesi@4
|
317 $this->cacheComplete = FALSE;
|
danielebarchiesi@4
|
318 parent::resetCache($ids);
|
danielebarchiesi@4
|
319 // Support the entitycache module.
|
danielebarchiesi@4
|
320 if (!empty($this->entityInfo['entity cache'])) {
|
danielebarchiesi@4
|
321 EntityCacheControllerHelper::resetEntityCache($this, $ids);
|
danielebarchiesi@4
|
322 }
|
danielebarchiesi@4
|
323 }
|
danielebarchiesi@4
|
324
|
danielebarchiesi@4
|
325 /**
|
danielebarchiesi@4
|
326 * Implements EntityAPIControllerInterface.
|
danielebarchiesi@4
|
327 */
|
danielebarchiesi@4
|
328 public function invoke($hook, $entity) {
|
danielebarchiesi@4
|
329 // entity_revision_delete() invokes hook_entity_revision_delete() and
|
danielebarchiesi@4
|
330 // hook_field_attach_delete_revision() just as node module does. So we need
|
danielebarchiesi@4
|
331 // to adjust the name of our revision deletion field attach hook in order to
|
danielebarchiesi@4
|
332 // stick to this pattern.
|
danielebarchiesi@4
|
333 $field_attach_hook = ($hook == 'revision_delete' ? 'delete_revision' : $hook);
|
danielebarchiesi@4
|
334 if (!empty($this->entityInfo['fieldable']) && function_exists($function = 'field_attach_' . $field_attach_hook)) {
|
danielebarchiesi@4
|
335 $function($this->entityType, $entity);
|
danielebarchiesi@4
|
336 }
|
danielebarchiesi@4
|
337
|
danielebarchiesi@4
|
338 if (!empty($this->entityInfo['bundle of']) && entity_type_is_fieldable($this->entityInfo['bundle of'])) {
|
danielebarchiesi@4
|
339 $type = $this->entityInfo['bundle of'];
|
danielebarchiesi@4
|
340 // Call field API bundle attachers for the entity we are a bundle of.
|
danielebarchiesi@4
|
341 if ($hook == 'insert') {
|
danielebarchiesi@4
|
342 field_attach_create_bundle($type, $entity->{$this->bundleKey});
|
danielebarchiesi@4
|
343 }
|
danielebarchiesi@4
|
344 elseif ($hook == 'delete') {
|
danielebarchiesi@4
|
345 field_attach_delete_bundle($type, $entity->{$this->bundleKey});
|
danielebarchiesi@4
|
346 }
|
danielebarchiesi@4
|
347 elseif ($hook == 'update' && $entity->original->{$this->bundleKey} != $entity->{$this->bundleKey}) {
|
danielebarchiesi@4
|
348 field_attach_rename_bundle($type, $entity->original->{$this->bundleKey}, $entity->{$this->bundleKey});
|
danielebarchiesi@4
|
349 }
|
danielebarchiesi@4
|
350 }
|
danielebarchiesi@4
|
351 // Invoke the hook.
|
danielebarchiesi@4
|
352 module_invoke_all($this->entityType . '_' . $hook, $entity);
|
danielebarchiesi@4
|
353 // Invoke the respective entity level hook.
|
danielebarchiesi@4
|
354 if ($hook == 'presave' || $hook == 'insert' || $hook == 'update' || $hook == 'delete') {
|
danielebarchiesi@4
|
355 module_invoke_all('entity_' . $hook, $entity, $this->entityType);
|
danielebarchiesi@4
|
356 }
|
danielebarchiesi@4
|
357 // Invoke rules.
|
danielebarchiesi@4
|
358 if (module_exists('rules')) {
|
danielebarchiesi@4
|
359 rules_invoke_event($this->entityType . '_' . $hook, $entity);
|
danielebarchiesi@4
|
360 }
|
danielebarchiesi@4
|
361 }
|
danielebarchiesi@4
|
362
|
danielebarchiesi@4
|
363 /**
|
danielebarchiesi@4
|
364 * Implements EntityAPIControllerInterface.
|
danielebarchiesi@4
|
365 *
|
danielebarchiesi@4
|
366 * @param $transaction
|
danielebarchiesi@4
|
367 * Optionally a DatabaseTransaction object to use. Allows overrides to pass
|
danielebarchiesi@4
|
368 * in their transaction object.
|
danielebarchiesi@4
|
369 */
|
danielebarchiesi@4
|
370 public function delete($ids, DatabaseTransaction $transaction = NULL) {
|
danielebarchiesi@4
|
371 $entities = $ids ? $this->load($ids) : FALSE;
|
danielebarchiesi@4
|
372 if (!$entities) {
|
danielebarchiesi@4
|
373 // Do nothing, in case invalid or no ids have been passed.
|
danielebarchiesi@4
|
374 return;
|
danielebarchiesi@4
|
375 }
|
danielebarchiesi@4
|
376 // This transaction causes troubles on MySQL, see
|
danielebarchiesi@4
|
377 // http://drupal.org/node/1007830. So we deactivate this by default until
|
danielebarchiesi@4
|
378 // is shipped in a point release.
|
danielebarchiesi@4
|
379 // $transaction = isset($transaction) ? $transaction : db_transaction();
|
danielebarchiesi@4
|
380
|
danielebarchiesi@4
|
381 try {
|
danielebarchiesi@4
|
382 $ids = array_keys($entities);
|
danielebarchiesi@4
|
383
|
danielebarchiesi@4
|
384 db_delete($this->entityInfo['base table'])
|
danielebarchiesi@4
|
385 ->condition($this->idKey, $ids, 'IN')
|
danielebarchiesi@4
|
386 ->execute();
|
danielebarchiesi@4
|
387
|
danielebarchiesi@4
|
388 if (isset($this->revisionTable)) {
|
danielebarchiesi@4
|
389 db_delete($this->revisionTable)
|
danielebarchiesi@4
|
390 ->condition($this->idKey, $ids, 'IN')
|
danielebarchiesi@4
|
391 ->execute();
|
danielebarchiesi@4
|
392 }
|
danielebarchiesi@4
|
393 // Reset the cache as soon as the changes have been applied.
|
danielebarchiesi@4
|
394 $this->resetCache($ids);
|
danielebarchiesi@4
|
395
|
danielebarchiesi@4
|
396 foreach ($entities as $id => $entity) {
|
danielebarchiesi@4
|
397 $this->invoke('delete', $entity);
|
danielebarchiesi@4
|
398 }
|
danielebarchiesi@4
|
399 // Ignore slave server temporarily.
|
danielebarchiesi@4
|
400 db_ignore_slave();
|
danielebarchiesi@4
|
401 }
|
danielebarchiesi@4
|
402 catch (Exception $e) {
|
danielebarchiesi@4
|
403 if (isset($transaction)) {
|
danielebarchiesi@4
|
404 $transaction->rollback();
|
danielebarchiesi@4
|
405 }
|
danielebarchiesi@4
|
406 watchdog_exception($this->entityType, $e);
|
danielebarchiesi@4
|
407 throw $e;
|
danielebarchiesi@4
|
408 }
|
danielebarchiesi@4
|
409 }
|
danielebarchiesi@4
|
410
|
danielebarchiesi@4
|
411 /**
|
danielebarchiesi@4
|
412 * Implements EntityAPIControllerRevisionableInterface::deleteRevision().
|
danielebarchiesi@4
|
413 */
|
danielebarchiesi@4
|
414 public function deleteRevision($revision_id) {
|
danielebarchiesi@4
|
415 if ($entity_revision = entity_revision_load($this->entityType, $revision_id)) {
|
danielebarchiesi@4
|
416 // Prevent deleting the default revision.
|
danielebarchiesi@4
|
417 if (entity_revision_is_default($this->entityType, $entity_revision)) {
|
danielebarchiesi@4
|
418 return FALSE;
|
danielebarchiesi@4
|
419 }
|
danielebarchiesi@4
|
420
|
danielebarchiesi@4
|
421 db_delete($this->revisionTable)
|
danielebarchiesi@4
|
422 ->condition($this->revisionKey, $revision_id)
|
danielebarchiesi@4
|
423 ->execute();
|
danielebarchiesi@4
|
424
|
danielebarchiesi@4
|
425 $this->invoke('revision_delete', $entity_revision);
|
danielebarchiesi@4
|
426 return TRUE;
|
danielebarchiesi@4
|
427 }
|
danielebarchiesi@4
|
428 return FALSE;
|
danielebarchiesi@4
|
429 }
|
danielebarchiesi@4
|
430
|
danielebarchiesi@4
|
431 /**
|
danielebarchiesi@4
|
432 * Implements EntityAPIControllerInterface.
|
danielebarchiesi@4
|
433 *
|
danielebarchiesi@4
|
434 * @param $transaction
|
danielebarchiesi@4
|
435 * Optionally a DatabaseTransaction object to use. Allows overrides to pass
|
danielebarchiesi@4
|
436 * in their transaction object.
|
danielebarchiesi@4
|
437 */
|
danielebarchiesi@4
|
438 public function save($entity, DatabaseTransaction $transaction = NULL) {
|
danielebarchiesi@4
|
439 $transaction = isset($transaction) ? $transaction : db_transaction();
|
danielebarchiesi@4
|
440 try {
|
danielebarchiesi@4
|
441 // Load the stored entity, if any.
|
danielebarchiesi@4
|
442 if (!empty($entity->{$this->idKey}) && !isset($entity->original)) {
|
danielebarchiesi@4
|
443 // In order to properly work in case of name changes, load the original
|
danielebarchiesi@4
|
444 // entity using the id key if it is available.
|
danielebarchiesi@4
|
445 $entity->original = entity_load_unchanged($this->entityType, $entity->{$this->idKey});
|
danielebarchiesi@4
|
446 }
|
danielebarchiesi@4
|
447 $entity->is_new = !empty($entity->is_new) || empty($entity->{$this->idKey});
|
danielebarchiesi@4
|
448 $this->invoke('presave', $entity);
|
danielebarchiesi@4
|
449
|
danielebarchiesi@4
|
450 if ($entity->is_new) {
|
danielebarchiesi@4
|
451 $return = drupal_write_record($this->entityInfo['base table'], $entity);
|
danielebarchiesi@4
|
452 if ($this->revisionKey) {
|
danielebarchiesi@4
|
453 $this->saveRevision($entity);
|
danielebarchiesi@4
|
454 }
|
danielebarchiesi@4
|
455 $this->invoke('insert', $entity);
|
danielebarchiesi@4
|
456 }
|
danielebarchiesi@4
|
457 else {
|
danielebarchiesi@4
|
458 // Update the base table if the entity doesn't have revisions or
|
danielebarchiesi@4
|
459 // we are updating the default revision.
|
danielebarchiesi@4
|
460 if (!$this->revisionKey || !empty($entity->{$this->defaultRevisionKey})) {
|
danielebarchiesi@4
|
461 $return = drupal_write_record($this->entityInfo['base table'], $entity, $this->idKey);
|
danielebarchiesi@4
|
462 }
|
danielebarchiesi@4
|
463 if ($this->revisionKey) {
|
danielebarchiesi@4
|
464 $return = $this->saveRevision($entity);
|
danielebarchiesi@4
|
465 }
|
danielebarchiesi@4
|
466 $this->resetCache(array($entity->{$this->idKey}));
|
danielebarchiesi@4
|
467 $this->invoke('update', $entity);
|
danielebarchiesi@4
|
468
|
danielebarchiesi@4
|
469 // Field API always saves as default revision, so if the revision saved
|
danielebarchiesi@4
|
470 // is not default we have to restore the field values of the default
|
danielebarchiesi@4
|
471 // revision now by invoking field_attach_update() once again.
|
danielebarchiesi@4
|
472 if ($this->revisionKey && !$entity->{$this->defaultRevisionKey} && !empty($this->entityInfo['fieldable'])) {
|
danielebarchiesi@4
|
473 field_attach_update($this->entityType, $entity->original);
|
danielebarchiesi@4
|
474 }
|
danielebarchiesi@4
|
475 }
|
danielebarchiesi@4
|
476
|
danielebarchiesi@4
|
477 // Ignore slave server temporarily.
|
danielebarchiesi@4
|
478 db_ignore_slave();
|
danielebarchiesi@4
|
479 unset($entity->is_new);
|
danielebarchiesi@4
|
480 unset($entity->is_new_revision);
|
danielebarchiesi@4
|
481 unset($entity->original);
|
danielebarchiesi@4
|
482
|
danielebarchiesi@4
|
483 return $return;
|
danielebarchiesi@4
|
484 }
|
danielebarchiesi@4
|
485 catch (Exception $e) {
|
danielebarchiesi@4
|
486 $transaction->rollback();
|
danielebarchiesi@4
|
487 watchdog_exception($this->entityType, $e);
|
danielebarchiesi@4
|
488 throw $e;
|
danielebarchiesi@4
|
489 }
|
danielebarchiesi@4
|
490 }
|
danielebarchiesi@4
|
491
|
danielebarchiesi@4
|
492 /**
|
danielebarchiesi@4
|
493 * Saves an entity revision.
|
danielebarchiesi@4
|
494 *
|
danielebarchiesi@4
|
495 * @param Entity $entity
|
danielebarchiesi@4
|
496 * Entity revision to save.
|
danielebarchiesi@4
|
497 */
|
danielebarchiesi@4
|
498 protected function saveRevision($entity) {
|
danielebarchiesi@4
|
499 // Convert the entity into an array as it might not have the same properties
|
danielebarchiesi@4
|
500 // as the entity, it is just a raw structure.
|
danielebarchiesi@4
|
501 $record = (array) $entity;
|
danielebarchiesi@4
|
502 // File fields assumes we are using $entity->revision instead of
|
danielebarchiesi@4
|
503 // $entity->is_new_revision, so we also support it and make sure it's set to
|
danielebarchiesi@4
|
504 // the same value.
|
danielebarchiesi@4
|
505 $entity->is_new_revision = !empty($entity->is_new_revision) || !empty($entity->revision) || $entity->is_new;
|
danielebarchiesi@4
|
506 $entity->revision = &$entity->is_new_revision;
|
danielebarchiesi@4
|
507 $entity->{$this->defaultRevisionKey} = !empty($entity->{$this->defaultRevisionKey}) || $entity->is_new;
|
danielebarchiesi@4
|
508
|
danielebarchiesi@4
|
509
|
danielebarchiesi@4
|
510
|
danielebarchiesi@4
|
511 // When saving a new revision, set any existing revision ID to NULL so as to
|
danielebarchiesi@4
|
512 // ensure that a new revision will actually be created.
|
danielebarchiesi@4
|
513 if ($entity->is_new_revision && isset($record[$this->revisionKey])) {
|
danielebarchiesi@4
|
514 $record[$this->revisionKey] = NULL;
|
danielebarchiesi@4
|
515 }
|
danielebarchiesi@4
|
516
|
danielebarchiesi@4
|
517 if ($entity->is_new_revision) {
|
danielebarchiesi@4
|
518 drupal_write_record($this->revisionTable, $record);
|
danielebarchiesi@4
|
519 $update_default_revision = $entity->{$this->defaultRevisionKey};
|
danielebarchiesi@4
|
520 }
|
danielebarchiesi@4
|
521 else {
|
danielebarchiesi@4
|
522 drupal_write_record($this->revisionTable, $record, $this->revisionKey);
|
danielebarchiesi@4
|
523 // @todo: Fix original entity to be of the same revision and check whether
|
danielebarchiesi@4
|
524 // the default revision key has been set.
|
danielebarchiesi@4
|
525 $update_default_revision = $entity->{$this->defaultRevisionKey} && $entity->{$this->revisionKey} != $entity->original->{$this->revisionKey};
|
danielebarchiesi@4
|
526 }
|
danielebarchiesi@4
|
527 // Make sure to update the new revision key for the entity.
|
danielebarchiesi@4
|
528 $entity->{$this->revisionKey} = $record[$this->revisionKey];
|
danielebarchiesi@4
|
529
|
danielebarchiesi@4
|
530 // Mark this revision as the default one.
|
danielebarchiesi@4
|
531 if ($update_default_revision) {
|
danielebarchiesi@4
|
532 db_update($this->entityInfo['base table'])
|
danielebarchiesi@4
|
533 ->fields(array($this->revisionKey => $record[$this->revisionKey]))
|
danielebarchiesi@4
|
534 ->condition($this->idKey, $entity->{$this->idKey})
|
danielebarchiesi@4
|
535 ->execute();
|
danielebarchiesi@4
|
536 }
|
danielebarchiesi@4
|
537 return $entity->is_new_revision ? SAVED_NEW : SAVED_UPDATED;
|
danielebarchiesi@4
|
538 }
|
danielebarchiesi@4
|
539
|
danielebarchiesi@4
|
540 /**
|
danielebarchiesi@4
|
541 * Implements EntityAPIControllerInterface.
|
danielebarchiesi@4
|
542 */
|
danielebarchiesi@4
|
543 public function create(array $values = array()) {
|
danielebarchiesi@4
|
544 // Add is_new property if it is not set.
|
danielebarchiesi@4
|
545 $values += array('is_new' => TRUE);
|
danielebarchiesi@4
|
546 if (isset($this->entityInfo['entity class']) && $class = $this->entityInfo['entity class']) {
|
danielebarchiesi@4
|
547 return new $class($values, $this->entityType);
|
danielebarchiesi@4
|
548 }
|
danielebarchiesi@4
|
549 return (object) $values;
|
danielebarchiesi@4
|
550 }
|
danielebarchiesi@4
|
551
|
danielebarchiesi@4
|
552 /**
|
danielebarchiesi@4
|
553 * Implements EntityAPIControllerInterface.
|
danielebarchiesi@4
|
554 *
|
danielebarchiesi@4
|
555 * @return
|
danielebarchiesi@4
|
556 * A serialized string in JSON format suitable for the import() method.
|
danielebarchiesi@4
|
557 */
|
danielebarchiesi@4
|
558 public function export($entity, $prefix = '') {
|
danielebarchiesi@4
|
559 $vars = get_object_vars($entity);
|
danielebarchiesi@4
|
560 unset($vars['is_new']);
|
danielebarchiesi@4
|
561 return entity_var_json_export($vars, $prefix);
|
danielebarchiesi@4
|
562 }
|
danielebarchiesi@4
|
563
|
danielebarchiesi@4
|
564 /**
|
danielebarchiesi@4
|
565 * Implements EntityAPIControllerInterface.
|
danielebarchiesi@4
|
566 *
|
danielebarchiesi@4
|
567 * @param $export
|
danielebarchiesi@4
|
568 * A serialized string in JSON format as produced by the export() method.
|
danielebarchiesi@4
|
569 */
|
danielebarchiesi@4
|
570 public function import($export) {
|
danielebarchiesi@4
|
571 $vars = drupal_json_decode($export);
|
danielebarchiesi@4
|
572 if (is_array($vars)) {
|
danielebarchiesi@4
|
573 return $this->create($vars);
|
danielebarchiesi@4
|
574 }
|
danielebarchiesi@4
|
575 return FALSE;
|
danielebarchiesi@4
|
576 }
|
danielebarchiesi@4
|
577
|
danielebarchiesi@4
|
578 /**
|
danielebarchiesi@4
|
579 * Implements EntityAPIControllerInterface.
|
danielebarchiesi@4
|
580 *
|
danielebarchiesi@4
|
581 * @param $content
|
danielebarchiesi@4
|
582 * Optionally. Allows pre-populating the built content to ease overridding
|
danielebarchiesi@4
|
583 * this method.
|
danielebarchiesi@4
|
584 */
|
danielebarchiesi@4
|
585 public function buildContent($entity, $view_mode = 'full', $langcode = NULL, $content = array()) {
|
danielebarchiesi@4
|
586 // Remove previously built content, if exists.
|
danielebarchiesi@4
|
587 $entity->content = $content;
|
danielebarchiesi@4
|
588 $langcode = isset($langcode) ? $langcode : $GLOBALS['language_content']->language;
|
danielebarchiesi@4
|
589
|
danielebarchiesi@4
|
590 // By default add in properties for all defined extra fields.
|
danielebarchiesi@4
|
591 if ($extra_field_controller = entity_get_extra_fields_controller($this->entityType)) {
|
danielebarchiesi@4
|
592 $wrapper = entity_metadata_wrapper($this->entityType, $entity);
|
danielebarchiesi@4
|
593 $extra = $extra_field_controller->fieldExtraFields();
|
danielebarchiesi@4
|
594 $type_extra = &$extra[$this->entityType][$this->entityType]['display'];
|
danielebarchiesi@4
|
595 $bundle_extra = &$extra[$this->entityType][$wrapper->getBundle()]['display'];
|
danielebarchiesi@4
|
596
|
danielebarchiesi@4
|
597 foreach ($wrapper as $name => $property) {
|
danielebarchiesi@4
|
598 if (isset($type_extra[$name]) || isset($bundle_extra[$name])) {
|
danielebarchiesi@4
|
599 $this->renderEntityProperty($wrapper, $name, $property, $view_mode, $langcode, $entity->content);
|
danielebarchiesi@4
|
600 }
|
danielebarchiesi@4
|
601 }
|
danielebarchiesi@4
|
602 }
|
danielebarchiesi@4
|
603
|
danielebarchiesi@4
|
604 // Add in fields.
|
danielebarchiesi@4
|
605 if (!empty($this->entityInfo['fieldable'])) {
|
danielebarchiesi@4
|
606 // Perform the preparation tasks if they have not been performed yet.
|
danielebarchiesi@4
|
607 // An internal flag prevents the operation from running twice.
|
danielebarchiesi@4
|
608 $key = isset($entity->{$this->idKey}) ? $entity->{$this->idKey} : NULL;
|
danielebarchiesi@4
|
609 field_attach_prepare_view($this->entityType, array($key => $entity), $view_mode);
|
danielebarchiesi@4
|
610 $entity->content += field_attach_view($this->entityType, $entity, $view_mode, $langcode);
|
danielebarchiesi@4
|
611 }
|
danielebarchiesi@4
|
612 // Invoke hook_ENTITY_view() to allow modules to add their additions.
|
danielebarchiesi@4
|
613 if (module_exists('rules')) {
|
danielebarchiesi@4
|
614 rules_invoke_all($this->entityType . '_view', $entity, $view_mode, $langcode);
|
danielebarchiesi@4
|
615 }
|
danielebarchiesi@4
|
616 else {
|
danielebarchiesi@4
|
617 module_invoke_all($this->entityType . '_view', $entity, $view_mode, $langcode);
|
danielebarchiesi@4
|
618 }
|
danielebarchiesi@4
|
619 module_invoke_all('entity_view', $entity, $this->entityType, $view_mode, $langcode);
|
danielebarchiesi@4
|
620 $build = $entity->content;
|
danielebarchiesi@4
|
621 unset($entity->content);
|
danielebarchiesi@4
|
622 return $build;
|
danielebarchiesi@4
|
623 }
|
danielebarchiesi@4
|
624
|
danielebarchiesi@4
|
625 /**
|
danielebarchiesi@4
|
626 * Renders a single entity property.
|
danielebarchiesi@4
|
627 */
|
danielebarchiesi@4
|
628 protected function renderEntityProperty($wrapper, $name, $property, $view_mode, $langcode, &$content) {
|
danielebarchiesi@4
|
629 $info = $property->info();
|
danielebarchiesi@4
|
630
|
danielebarchiesi@4
|
631 $content[$name] = array(
|
danielebarchiesi@4
|
632 '#label_hidden' => FALSE,
|
danielebarchiesi@4
|
633 '#label' => $info['label'],
|
danielebarchiesi@4
|
634 '#entity_wrapped' => $wrapper,
|
danielebarchiesi@4
|
635 '#theme' => 'entity_property',
|
danielebarchiesi@4
|
636 '#property_name' => $name,
|
danielebarchiesi@4
|
637 '#access' => $property->access('view'),
|
danielebarchiesi@4
|
638 '#entity_type' => $this->entityType,
|
danielebarchiesi@4
|
639 );
|
danielebarchiesi@4
|
640 $content['#attached']['css']['entity.theme'] = drupal_get_path('module', 'entity') . '/theme/entity.theme.css';
|
danielebarchiesi@4
|
641 }
|
danielebarchiesi@4
|
642
|
danielebarchiesi@4
|
643 /**
|
danielebarchiesi@4
|
644 * Implements EntityAPIControllerInterface.
|
danielebarchiesi@4
|
645 */
|
danielebarchiesi@4
|
646 public function view($entities, $view_mode = 'full', $langcode = NULL, $page = NULL) {
|
danielebarchiesi@4
|
647 // For Field API and entity_prepare_view, the entities have to be keyed by
|
danielebarchiesi@4
|
648 // (numeric) id.
|
danielebarchiesi@4
|
649 $entities = entity_key_array_by_property($entities, $this->idKey);
|
danielebarchiesi@4
|
650 if (!empty($this->entityInfo['fieldable'])) {
|
danielebarchiesi@4
|
651 field_attach_prepare_view($this->entityType, $entities, $view_mode);
|
danielebarchiesi@4
|
652 }
|
danielebarchiesi@4
|
653 entity_prepare_view($this->entityType, $entities);
|
danielebarchiesi@4
|
654 $langcode = isset($langcode) ? $langcode : $GLOBALS['language_content']->language;
|
danielebarchiesi@4
|
655
|
danielebarchiesi@4
|
656 $view = array();
|
danielebarchiesi@4
|
657 foreach ($entities as $entity) {
|
danielebarchiesi@4
|
658 $build = entity_build_content($this->entityType, $entity, $view_mode, $langcode);
|
danielebarchiesi@4
|
659 $build += array(
|
danielebarchiesi@4
|
660 // If the entity type provides an implementation, use this instead the
|
danielebarchiesi@4
|
661 // generic one.
|
danielebarchiesi@4
|
662 // @see template_preprocess_entity()
|
danielebarchiesi@4
|
663 '#theme' => 'entity',
|
danielebarchiesi@4
|
664 '#entity_type' => $this->entityType,
|
danielebarchiesi@4
|
665 '#entity' => $entity,
|
danielebarchiesi@4
|
666 '#view_mode' => $view_mode,
|
danielebarchiesi@4
|
667 '#language' => $langcode,
|
danielebarchiesi@4
|
668 '#page' => $page,
|
danielebarchiesi@4
|
669 );
|
danielebarchiesi@4
|
670 // Allow modules to modify the structured entity.
|
danielebarchiesi@4
|
671 drupal_alter(array($this->entityType . '_view', 'entity_view'), $build, $this->entityType);
|
danielebarchiesi@4
|
672 $key = isset($entity->{$this->idKey}) ? $entity->{$this->idKey} : NULL;
|
danielebarchiesi@4
|
673 $view[$this->entityType][$key] = $build;
|
danielebarchiesi@4
|
674 }
|
danielebarchiesi@4
|
675 return $view;
|
danielebarchiesi@4
|
676 }
|
danielebarchiesi@4
|
677 }
|
danielebarchiesi@4
|
678
|
danielebarchiesi@4
|
679 /**
|
danielebarchiesi@4
|
680 * A controller implementing exportables stored in the database.
|
danielebarchiesi@4
|
681 */
|
danielebarchiesi@4
|
682 class EntityAPIControllerExportable extends EntityAPIController {
|
danielebarchiesi@4
|
683
|
danielebarchiesi@4
|
684 protected $entityCacheByName = array();
|
danielebarchiesi@4
|
685 protected $nameKey, $statusKey, $moduleKey;
|
danielebarchiesi@4
|
686
|
danielebarchiesi@4
|
687 /**
|
danielebarchiesi@4
|
688 * Overridden.
|
danielebarchiesi@4
|
689 *
|
danielebarchiesi@4
|
690 * Allows specifying a name key serving as uniform identifier for this entity
|
danielebarchiesi@4
|
691 * type while still internally we are using numeric identifieres.
|
danielebarchiesi@4
|
692 */
|
danielebarchiesi@4
|
693 public function __construct($entityType) {
|
danielebarchiesi@4
|
694 parent::__construct($entityType);
|
danielebarchiesi@4
|
695 // Use the name key as primary identifier.
|
danielebarchiesi@4
|
696 $this->nameKey = isset($this->entityInfo['entity keys']['name']) ? $this->entityInfo['entity keys']['name'] : $this->idKey;
|
danielebarchiesi@4
|
697 if (!empty($this->entityInfo['exportable'])) {
|
danielebarchiesi@4
|
698 $this->statusKey = isset($this->entityInfo['entity keys']['status']) ? $this->entityInfo['entity keys']['status'] : 'status';
|
danielebarchiesi@4
|
699 $this->moduleKey = isset($this->entityInfo['entity keys']['module']) ? $this->entityInfo['entity keys']['module'] : 'module';
|
danielebarchiesi@4
|
700 }
|
danielebarchiesi@4
|
701 }
|
danielebarchiesi@4
|
702
|
danielebarchiesi@4
|
703 /**
|
danielebarchiesi@4
|
704 * Support loading by name key.
|
danielebarchiesi@4
|
705 */
|
danielebarchiesi@4
|
706 protected function buildQuery($ids, $conditions = array(), $revision_id = FALSE) {
|
danielebarchiesi@4
|
707 // Add the id condition ourself, as we might have a separate name key.
|
danielebarchiesi@4
|
708 $query = parent::buildQuery(array(), $conditions, $revision_id);
|
danielebarchiesi@4
|
709 if ($ids) {
|
danielebarchiesi@4
|
710 // Support loading by numeric ids as well as by machine names.
|
danielebarchiesi@4
|
711 $key = is_numeric(reset($ids)) ? $this->idKey : $this->nameKey;
|
danielebarchiesi@4
|
712 $query->condition("base.$key", $ids, 'IN');
|
danielebarchiesi@4
|
713 }
|
danielebarchiesi@4
|
714 return $query;
|
danielebarchiesi@4
|
715 }
|
danielebarchiesi@4
|
716
|
danielebarchiesi@4
|
717 /**
|
danielebarchiesi@4
|
718 * Overridden to support passing numeric ids as well as names as $ids.
|
danielebarchiesi@4
|
719 */
|
danielebarchiesi@4
|
720 public function load($ids = array(), $conditions = array()) {
|
danielebarchiesi@4
|
721 $entities = array();
|
danielebarchiesi@4
|
722
|
danielebarchiesi@4
|
723 // Only do something if loaded by names.
|
danielebarchiesi@4
|
724 if (!$ids || $this->nameKey == $this->idKey || is_numeric(reset($ids))) {
|
danielebarchiesi@4
|
725 return parent::load($ids, $conditions);
|
danielebarchiesi@4
|
726 }
|
danielebarchiesi@4
|
727
|
danielebarchiesi@4
|
728 // Revisions are not statically cached, and require a different query to
|
danielebarchiesi@4
|
729 // other conditions, so separate the revision id into its own variable.
|
danielebarchiesi@4
|
730 if ($this->revisionKey && isset($conditions[$this->revisionKey])) {
|
danielebarchiesi@4
|
731 $revision_id = $conditions[$this->revisionKey];
|
danielebarchiesi@4
|
732 unset($conditions[$this->revisionKey]);
|
danielebarchiesi@4
|
733 }
|
danielebarchiesi@4
|
734 else {
|
danielebarchiesi@4
|
735 $revision_id = FALSE;
|
danielebarchiesi@4
|
736 }
|
danielebarchiesi@4
|
737 $passed_ids = !empty($ids) ? array_flip($ids) : FALSE;
|
danielebarchiesi@4
|
738
|
danielebarchiesi@4
|
739 // Care about the static cache.
|
danielebarchiesi@4
|
740 if ($this->cache && !$revision_id) {
|
danielebarchiesi@4
|
741 $entities = $this->cacheGetByName($ids, $conditions);
|
danielebarchiesi@4
|
742 }
|
danielebarchiesi@4
|
743 // If any entities were loaded, remove them from the ids still to load.
|
danielebarchiesi@4
|
744 if ($entities) {
|
danielebarchiesi@4
|
745 $ids = array_keys(array_diff_key($passed_ids, $entities));
|
danielebarchiesi@4
|
746 }
|
danielebarchiesi@4
|
747
|
danielebarchiesi@4
|
748 $entities_by_id = parent::load($ids, $conditions);
|
danielebarchiesi@4
|
749 $entities += entity_key_array_by_property($entities_by_id, $this->nameKey);
|
danielebarchiesi@4
|
750
|
danielebarchiesi@4
|
751 // Ensure that the returned array is keyed by numeric id and ordered the
|
danielebarchiesi@4
|
752 // same as the original $ids array and remove any invalid ids.
|
danielebarchiesi@4
|
753 $return = array();
|
danielebarchiesi@4
|
754 foreach ($passed_ids as $name => $value) {
|
danielebarchiesi@4
|
755 if (isset($entities[$name])) {
|
danielebarchiesi@4
|
756 $return[$entities[$name]->{$this->idKey}] = $entities[$name];
|
danielebarchiesi@4
|
757 }
|
danielebarchiesi@4
|
758 }
|
danielebarchiesi@4
|
759 return $return;
|
danielebarchiesi@4
|
760 }
|
danielebarchiesi@4
|
761
|
danielebarchiesi@4
|
762 /**
|
danielebarchiesi@4
|
763 * Overridden.
|
danielebarchiesi@4
|
764 * @see DrupalDefaultEntityController::cacheGet()
|
danielebarchiesi@4
|
765 */
|
danielebarchiesi@4
|
766 protected function cacheGet($ids, $conditions = array()) {
|
danielebarchiesi@4
|
767 if (!empty($this->entityCache) && $ids !== array()) {
|
danielebarchiesi@4
|
768 $entities = $ids ? array_intersect_key($this->entityCache, array_flip($ids)) : $this->entityCache;
|
danielebarchiesi@4
|
769 return $this->applyConditions($entities, $conditions);
|
danielebarchiesi@4
|
770 }
|
danielebarchiesi@4
|
771 return array();
|
danielebarchiesi@4
|
772 }
|
danielebarchiesi@4
|
773
|
danielebarchiesi@4
|
774 /**
|
danielebarchiesi@4
|
775 * Like cacheGet() but keyed by name.
|
danielebarchiesi@4
|
776 */
|
danielebarchiesi@4
|
777 protected function cacheGetByName($names, $conditions = array()) {
|
danielebarchiesi@4
|
778 if (!empty($this->entityCacheByName) && $names !== array() && $names) {
|
danielebarchiesi@4
|
779 // First get the entities by ids, then apply the conditions.
|
danielebarchiesi@4
|
780 // Generally, we make use of $this->entityCache, but if we are loading by
|
danielebarchiesi@4
|
781 // name, we have to use $this->entityCacheByName.
|
danielebarchiesi@4
|
782 $entities = array_intersect_key($this->entityCacheByName, array_flip($names));
|
danielebarchiesi@4
|
783 return $this->applyConditions($entities, $conditions);
|
danielebarchiesi@4
|
784 }
|
danielebarchiesi@4
|
785 return array();
|
danielebarchiesi@4
|
786 }
|
danielebarchiesi@4
|
787
|
danielebarchiesi@4
|
788 protected function applyConditions($entities, $conditions = array()) {
|
danielebarchiesi@4
|
789 if ($conditions) {
|
danielebarchiesi@4
|
790 foreach ($entities as $key => $entity) {
|
danielebarchiesi@4
|
791 $entity_values = (array) $entity;
|
danielebarchiesi@4
|
792 // We cannot use array_diff_assoc() here because condition values can
|
danielebarchiesi@4
|
793 // also be arrays, e.g. '$conditions = array('status' => array(1, 2))'
|
danielebarchiesi@4
|
794 foreach ($conditions as $condition_key => $condition_value) {
|
danielebarchiesi@4
|
795 if (is_array($condition_value)) {
|
danielebarchiesi@4
|
796 if (!isset($entity_values[$condition_key]) || !in_array($entity_values[$condition_key], $condition_value)) {
|
danielebarchiesi@4
|
797 unset($entities[$key]);
|
danielebarchiesi@4
|
798 }
|
danielebarchiesi@4
|
799 }
|
danielebarchiesi@4
|
800 elseif (!isset($entity_values[$condition_key]) || $entity_values[$condition_key] != $condition_value) {
|
danielebarchiesi@4
|
801 unset($entities[$key]);
|
danielebarchiesi@4
|
802 }
|
danielebarchiesi@4
|
803 }
|
danielebarchiesi@4
|
804 }
|
danielebarchiesi@4
|
805 }
|
danielebarchiesi@4
|
806 return $entities;
|
danielebarchiesi@4
|
807 }
|
danielebarchiesi@4
|
808
|
danielebarchiesi@4
|
809 /**
|
danielebarchiesi@4
|
810 * Overridden.
|
danielebarchiesi@4
|
811 * @see DrupalDefaultEntityController::cacheSet()
|
danielebarchiesi@4
|
812 */
|
danielebarchiesi@4
|
813 protected function cacheSet($entities) {
|
danielebarchiesi@4
|
814 $this->entityCache += $entities;
|
danielebarchiesi@4
|
815 // If we have a name key, also support static caching when loading by name.
|
danielebarchiesi@4
|
816 if ($this->nameKey != $this->idKey) {
|
danielebarchiesi@4
|
817 $this->entityCacheByName += entity_key_array_by_property($entities, $this->nameKey);
|
danielebarchiesi@4
|
818 }
|
danielebarchiesi@4
|
819 }
|
danielebarchiesi@4
|
820
|
danielebarchiesi@4
|
821 /**
|
danielebarchiesi@4
|
822 * Overridden.
|
danielebarchiesi@4
|
823 * @see DrupalDefaultEntityController::attachLoad()
|
danielebarchiesi@4
|
824 *
|
danielebarchiesi@4
|
825 * Changed to call type-specific hook with the entities keyed by name if they
|
danielebarchiesi@4
|
826 * have one.
|
danielebarchiesi@4
|
827 */
|
danielebarchiesi@4
|
828 protected function attachLoad(&$queried_entities, $revision_id = FALSE) {
|
danielebarchiesi@4
|
829 // Attach fields.
|
danielebarchiesi@4
|
830 if ($this->entityInfo['fieldable']) {
|
danielebarchiesi@4
|
831 if ($revision_id) {
|
danielebarchiesi@4
|
832 field_attach_load_revision($this->entityType, $queried_entities);
|
danielebarchiesi@4
|
833 }
|
danielebarchiesi@4
|
834 else {
|
danielebarchiesi@4
|
835 field_attach_load($this->entityType, $queried_entities);
|
danielebarchiesi@4
|
836 }
|
danielebarchiesi@4
|
837 }
|
danielebarchiesi@4
|
838
|
danielebarchiesi@4
|
839 // Call hook_entity_load().
|
danielebarchiesi@4
|
840 foreach (module_implements('entity_load') as $module) {
|
danielebarchiesi@4
|
841 $function = $module . '_entity_load';
|
danielebarchiesi@4
|
842 $function($queried_entities, $this->entityType);
|
danielebarchiesi@4
|
843 }
|
danielebarchiesi@4
|
844 // Call hook_TYPE_load(). The first argument for hook_TYPE_load() are
|
danielebarchiesi@4
|
845 // always the queried entities, followed by additional arguments set in
|
danielebarchiesi@4
|
846 // $this->hookLoadArguments.
|
danielebarchiesi@4
|
847 // For entities with a name key, pass the entities keyed by name to the
|
danielebarchiesi@4
|
848 // specific load hook.
|
danielebarchiesi@4
|
849 if ($this->nameKey != $this->idKey) {
|
danielebarchiesi@4
|
850 $entities_by_name = entity_key_array_by_property($queried_entities, $this->nameKey);
|
danielebarchiesi@4
|
851 }
|
danielebarchiesi@4
|
852 else {
|
danielebarchiesi@4
|
853 $entities_by_name = $queried_entities;
|
danielebarchiesi@4
|
854 }
|
danielebarchiesi@4
|
855 $args = array_merge(array($entities_by_name), $this->hookLoadArguments);
|
danielebarchiesi@4
|
856 foreach (module_implements($this->entityInfo['load hook']) as $module) {
|
danielebarchiesi@4
|
857 call_user_func_array($module . '_' . $this->entityInfo['load hook'], $args);
|
danielebarchiesi@4
|
858 }
|
danielebarchiesi@4
|
859 }
|
danielebarchiesi@4
|
860
|
danielebarchiesi@4
|
861 public function resetCache(array $ids = NULL) {
|
danielebarchiesi@4
|
862 $this->cacheComplete = FALSE;
|
danielebarchiesi@4
|
863 if (isset($ids)) {
|
danielebarchiesi@4
|
864 foreach (array_intersect_key($this->entityCache, array_flip($ids)) as $id => $entity) {
|
danielebarchiesi@4
|
865 unset($this->entityCacheByName[$this->entityCache[$id]->{$this->nameKey}]);
|
danielebarchiesi@4
|
866 unset($this->entityCache[$id]);
|
danielebarchiesi@4
|
867 }
|
danielebarchiesi@4
|
868 }
|
danielebarchiesi@4
|
869 else {
|
danielebarchiesi@4
|
870 $this->entityCache = array();
|
danielebarchiesi@4
|
871 $this->entityCacheByName = array();
|
danielebarchiesi@4
|
872 }
|
danielebarchiesi@4
|
873 }
|
danielebarchiesi@4
|
874
|
danielebarchiesi@4
|
875 /**
|
danielebarchiesi@4
|
876 * Overridden to care about reverted entities.
|
danielebarchiesi@4
|
877 */
|
danielebarchiesi@4
|
878 public function delete($ids, DatabaseTransaction $transaction = NULL) {
|
danielebarchiesi@4
|
879 $entities = $ids ? $this->load($ids) : FALSE;
|
danielebarchiesi@4
|
880 if ($entities) {
|
danielebarchiesi@4
|
881 parent::delete($ids, $transaction);
|
danielebarchiesi@4
|
882
|
danielebarchiesi@4
|
883 foreach ($entities as $id => $entity) {
|
danielebarchiesi@4
|
884 if (entity_has_status($this->entityType, $entity, ENTITY_IN_CODE)) {
|
danielebarchiesi@4
|
885 entity_defaults_rebuild(array($this->entityType));
|
danielebarchiesi@4
|
886 break;
|
danielebarchiesi@4
|
887 }
|
danielebarchiesi@4
|
888 }
|
danielebarchiesi@4
|
889 }
|
danielebarchiesi@4
|
890 }
|
danielebarchiesi@4
|
891
|
danielebarchiesi@4
|
892 /**
|
danielebarchiesi@4
|
893 * Overridden to care about reverted bundle entities and to skip Rules.
|
danielebarchiesi@4
|
894 */
|
danielebarchiesi@4
|
895 public function invoke($hook, $entity) {
|
danielebarchiesi@4
|
896 if ($hook == 'delete') {
|
danielebarchiesi@4
|
897 // To ease figuring out whether this is a revert, make sure that the
|
danielebarchiesi@4
|
898 // entity status is updated in case the providing module has been
|
danielebarchiesi@4
|
899 // disabled.
|
danielebarchiesi@4
|
900 if (entity_has_status($this->entityType, $entity, ENTITY_IN_CODE) && !module_exists($entity->{$this->moduleKey})) {
|
danielebarchiesi@4
|
901 $entity->{$this->statusKey} = ENTITY_CUSTOM;
|
danielebarchiesi@4
|
902 }
|
danielebarchiesi@4
|
903 $is_revert = entity_has_status($this->entityType, $entity, ENTITY_IN_CODE);
|
danielebarchiesi@4
|
904 }
|
danielebarchiesi@4
|
905
|
danielebarchiesi@4
|
906 if (!empty($this->entityInfo['fieldable']) && function_exists($function = 'field_attach_' . $hook)) {
|
danielebarchiesi@4
|
907 $function($this->entityType, $entity);
|
danielebarchiesi@4
|
908 }
|
danielebarchiesi@4
|
909
|
danielebarchiesi@4
|
910 if (isset($this->entityInfo['bundle of']) && $type = $this->entityInfo['bundle of']) {
|
danielebarchiesi@4
|
911 // Call field API bundle attachers for the entity we are a bundle of.
|
danielebarchiesi@4
|
912 if ($hook == 'insert') {
|
danielebarchiesi@4
|
913 field_attach_create_bundle($type, $entity->{$this->bundleKey});
|
danielebarchiesi@4
|
914 }
|
danielebarchiesi@4
|
915 elseif ($hook == 'delete' && !$is_revert) {
|
danielebarchiesi@4
|
916 field_attach_delete_bundle($type, $entity->{$this->bundleKey});
|
danielebarchiesi@4
|
917 }
|
danielebarchiesi@4
|
918 elseif ($hook == 'update' && $id = $entity->{$this->nameKey}) {
|
danielebarchiesi@4
|
919 if ($entity->original->{$this->bundleKey} != $entity->{$this->bundleKey}) {
|
danielebarchiesi@4
|
920 field_attach_rename_bundle($type, $entity->original->{$this->bundleKey}, $entity->{$this->bundleKey});
|
danielebarchiesi@4
|
921 }
|
danielebarchiesi@4
|
922 }
|
danielebarchiesi@4
|
923 }
|
danielebarchiesi@4
|
924 // Invoke the hook.
|
danielebarchiesi@4
|
925 module_invoke_all($this->entityType . '_' . $hook, $entity);
|
danielebarchiesi@4
|
926 // Invoke the respective entity level hook.
|
danielebarchiesi@4
|
927 if ($hook == 'presave' || $hook == 'insert' || $hook == 'update' || $hook == 'delete') {
|
danielebarchiesi@4
|
928 module_invoke_all('entity_' . $hook, $entity, $this->entityType);
|
danielebarchiesi@4
|
929 }
|
danielebarchiesi@4
|
930 }
|
danielebarchiesi@4
|
931
|
danielebarchiesi@4
|
932 /**
|
danielebarchiesi@4
|
933 * Overridden to care exportables that are overridden.
|
danielebarchiesi@4
|
934 */
|
danielebarchiesi@4
|
935 public function save($entity, DatabaseTransaction $transaction = NULL) {
|
danielebarchiesi@4
|
936 // Preload $entity->original by name key if necessary.
|
danielebarchiesi@4
|
937 if (!empty($entity->{$this->nameKey}) && empty($entity->{$this->idKey}) && !isset($entity->original)) {
|
danielebarchiesi@4
|
938 $entity->original = entity_load_unchanged($this->entityType, $entity->{$this->nameKey});
|
danielebarchiesi@4
|
939 }
|
danielebarchiesi@4
|
940 // Update the status for entities getting overridden.
|
danielebarchiesi@4
|
941 if (entity_has_status($this->entityType, $entity, ENTITY_IN_CODE) && empty($entity->is_rebuild)) {
|
danielebarchiesi@4
|
942 $entity->{$this->statusKey} |= ENTITY_CUSTOM;
|
danielebarchiesi@4
|
943 }
|
danielebarchiesi@4
|
944 return parent::save($entity, $transaction);
|
danielebarchiesi@4
|
945 }
|
danielebarchiesi@4
|
946
|
danielebarchiesi@4
|
947 /**
|
danielebarchiesi@4
|
948 * Overridden.
|
danielebarchiesi@4
|
949 */
|
danielebarchiesi@4
|
950 public function export($entity, $prefix = '') {
|
danielebarchiesi@4
|
951 $vars = get_object_vars($entity);
|
danielebarchiesi@4
|
952 unset($vars[$this->statusKey], $vars[$this->moduleKey], $vars['is_new']);
|
danielebarchiesi@4
|
953 if ($this->nameKey != $this->idKey) {
|
danielebarchiesi@4
|
954 unset($vars[$this->idKey]);
|
danielebarchiesi@4
|
955 }
|
danielebarchiesi@4
|
956 return entity_var_json_export($vars, $prefix);
|
danielebarchiesi@4
|
957 }
|
danielebarchiesi@4
|
958
|
danielebarchiesi@4
|
959 /**
|
danielebarchiesi@4
|
960 * Implements EntityAPIControllerInterface.
|
danielebarchiesi@4
|
961 */
|
danielebarchiesi@4
|
962 public function view($entities, $view_mode = 'full', $langcode = NULL, $page = NULL) {
|
danielebarchiesi@4
|
963 $view = parent::view($entities, $view_mode, $langcode, $page);
|
danielebarchiesi@4
|
964
|
danielebarchiesi@4
|
965 if ($this->nameKey != $this->idKey) {
|
danielebarchiesi@4
|
966 // Re-key the view array to be keyed by name.
|
danielebarchiesi@4
|
967 $return = array();
|
danielebarchiesi@4
|
968 foreach ($view[$this->entityType] as $id => $content) {
|
danielebarchiesi@4
|
969 $key = isset($content['#entity']->{$this->nameKey}) ? $content['#entity']->{$this->nameKey} : NULL;
|
danielebarchiesi@4
|
970 $return[$this->entityType][$key] = $content;
|
danielebarchiesi@4
|
971 }
|
danielebarchiesi@4
|
972 $view = $return;
|
danielebarchiesi@4
|
973 }
|
danielebarchiesi@4
|
974 return $view;
|
danielebarchiesi@4
|
975 }
|
danielebarchiesi@4
|
976 }
|